From beba6b725dc3de73d93a2f8fc181f8a49400c0c2 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 28 Mar 2025 12:17:34 +0530 Subject: [PATCH 001/182] Add missing cstdint header for newer gcc This used to be implicitly included, but isn't as of gcc 12. --- contrib/epee/include/epee/storages/parserse_base_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/epee/include/epee/storages/parserse_base_utils.h b/contrib/epee/include/epee/storages/parserse_base_utils.h index c25982b5603..f91c33310d9 100755 --- a/contrib/epee/include/epee/storages/parserse_base_utils.h +++ b/contrib/epee/include/epee/storages/parserse_base_utils.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include From aa904b21e5f2e052cb1dd0bdb668309e9dd5ad60 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 28 Mar 2025 12:40:32 +0530 Subject: [PATCH 002/182] Modified cryptonote_config.h macros --- Makefile | 4 +- .../serialization/keyvalue_serialization.h | 4 +- src/beldex_economy.h | 60 +- src/blockchain_db/blockchain_db.cpp | 4 +- src/blockchain_db/lmdb/db_lmdb.cpp | 50 +- src/blockchain_db/testdb.h | 2 +- .../blockchain_ancestry.cpp | 2 +- src/blockchain_utilities/blockchain_depth.cpp | 2 +- .../blockchain_export.cpp | 2 +- src/blockchain_utilities/blockchain_prune.cpp | 6 +- .../blockchain_prune_known_spent_data.cpp | 2 +- src/blockchain_utilities/blockchain_stats.cpp | 2 +- src/blockchain_utilities/blockchain_usage.cpp | 2 +- src/checkpoints/checkpoints.cpp | 8 +- src/checkpoints/checkpoints.h | 2 +- src/common/file.cpp | 32 +- src/common/pruning.cpp | 32 +- src/common/pruning.h | 16 +- src/common/rules.cpp | 6 +- src/common/rules.h | 10 +- src/cryptonote_basic/account.cpp | 2 +- src/cryptonote_basic/cryptonote_basic.h | 98 +-- .../cryptonote_basic_impl.cpp | 52 +- src/cryptonote_basic/cryptonote_basic_impl.h | 5 +- .../cryptonote_format_utils.cpp | 28 +- .../cryptonote_format_utils.h | 4 +- src/cryptonote_basic/difficulty.cpp | 18 +- src/cryptonote_basic/hardfork.cpp | 64 +- src/cryptonote_basic/hardfork.h | 16 +- src/cryptonote_config.h | 654 +++++++++--------- src/cryptonote_core/beldex_name_system.cpp | 27 +- src/cryptonote_core/beldex_name_system.h | 12 +- src/cryptonote_core/blockchain.cpp | 241 ++++--- src/cryptonote_core/blockchain.h | 8 +- src/cryptonote_core/cryptonote_core.cpp | 57 +- src/cryptonote_core/cryptonote_tx_utils.cpp | 74 +- src/cryptonote_core/cryptonote_tx_utils.h | 16 +- src/cryptonote_core/master_node_list.cpp | 213 +++--- src/cryptonote_core/master_node_list.h | 27 +- .../master_node_quorum_cop.cpp | 63 +- src/cryptonote_core/master_node_quorum_cop.h | 14 +- src/cryptonote_core/master_node_rules.cpp | 44 +- src/cryptonote_core/master_node_rules.h | 220 +++--- src/cryptonote_core/master_node_voting.cpp | 33 +- src/cryptonote_core/master_node_voting.h | 18 +- src/cryptonote_core/pos.cpp | 14 +- src/cryptonote_core/tx_pool.cpp | 51 +- src/cryptonote_core/tx_pool.h | 20 +- src/cryptonote_protocol/block_queue.cpp | 4 +- .../cryptonote_protocol_defs.cpp | 2 +- .../cryptonote_protocol_defs.h | 2 +- .../cryptonote_protocol_handler.inl | 47 +- src/cryptonote_protocol/levin_notify.cpp | 29 +- src/cryptonote_protocol/quorumnet.cpp | 2 +- src/daemon/command_line_args.h | 4 +- src/daemon/command_parser_executor.cpp | 18 +- src/daemon/daemon.cpp | 6 +- src/daemon/main.cpp | 20 +- src/daemon/rpc_command_executor.cpp | 91 +-- src/daemonizer/windows_daemonizer.inl | 4 +- src/debug_utilities/cn_deserialize.cpp | 17 +- src/device/device_default.cpp | 14 +- src/device_trezor/CMakeLists.txt | 2 - src/device_trezor/trezor/protocol.cpp | 8 +- src/gen_multisig/gen_multisig.cpp | 2 +- src/multisig/multisig.cpp | 4 +- src/net/dandelionpp.cpp | 2 +- src/p2p/net_node.cpp | 18 +- src/p2p/net_node.h | 23 +- src/p2p/net_node.inl | 177 +++-- src/p2p/net_peerlist.h | 6 +- src/p2p/net_peerlist_boost_serialization.h | 9 +- src/p2p/p2p_protocol_defs.h | 6 +- src/ringct/bulletproofs.cc | 4 +- src/ringct/rctSigs.cpp | 14 +- src/ringct/rctTypes.cpp | 4 +- src/rpc/core_rpc_server.cpp | 70 +- src/rpc/core_rpc_server_commands_defs.cpp | 5 +- src/rpc/core_rpc_server_commands_defs.h | 7 +- src/rpc/http_server.cpp | 2 +- src/rpc/lmq_server.cpp | 5 +- src/simplewallet/simplewallet.cpp | 50 +- src/simplewallet/simplewallet.h | 2 +- src/wallet/CMakeLists.txt | 5 +- src/wallet/api/CMakeLists.txt | 10 +- src/wallet/api/pending_transaction.cpp | 3 +- src/wallet/api/wallet.cpp | 21 +- src/wallet/node_rpc_proxy.cpp | 2 +- src/wallet/node_rpc_proxy.h | 2 +- src/wallet/ringdb.cpp | 6 +- src/wallet/tx_construction_data.h | 4 +- src/wallet/wallet2.cpp | 168 ++--- src/wallet/wallet2.h | 15 +- src/wallet/wallet_rpc_server.cpp | 44 +- .../wallet_rpc_server_commands_defs.cpp | 2 +- src/wallet/wallet_rpc_server_commands_defs.h | 2 +- tests/block_weight/block_weight.cpp | 14 +- tests/core_proxy/core_proxy.h | 4 +- tests/core_tests/beldex_tests.cpp | 156 ++--- tests/core_tests/beldex_tests.h | 4 +- tests/core_tests/block_reward.cpp | 24 +- tests/core_tests/block_validation.cpp | 78 +-- tests/core_tests/block_validation.h | 8 +- tests/core_tests/bulletproofs.cpp | 52 +- tests/core_tests/bulletproofs.h | 2 +- tests/core_tests/chain_switch_1.cpp | 8 +- tests/core_tests/chaingen.cpp | 186 +++-- tests/core_tests/chaingen.h | 68 +- tests/core_tests/chaingen001.cpp | 4 +- tests/core_tests/chaingen_main.cpp | 2 +- tests/core_tests/double_spend.inl | 2 +- tests/core_tests/integer_overflow.cpp | 14 +- tests/core_tests/master_nodes.cpp | 26 +- tests/core_tests/multisig.cpp | 66 +- tests/core_tests/multisig.h | 2 +- tests/core_tests/rct.cpp | 20 +- tests/core_tests/rct.h | 6 +- tests/core_tests/ring_signature_1.cpp | 12 +- tests/core_tests/tx_validation.cpp | 52 +- tests/core_tests/v2_tests.cpp | 6 +- tests/core_tests/v2_tests.h | 2 +- tests/core_tests/wallet_tools.cpp | 2 +- tests/difficulty/difficulty.cpp | 8 +- .../transactions_flow_test.cpp | 4 +- tests/performance_tests/check_tx_signature.h | 4 +- tests/performance_tests/construct_tx.h | 2 +- tests/trezor/trezor_tests.cpp | 2 +- tests/unit_tests/base58.cpp | 20 +- tests/unit_tests/block_reward.cpp | 26 +- tests/unit_tests/long_term_block_weight.cpp | 44 +- tests/unit_tests/master_nodes.cpp | 128 ++-- tests/unit_tests/node_server.cpp | 4 +- tests/unit_tests/output_distribution.cpp | 2 +- tests/unit_tests/pruning.cpp | 90 +-- 134 files changed, 2255 insertions(+), 2204 deletions(-) diff --git a/Makefile b/Makefile index 90ac981b73a..a5bee7fa890 100755 --- a/Makefile +++ b/Makefile @@ -97,11 +97,11 @@ release: cmake-release release-test: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) && $(MAKE) test + cd $(builddir)/release && cmake -D BUILD_TESTS=ON -D USE_LTO=OFF -D RANDOMX_ENABLE_JIT=OFF -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) && $(MAKE) test release-all: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -D BUILD_TESTS=OFF -DUSE_LTO=OFF -DRANDOMX_ENABLE_JIT=OFF -D CMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) release-static: mkdir -p $(builddir)/release diff --git a/contrib/epee/include/epee/serialization/keyvalue_serialization.h b/contrib/epee/include/epee/serialization/keyvalue_serialization.h index 73b5a04298d..e487761ca5c 100755 --- a/contrib/epee/include/epee/serialization/keyvalue_serialization.h +++ b/contrib/epee/include/epee/serialization/keyvalue_serialization.h @@ -158,8 +158,8 @@ public: \ using int_t = std::underlying_type_t; \ int_t int_value = is_store ? static_cast(this_ref.enum_) : 0; \ epee::serialization::perform_serialize(int_value, stg, parent_section, #enum_); \ - if (!is_store) \ - const_cast(this_ref.enum_) = static_cast(int_value); \ + if constexpr (!is_store) \ + this_ref.enum_ = static_cast(int_value); \ } while(0); // Stashes `this` in the storage object's context for a dependent type that needs to access it. diff --git a/src/beldex_economy.h b/src/beldex_economy.h index 526a1bd9c26..da80a78730e 100755 --- a/src/beldex_economy.h +++ b/src/beldex_economy.h @@ -1,32 +1,54 @@ #pragma once -#include +#include "cryptonote_config.h" -constexpr uint64_t COIN = (uint64_t)1000000000; // 1 BELDEX = pow(10, 9) -constexpr uint64_t MONEY_SUPPLY = ((uint64_t)(-1)); // MONEY_SUPPLY - total number coins to be generated -constexpr uint64_t EMISSION_LINEAR_BASE = ((uint64_t)(1) << 58); -constexpr uint64_t EMISSION_SUPPLY_MULTIPLIER = 19; -constexpr uint64_t EMISSION_SUPPLY_DIVISOR = 10; -constexpr uint64_t EMISSION_DIVISOR = 2000000; +namespace beldex { -constexpr uint64_t MODIFIED_STAKING_REQUIREMENT_HEIGHT = 56500; +inline constexpr uint64_t COIN = (uint64_t)1000000000; // 1 BELDEX = pow(10, 9) +inline constexpr size_t DISPLAY_DECIMAL_POINT = 9; +inline constexpr uint64_t MONEY_SUPPLY = ((uint64_t)(-1)); // MONEY_SUPPLY - total number coins to be generated +inline constexpr uint64_t EMISSION_LINEAR_BASE = ((uint64_t)(1) << 58); +inline constexpr uint64_t EMISSION_SUPPLY_MULTIPLIER = 19; +inline constexpr uint64_t EMISSION_SUPPLY_DIVISOR = 10; +inline constexpr uint64_t EMISSION_DIVISOR = 2000000; + +inline constexpr uint64_t MODIFIED_STAKING_REQUIREMENT_HEIGHT = 56500; // HF15 money supply parameters: -constexpr uint64_t BLOCK_REWARD_HF16 = 2 * COIN; -constexpr uint64_t BLOCK_REWARD_HF17_POS = 10 *COIN; -constexpr uint64_t MINER_REWARD_HF16 = BLOCK_REWARD_HF16 * 10 / 100; // Only until HF16 -constexpr uint64_t MN_REWARD_HF16 = BLOCK_REWARD_HF16 * 90 / 100; -constexpr uint64_t MN_REWARD_HF17_POS = BLOCK_REWARD_HF17_POS * 62.5 / 100; // After HF17 MN_REWARD changed about 6.25 BDX for each Block +inline constexpr uint64_t BLOCK_REWARD_HF16 = 2 * COIN; +inline constexpr uint64_t BLOCK_REWARD_HF17_POS = 10 *COIN; +inline constexpr uint64_t MINER_REWARD_HF16 = BLOCK_REWARD_HF16 * 10 / 100; // Only until HF16 +inline constexpr uint64_t MN_REWARD_HF16 = BLOCK_REWARD_HF16 * 90 / 100; +inline constexpr uint64_t MN_REWARD_HF17_POS = BLOCK_REWARD_HF17_POS * 62.5 / 100; // After HF17 MN_REWARD changed about 6.25 BDX for each Block (62.5%) // HF16+ money supply parameters: same as HF16 except the miner fee goes away and is redirected to // LF to be used exclusively for Beldex Chainflip liquidity seeding and incentives. See // https://github.com/beldex-project/beldex-improvement-proposals/issues/24 for more details. This ends // after 6 months. -constexpr uint64_t BLOCK_REWARD_HF17 = BLOCK_REWARD_HF16; -constexpr uint64_t FOUNDATION_REWARD_HF17 = BLOCK_REWARD_HF17_POS * 37.5 /100; //governance reward 3.75 BDX after HF17 +inline constexpr uint64_t BLOCK_REWARD_HF17 = BLOCK_REWARD_HF16; +inline constexpr uint64_t FOUNDATION_REWARD_HF17 = BLOCK_REWARD_HF17_POS * 37.5 /100; //governance reward 3.75 BDX after HF17 (37.5%) static_assert(MINER_REWARD_HF16 + MN_REWARD_HF16 == BLOCK_REWARD_HF16); static_assert(MN_REWARD_HF17_POS + FOUNDATION_REWARD_HF17 == BLOCK_REWARD_HF17_POS); +// ------------------------------------------------------------------------------------------------- +// +// Master Nodes +// +// ------------------------------------------------------------------------------------------------- + +// Fixed staking requirement see +// master_node_rules.cpp): +inline constexpr uint64_t STAKING_REQUIREMENT = 10'000 * COIN; +// testnet/devnet/fakenet have always had a fixed 10000 BDX staking requirement: +inline constexpr uint64_t STAKING_REQUIREMENT_TESTNET = 10'000 * COIN; + +// Max contributors: +inline constexpr size_t MAX_NUMBER_OF_CONTRIBUTORS = 4; + +// // Required operator contribution is 1/4 of the staking requirement +// inline constexpr uint64_t MINIMUM_OPERATOR_DIVISOR = 4; + + // ------------------------------------------------------------------------------------------------- // // Flash @@ -47,6 +69,8 @@ static_assert(FLASH_MINER_TX_FEE_PERCENT >= 100, "flash miner fee cannot be smal static_assert(FLASH_BURN_FIXED >= 0, "fixed flash burn amount cannot be negative"); static_assert(FLASH_BURN_TX_FEE_PERCENT_OLD >= 0, "flash burn tx percent cannot be negative"); +} // namespace beldex + // ------------------------------------------------------------------------------------------------- // // BNS @@ -84,12 +108,12 @@ constexpr bool is_renewal_type(mapping_years y) { return y >= mapping_years::bns // days per registration "year" to allow for some blockchain time drift + leap years. constexpr uint64_t REGISTRATION_YEAR_DAYS = 368; -constexpr uint64_t burn_needed(uint8_t hf_version, mapping_years map_years) +constexpr uint64_t burn_needed(cryptonote::hf hf_version, mapping_years map_years) { uint64_t result = 0; - const uint64_t basic_fee = (hf_version >= 18 ? 500 * COIN : // cryptonote::network_version_18_bns -- but don't want to add cryptonote_config.h include - 15 * COIN // cryptonote::network_version_17_POS + const uint64_t basic_fee = (hf_version >= cryptonote::hf::hf18_bns ? 500 * beldex::COIN : // cryptonote::hf::hf18_bns -- but don't want to add cryptonote_config.h include + 15 * beldex::COIN // cryptonote::hf::hf17_POS ); switch (map_years) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index f8bc95eefb9..6f623b7d32e 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -432,8 +432,8 @@ void BlockchainDB::fill_timestamps_and_difficulties_for_pow(cryptonote::network_ return; uint64_t const top_block_height = chain_height - 1; - bool const before_hf16 = !is_hard_fork_at_least(nettype, network_version_17_POS, chain_height); - uint64_t const block_count = DIFFICULTY_BLOCKS_COUNT(before_hf16); + bool const before_hf16 = !is_hard_fork_at_least(nettype, hf::hf17_POS, chain_height); + uint64_t const block_count = old::DIFFICULTY_BLOCKS_COUNT(before_hf16); timestamps.reserve(block_count); difficulties.reserve(block_count); diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 25d3031b185..6b1477f3167 100755 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -894,12 +894,12 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l bi.bi_diff = cumulative_difficulty; bi.bi_hash = blk_hash; bi.bi_cum_rct = num_rct_outs; - if (blk.major_version >= 4 && m_height > 0) + if (m_height > 0) { uint64_t last_height = m_height-1; MDB_val_set(h, last_height); if ((result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &h, MDB_GET_BOTH))) - throw1(BLOCK_DNE(lmdb_error("Failed to get block info: ", result).c_str())); + throw1(BLOCK_DNE(lmdb_error("Failed to get parent block info: ", result).c_str())); const mdb_block_info *bi_prev = (const mdb_block_info*)h.mv_data; bi.bi_cum_rct += bi_prev->bi_cum_rct; } @@ -1417,11 +1417,11 @@ void BlockchainLMDB::open(const fs::path& filename, cryptonote::network_type net // check for existing LMDB files in base directory auto old_files = filename.parent_path(); - if (fs::exists(old_files / CRYPTONOTE_BLOCKCHAINDATA_FILENAME) - || fs::exists(old_files / CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME)) + if (fs::exists(old_files / BLOCKCHAINDATA_FILENAME) + || fs::exists(old_files / BLOCKCHAINDATA_LOCK_FILENAME)) { LOG_PRINT_L0("Found existing LMDB files in " << old_files.u8string()); - LOG_PRINT_L0("Move " << CRYPTONOTE_BLOCKCHAINDATA_FILENAME << " and/or " << CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME << " to " << filename << ", or delete them, and then restart"); + LOG_PRINT_L0("Move " << BLOCKCHAINDATA_FILENAME << " and/or " << BLOCKCHAINDATA_LOCK_FILENAME << " to " << filename << ", or delete them, and then restart"); throw DB_ERROR("Database could not be opened"); } @@ -1729,14 +1729,14 @@ std::vector BlockchainLMDB::get_filenames() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); std::vector paths; - paths.push_back(m_folder / CRYPTONOTE_BLOCKCHAINDATA_FILENAME); - paths.push_back(m_folder / CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME); + paths.push_back(m_folder / BLOCKCHAINDATA_FILENAME); + paths.push_back(m_folder / BLOCKCHAINDATA_LOCK_FILENAME); return paths; } bool BlockchainLMDB::remove_data_file(const fs::path& folder) const { - auto filename = folder / CRYPTONOTE_BLOCKCHAINDATA_FILENAME; + auto filename = folder / BLOCKCHAINDATA_FILENAME; try { fs::remove(filename); @@ -2052,10 +2052,10 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); const uint32_t log_stripes = tools::get_pruning_log_stripes(pruning_seed); - if (log_stripes && log_stripes != CRYPTONOTE_PRUNING_LOG_STRIPES) + if (log_stripes && log_stripes != PRUNING_LOG_STRIPES) throw0(DB_ERROR("Pruning seed not in range")); pruning_seed = tools::get_pruning_stripe(pruning_seed); - if (pruning_seed > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES)) + if (pruning_seed > (1ul << PRUNING_LOG_STRIPES)) throw0(DB_ERROR("Pruning seed not in range")); check_open(); @@ -2090,7 +2090,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) } if (pruning_seed == 0) pruning_seed = tools::get_random_stripe(); - pruning_seed = tools::make_pruning_seed(pruning_seed, CRYPTONOTE_PRUNING_LOG_STRIPES); + pruning_seed = tools::make_pruning_seed(pruning_seed, PRUNING_LOG_STRIPES); v.mv_data = &pruning_seed; v.mv_size = sizeof(pruning_seed); result = mdb_put(txn, m_properties, &k, &v, 0); @@ -2108,9 +2108,9 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) pruning_seed = tools::get_pruning_stripe(data); if (tools::get_pruning_stripe(data) != pruning_seed) throw0(DB_ERROR("Blockchain already pruned with different seed")); - if (tools::get_pruning_log_stripes(data) != CRYPTONOTE_PRUNING_LOG_STRIPES) + if (tools::get_pruning_log_stripes(data) != PRUNING_LOG_STRIPES) throw0(DB_ERROR("Blockchain already pruned with different base")); - pruning_seed = tools::make_pruning_seed(pruning_seed, CRYPTONOTE_PRUNING_LOG_STRIPES); + pruning_seed = tools::make_pruning_seed(pruning_seed, PRUNING_LOG_STRIPES); prune_tip_table = (mode == prune_mode_update); } else @@ -2149,7 +2149,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) uint64_t block_height; memcpy(&block_height, v.mv_data, sizeof(block_height)); - if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS < blockchain_height) + if (block_height + PRUNING_TIP_BLOCKS < blockchain_height) { ++n_total_records; if (!tools::has_unpruned_block(block_height, blockchain_height, pruning_seed) && !is_v1_tx(c_txs_pruned, &k)) @@ -2217,7 +2217,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) txindex ti; memcpy(&ti, v.mv_data, sizeof(ti)); const uint64_t block_height = ti.data.block_id; - if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height) + if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height) { MDB_val_set(kp, ti.data.tx_id); MDB_val_set(vp, block_height); @@ -4389,10 +4389,10 @@ std::map> BlockchainLMDB::get while (num_elems > 0) { const tx_out_index toi = get_output_tx_and_index(amount, num_elems - 1); const uint64_t height = get_tx_block_height(toi.first); - const uint8_t hf_version = cryptonote::get_network_version(nettype, height); - LOG_PRINT_L2("TX hf_version:" << hf_version); + const auto hf_version = cryptonote::get_network_version(nettype, height); + LOG_PRINT_L2("TX hf_version:" << static_cast(hf_version)); - if ((height + (hf_version>=cryptonote::network_version_17_POS?CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17:CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)) <= blockchain_height) + if ((height + (hf_version >= cryptonote::hf::hf17_POS ? DEFAULT_TX_SPENDABLE_AGE_V17 : cryptonote::old::DEFAULT_TX_SPENDABLE_AGE)) <= blockchain_height) break; --num_elems; } @@ -4656,7 +4656,7 @@ bool BlockchainLMDB::is_read_only() const uint64_t BlockchainLMDB::get_database_size() const { - return fs::file_size(m_folder / CRYPTONOTE_BLOCKCHAINDATA_FILENAME); + return fs::file_size(m_folder / BLOCKCHAINDATA_FILENAME); } void BlockchainLMDB::fixup(cryptonote::network_type nettype) @@ -4699,7 +4699,7 @@ void BlockchainLMDB::fixup(cryptonote::network_type nettype) add_timestamp_and_difficulty(nettype, curr_chain_height, timestamps, difficulties, curr_timestamp, curr_cumulative_diff); // NOTE: Calculate next block difficulty - if (is_hard_fork_at_least(nettype, cryptonote::network_version_17_POS, curr_height) + if (is_hard_fork_at_least(nettype, hf::hf17_POS, curr_height) && block_header_has_POS_components(get_block_header_from_height(curr_height))) { diff = POS_FIXED_DIFFICULTY; // TARGET_BLOCK_TIME @@ -4708,7 +4708,7 @@ void BlockchainLMDB::fixup(cryptonote::network_type nettype) { diff = next_difficulty_v2(timestamps, difficulties, - tools::to_seconds(TARGET_BLOCK_TIME_OLD), //use OLD BLOCK_TIME + tools::to_seconds(old::TARGET_BLOCK_TIME_12), //use OLD BLOCK_TIME difficulty_mode(nettype, curr_height + 1)); } } @@ -5694,7 +5694,7 @@ void BlockchainLMDB::migrate_3_4() throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); const uint64_t blockchain_height = db_stats.ms_entries; - boost::circular_buffer long_term_block_weights(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE); + boost::circular_buffer long_term_block_weights(LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE); /* the block_info table name is the same but the old version and new version * have incompatible data. Create a new table. We want the name to be similar @@ -5760,8 +5760,8 @@ void BlockchainLMDB::migrate_3_4() throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str())); if (vb.mv_size == 0) throw0(DB_ERROR("Invalid data from m_blocks")); - const uint8_t block_major_version = *((const uint8_t*)vb.mv_data); - if (block_major_version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT) + const hf block_major_version {*((const uint8_t*)vb.mv_data)}; + if (block_major_version >= feature::LONG_TERM_BLOCK_WEIGHT) past_long_term_weight = true; } @@ -5769,7 +5769,7 @@ void BlockchainLMDB::migrate_3_4() if (past_long_term_weight) { std::vector weights(long_term_block_weights.begin(), long_term_block_weights.end()); - uint64_t long_term_effective_block_median_weight = std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, tools::median(weights)); + uint64_t long_term_effective_block_median_weight = std::max(BLOCK_GRANTED_FULL_REWARD_ZONE_V5, tools::median(weights)); long_term_block_weight = std::min(bi.bi_weight, long_term_effective_block_median_weight + long_term_effective_block_median_weight * 2 / 5); } else diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index f2fee7c9f7a..1b3079ca18a 100755 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -43,7 +43,7 @@ namespace cryptonote class BaseTestDB: public cryptonote::BlockchainDB { public: BaseTestDB() {} - virtual void open(const fs::path& filename, network_type nettype = FAKECHAIN, const int db_flags = 0) override { } + virtual void open(const fs::path& filename, network_type nettype = network_type::FAKECHAIN, const int db_flags = 0) override { } virtual void close() override {} virtual void sync() override {} virtual void safesyncmode(const bool onoff) override {} diff --git a/src/blockchain_utilities/blockchain_ancestry.cpp b/src/blockchain_utilities/blockchain_ancestry.cpp index ad836b6289c..16dd215ce69 100755 --- a/src/blockchain_utilities/blockchain_ancestry.cpp +++ b/src/blockchain_utilities/blockchain_ancestry.cpp @@ -400,7 +400,7 @@ int main(int argc, char* argv[]) std::string opt_data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir); bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); - network_type net_type = opt_testnet ? TESTNET : opt_devnet ? DEVNET : MAINNET; + network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET; std::string opt_txid_string = command_line::get_arg(vm, arg_txid); std::string opt_output_string = command_line::get_arg(vm, arg_output); uint64_t opt_height = command_line::get_arg(vm, arg_height); diff --git a/src/blockchain_utilities/blockchain_depth.cpp b/src/blockchain_utilities/blockchain_depth.cpp index 7a0f6322b57..fb55c45938f 100755 --- a/src/blockchain_utilities/blockchain_depth.cpp +++ b/src/blockchain_utilities/blockchain_depth.cpp @@ -100,7 +100,7 @@ int main(int argc, char* argv[]) bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); - network_type net_type = opt_testnet ? TESTNET : opt_devnet ? DEVNET : MAINNET; + network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET; std::string opt_txid_string = command_line::get_arg(vm, arg_txid); uint64_t opt_height = command_line::get_arg(vm, arg_height); bool opt_include_coinbase = command_line::get_arg(vm, arg_include_coinbase); diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index b6ff123fe5e..859c091bbd9 100755 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -142,7 +142,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, nullptr, opt_testnet ? cryptonote::TESTNET : opt_devnet ? cryptonote::DEVNET : cryptonote::MAINNET); + r = core_storage->init(db, nullptr, opt_testnet ? cryptonote::network_type::TESTNET : opt_devnet ? cryptonote::network_type::DEVNET : cryptonote::network_type::MAINNET); if (core_storage->get_blockchain_pruning_seed() && !opt_blocks_dat) { diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index fd620282ab7..e0922f31d3f 100755 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -304,7 +304,7 @@ static void prune(MDB_env *env0, MDB_env *env1) if (dbr) throw std::runtime_error("Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); MDB_val k, v; - uint32_t pruning_seed = tools::make_pruning_seed(tools::get_random_stripe(), CRYPTONOTE_PRUNING_LOG_STRIPES); + uint32_t pruning_seed = tools::make_pruning_seed(tools::get_random_stripe(), PRUNING_LOG_STRIPES); static char pruning_seed_key[] = "pruning_seed"; k.mv_data = pruning_seed_key; k.mv_size = strlen("pruning_seed") + 1; @@ -334,7 +334,7 @@ static void prune(MDB_env *env0, MDB_env *env1) const txindex *ti = (const txindex*)v.mv_data; const uint64_t block_height = ti->data.block_id; MDB_val_set(kk, ti->data.tx_id); - if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height) + if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height) { MDEBUG(block_height << "/" << blockchain_height << " is in tip"); MDB_val_set(vv, block_height); @@ -494,7 +494,7 @@ int main(int argc, char* argv[]) bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); - network_type net_type = opt_testnet ? TESTNET : opt_devnet ? DEVNET : MAINNET; + network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET; bool opt_copy_pruned_database = command_line::get_arg(vm, arg_copy_pruned_database); std::string data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir); while (tools::ends_with(data_dir, "/") || tools::ends_with(data_dir, "\\")) diff --git a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp index c18c8d4bec6..ee5c5391da3 100755 --- a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp +++ b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp @@ -160,7 +160,7 @@ int main(int argc, char* argv[]) bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); - network_type net_type = opt_testnet ? TESTNET : opt_devnet ? DEVNET : MAINNET; + network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET; bool opt_verbose = command_line::get_arg(vm, arg_verbose); bool opt_dry_run = command_line::get_arg(vm, arg_dry_run); diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index ef9d5b8c319..4de46d3a6d9 100755 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -114,7 +114,7 @@ int main(int argc, char* argv[]) std::string opt_data_dir = command_line::get_arg(vm, cryptonote::arg_data_dir); bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); - network_type net_type = opt_testnet ? TESTNET : opt_devnet ? DEVNET : MAINNET; + network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET; block_start = command_line::get_arg(vm, arg_block_start); block_stop = command_line::get_arg(vm, arg_block_stop); bool do_inputs = command_line::get_arg(vm, arg_inputs); diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index 1d6fb3835ab..d8fe52a01c8 100755 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -132,7 +132,7 @@ int main(int argc, char* argv[]) bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); - network_type net_type = opt_testnet ? TESTNET : opt_devnet ? DEVNET : MAINNET; + network_type net_type = opt_testnet ? network_type::TESTNET : opt_devnet ? network_type::DEVNET : network_type::MAINNET; bool opt_rct_only = command_line::get_arg(vm, arg_rct_only); // If we wanted to use the memory pool, we would set up a fake_core. diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 4a3d5d77dd5..59ec50f4576 100755 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -78,10 +78,10 @@ namespace cryptonote { crypto::hash result = crypto::null_hash; *height = 0; - if (nettype != MAINNET && nettype != TESTNET) + if (nettype != network_type::MAINNET && nettype != network_type::TESTNET) return result; - if (nettype == MAINNET) + if (nettype == network_type::MAINNET) { uint64_t last_index = beldex::array_count(HARDCODED_MAINNET_CHECKPOINTS) - 1; height_to_hash const &entry = HARDCODED_MAINNET_CHECKPOINTS[last_index]; @@ -180,7 +180,7 @@ namespace cryptonote void checkpoints::block_add(const block_add_info& info) { uint64_t const height = get_block_height(info.block); - if (height < master_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL || info.block.major_version < network_version_13_checkpointing) + if (height < master_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL || info.block.major_version < hf::hf13_checkpointing) return; uint64_t end_cull_height = 0; @@ -317,7 +317,7 @@ namespace cryptonote return true; #if !defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) - if (nettype == MAINNET) + if (nettype == network_type::MAINNET) { for (size_t i = 0; i < beldex::array_count(HARDCODED_MAINNET_CHECKPOINTS); ++i) { diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index f6327649296..066487d373a 100755 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -195,7 +195,7 @@ namespace cryptonote bool init(network_type nettype, class BlockchainDB *db); private: - network_type m_nettype = UNDEFINED; + network_type m_nettype = network_type::UNDEFINED; uint64_t m_last_cull_height = 0; uint64_t m_immutable_height = 0; BlockchainDB *m_db; diff --git a/src/common/file.cpp b/src/common/file.cpp index 05f94eca27e..9df0cf68916 100755 --- a/src/common/file.cpp +++ b/src/common/file.cpp @@ -254,7 +254,7 @@ namespace tools { } -#ifdef WIN32 +#ifdef _WIN32 fs::path get_special_folder_path(int nfolder, bool iscreate) { WCHAR psz_path[MAX_PATH] = L""; @@ -267,30 +267,28 @@ namespace tools { LOG_ERROR("SHGetSpecialFolderPathW() failed, could not obtain requested path."); return ""; } +#endif - // Windows < Vista: C:\Documents and Settings\Username\Application Data\CRYPTONOTE_NAME - // Windows >= Vista: C:\Users\Username\AppData\Roaming\CRYPTONOTE_NAME - fs::path get_default_data_dir() - { - return get_special_folder_path(CSIDL_COMMON_APPDATA, true) / fs::u8path(CRYPTONOTE_NAME); - } - fs::path get_depreciated_default_data_dir() - { - return get_special_folder_path(CSIDL_COMMON_APPDATA, true) / fs::u8path("beldex"); - } + // Windows < Vista: C:\Documents and Settings\Username\Application Data\... + // Windows >= Vista: C:\Users\Username\AppData\Roaming\... + // Sane OSes: ~/ + static fs::path get_default_parent_dir() { +#ifdef _WIN32 + return get_special_folder_path(CSIDL_COMMON_APPDATA, true); #else - // Non-windows: ~/.CRYPTONOTE_NAME + char* home = std::getenv("HOME"); + return home && std::strlen(home) ? fs::u8path(home) : fs::current_path(); +#endif + } + fs::path get_default_data_dir() { - char* home = std::getenv("HOME"); - return (home && std::strlen(home) ? fs::u8path(home) : fs::current_path()) / fs::u8path("." CRYPTONOTE_NAME); + return get_default_parent_dir() / fs::u8path(cryptonote::DATA_DIRNAME); } fs::path get_depreciated_default_data_dir() { - char* home = std::getenv("HOME"); - return (home && std::strlen(home) ? fs::u8path(home) : fs::current_path()) / fs::u8path(".beldex"); + return get_default_parent_dir() / fs::u8path(cryptonote::DATA_DIRNAME); } -#endif void set_strict_default_file_permissions(bool strict) { diff --git a/src/common/pruning.cpp b/src/common/pruning.cpp index 527c62c4d55..0e62658bb3f 100755 --- a/src/common/pruning.cpp +++ b/src/common/pruning.cpp @@ -34,6 +34,8 @@ namespace tools { +using namespace cryptonote; + uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes) { CHECK_AND_ASSERT_THROW_MES(log_stripes <= PRUNING_SEED_LOG_STRIPES_MASK, "log_stripes out of range"); @@ -53,9 +55,9 @@ bool has_unpruned_block(uint64_t block_height, uint64_t blockchain_height, uint3 uint32_t get_pruning_stripe(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes) { - if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height) + if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height) return 0; - return ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & (uint64_t)((1ul << log_stripes) - 1)) + 1; + return ((block_height / PRUNING_STRIPE_SIZE) & (uint64_t)((1ul << log_stripes) - 1)) + 1; } uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uint32_t log_stripes) @@ -68,24 +70,24 @@ uint32_t get_pruning_seed(uint64_t block_height, uint64_t blockchain_height, uin uint64_t get_next_unpruned_block_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) { - CHECK_AND_ASSERT_MES(block_height <= CRYPTONOTE_MAX_BLOCK_NUMBER+1, block_height, "block_height too large"); - CHECK_AND_ASSERT_MES(blockchain_height <= CRYPTONOTE_MAX_BLOCK_NUMBER+1, block_height, "blockchain_height too large"); + CHECK_AND_ASSERT_MES(block_height <= MAX_BLOCK_NUMBER+1, block_height, "block_height too large"); + CHECK_AND_ASSERT_MES(blockchain_height <= MAX_BLOCK_NUMBER+1, block_height, "blockchain_height too large"); const uint32_t stripe = get_pruning_stripe(pruning_seed); if (stripe == 0) return block_height; - if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height) + if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height) return block_height; const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed); - const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : CRYPTONOTE_PRUNING_LOG_STRIPES; + const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : PRUNING_LOG_STRIPES; const uint64_t mask = (1ul << log_stripes) - 1; - const uint32_t block_pruning_stripe = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1; + const uint32_t block_pruning_stripe = ((block_height / PRUNING_STRIPE_SIZE) & mask) + 1; if (block_pruning_stripe == stripe) return block_height; - const uint64_t cycles = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) >> log_stripes); + const uint64_t cycles = ((block_height / PRUNING_STRIPE_SIZE) >> log_stripes); const uint64_t cycle_start = cycles + ((stripe > block_pruning_stripe) ? 0 : 1); - const uint64_t h = cycle_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE; - if (h + CRYPTONOTE_PRUNING_TIP_BLOCKS > blockchain_height) - return blockchain_height < CRYPTONOTE_PRUNING_TIP_BLOCKS ? 0 : blockchain_height - CRYPTONOTE_PRUNING_TIP_BLOCKS; + const uint64_t h = cycle_start * (PRUNING_STRIPE_SIZE << log_stripes) + (stripe - 1) * PRUNING_STRIPE_SIZE; + if (h + PRUNING_TIP_BLOCKS > blockchain_height) + return blockchain_height < PRUNING_TIP_BLOCKS ? 0 : blockchain_height - PRUNING_TIP_BLOCKS; CHECK_AND_ASSERT_MES(h >= block_height, block_height, "h < block_height, unexpected"); return h; } @@ -95,12 +97,12 @@ uint64_t get_next_pruned_block_height(uint64_t block_height, uint64_t blockchain const uint32_t stripe = get_pruning_stripe(pruning_seed); if (stripe == 0) return blockchain_height; - if (block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height) + if (block_height + PRUNING_TIP_BLOCKS >= blockchain_height) return blockchain_height; const uint32_t seed_log_stripes = get_pruning_log_stripes(pruning_seed); - const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : CRYPTONOTE_PRUNING_LOG_STRIPES; + const uint64_t log_stripes = seed_log_stripes ? seed_log_stripes : PRUNING_LOG_STRIPES; const uint64_t mask = (1ul << log_stripes) - 1; - const uint32_t block_pruning_seed = ((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & mask) + 1; + const uint32_t block_pruning_seed = ((block_height / PRUNING_STRIPE_SIZE) & mask) + 1; if (block_pruning_seed != stripe) return block_height; const uint32_t next_stripe = 1 + (block_pruning_seed & mask); @@ -109,7 +111,7 @@ uint64_t get_next_pruned_block_height(uint64_t block_height, uint64_t blockchain uint32_t get_random_stripe() { - return 1 + crypto::rand() % (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES); + return 1 + crypto::rand() % (1ul << PRUNING_LOG_STRIPES); } } diff --git a/src/common/pruning.h b/src/common/pruning.h index f6cbfeffb37..1e234bbd0d9 100755 --- a/src/common/pruning.h +++ b/src/common/pruning.h @@ -32,13 +32,17 @@ namespace tools { - static constexpr uint32_t PRUNING_SEED_LOG_STRIPES_SHIFT = 7; - static constexpr uint32_t PRUNING_SEED_LOG_STRIPES_MASK = 0x7; - static constexpr uint32_t PRUNING_SEED_STRIPE_SHIFT = 0; - static constexpr uint32_t PRUNING_SEED_STRIPE_MASK = 0x7f; + inline constexpr uint32_t PRUNING_SEED_LOG_STRIPES_SHIFT = 7; + inline constexpr uint32_t PRUNING_SEED_LOG_STRIPES_MASK = 0x7; + inline constexpr uint32_t PRUNING_SEED_STRIPE_SHIFT = 0; + inline constexpr uint32_t PRUNING_SEED_STRIPE_MASK = 0x7f; - constexpr inline uint32_t get_pruning_log_stripes(uint32_t pruning_seed) { return (pruning_seed >> PRUNING_SEED_LOG_STRIPES_SHIFT) & PRUNING_SEED_LOG_STRIPES_MASK; } - inline uint32_t get_pruning_stripe(uint32_t pruning_seed) { if (pruning_seed == 0) return 0; return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK); } + inline constexpr uint32_t get_pruning_log_stripes(uint32_t pruning_seed) { + return (pruning_seed >> PRUNING_SEED_LOG_STRIPES_SHIFT) & PRUNING_SEED_LOG_STRIPES_MASK; + } + inline uint32_t get_pruning_stripe(uint32_t pruning_seed) { + if (pruning_seed == 0) return 0; return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK); + } uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes); diff --git a/src/common/rules.cpp b/src/common/rules.cpp index 4bafa1cb9ab..e6f308d3109 100755 --- a/src/common/rules.cpp +++ b/src/common/rules.cpp @@ -42,11 +42,11 @@ namespace rules bool is_output_unlocked(uint64_t unlock_time, uint64_t height,network_type nettype) { - if(unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) + if(unlock_time < MAX_BLOCK_NUMBER) { // ND: Instead of calling get_current_blockchain_height(), call m_db->height() // directly as get_current_blockchain_height() locks the recursive mutex. - if(height - 1 + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) + if(height - 1 + LOCKED_TX_ALLOWED_DELTA_BLOCKS >= unlock_time) return true; else return false; @@ -57,7 +57,7 @@ bool is_output_unlocked(uint64_t unlock_time, uint64_t height,network_type netty auto hf_version = ::cryptonote::get_network_version(nettype, height); //interpret as time uint64_t current_time = static_cast(time(NULL)); - if(current_time + tools::to_seconds((hf_version>=cryptonote::network_version_17_POS?CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V3:CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2)) >= unlock_time) + if(current_time + tools::to_seconds( LOCKED_TX_ALLOWED_DELTA_BLOCKS * (hf_version >= hf::hf17_POS ? TARGET_BLOCK_TIME : old::TARGET_BLOCK_TIME_12)) >= unlock_time) // have to change return true; else return false; diff --git a/src/common/rules.h b/src/common/rules.h index b8d504b7ca7..6b9ccc35076 100755 --- a/src/common/rules.h +++ b/src/common/rules.h @@ -27,16 +27,12 @@ // 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 +#include #include "cryptonote_config.h" -namespace cryptonote -{ - -namespace rules +namespace cryptonote::rules { bool is_output_unlocked(uint64_t unlock_time, uint64_t height,network_type nettype); -} // namespace rules - -} // namespace cryptonote +} // namespace cryptonote::rules \ No newline at end of file diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index dfd300481fc..5383369cbbc 100755 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -68,7 +68,7 @@ DISABLE_VS_WARNINGS(4244 4345) static_assert(sizeof(base_key) == sizeof(crypto::hash), "chacha key and hash should be the same size"); epee::mlocked> data; memcpy(data.data(), &base_key, sizeof(base_key)); - data[sizeof(base_key)] = config::HASH_KEY_MEMORY; + data[sizeof(base_key)] = cryptonote::hashkey::MEMORY; crypto::generate_chacha_key(data.data(), sizeof(data), key, 1); } //----------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index fcfc4bd6d71..c5a904288f0 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -176,9 +176,9 @@ namespace cryptonote static char const* version_to_string(txversion v); static char const* type_to_string(txtype type); - static constexpr txversion get_min_version_for_hf(uint8_t hf_version); - static txversion get_max_version_for_hf(uint8_t hf_version); - static constexpr txtype get_max_type_for_hf (uint8_t hf_version); + static constexpr txversion get_min_version_for_hf(hf hf_version); + static txversion get_max_version_for_hf(hf hf_version); + static constexpr txtype get_max_type_for_hf (hf hf_version); // tx information txversion version; @@ -392,22 +392,12 @@ namespace cryptonote struct block_header { - uint8_t major_version = cryptonote::network_version_7; - uint8_t minor_version = cryptonote::network_version_7; // now used as a voting mechanism, rather than how this particular block is built + hf major_version = hf::hf7; + uint8_t minor_version = 0; uint64_t timestamp; crypto::hash prev_id; uint32_t nonce; POS_header POS = {}; - - BEGIN_SERIALIZE() - VARINT_FIELD(major_version) - VARINT_FIELD(minor_version) - VARINT_FIELD(timestamp) - FIELD(prev_id) - FIELD(nonce) - if (major_version >= cryptonote::network_version_17_POS) - FIELD(POS) - END_SERIALIZE() }; struct block: public block_header @@ -433,21 +423,34 @@ namespace cryptonote // hash cache mutable crypto::hash hash; std::vector signatures; - - BEGIN_SERIALIZE_OBJECT() - if (Archive::is_deserializer) - set_hash_valid(false); - - FIELDS(static_cast(*this)) - FIELD(miner_tx) - FIELD(tx_hashes) - if (tx_hashes.size() > CRYPTONOTE_MAX_TX_PER_BLOCK) - throw std::invalid_argument{"too many txs in block"}; - if (major_version >= cryptonote::network_version_17_POS) - FIELD(signatures) - END_SERIALIZE() }; + template + void serialize_value(Archive& ar, block_header& b) { + using namespace serialization; + field(ar, "major_version", b.major_version); + field_varint(ar, "minor_version", b.minor_version); + field_varint(ar, "timestamp", b.timestamp); + field(ar, "prev_id", b.prev_id); + field(ar, "nonce", b.nonce); + if (b.major_version >= hf::hf17_POS) + field(ar, "POS", b.POS); + } + + template + void serialize_value(Archive& ar, block& b) { + auto _obj = ar.begin_object(); + if constexpr (Archive::is_deserializer) + b.set_hash_valid(false); + + serialization::value(ar, static_cast(b)); + field(ar, "miner_tx", b.miner_tx); + field(ar, "tx_hashes", b.tx_hashes); + if (b.tx_hashes.size() > MAX_TX_PER_BLOCK) + throw std::invalid_argument{"too many txs in block"}; + if (b.major_version >= hf::hf17_POS) + field(ar, "signatures", b.signatures); + } /************************************************************************/ /* */ @@ -502,9 +505,9 @@ namespace cryptonote using byte_and_output_fees = std::pair; //--------------------------------------------------------------- - constexpr txversion transaction_prefix::get_min_version_for_hf(uint8_t hf_version) + constexpr txversion transaction_prefix::get_min_version_for_hf(hf hf_version) { - if (hf_version >= cryptonote::network_version_7 && hf_version <= cryptonote::network_version_10_bulletproofs) + if (hf_version >= hf::hf7 && hf_version <= hf::hf10_bulletproofs) return txversion::v1; return txversion::v4_tx_types; } @@ -513,27 +516,27 @@ namespace cryptonote // tests can still use particular hard forks without needing to actually generate pre-v4 txes. namespace hack { inline bool test_suite_permissive_txes = false; } - inline txversion transaction_prefix::get_max_version_for_hf(uint8_t hf_version) + inline txversion transaction_prefix::get_max_version_for_hf(hf hf_version) { if (!hack::test_suite_permissive_txes) { - if (hf_version >= cryptonote::network_version_7 && hf_version <= cryptonote::network_version_8) + if (hf_version >= hf::hf7 && hf_version <= hf::hf8) return txversion::v2_ringct; - if (hf_version >= cryptonote::network_version_9_master_nodes && hf_version <= cryptonote::network_version_10_bulletproofs) + if (hf_version >= hf::hf9_master_nodes && hf_version <= hf::hf10_bulletproofs) return txversion::v3_per_output_unlock_times; } return txversion::v4_tx_types; } - constexpr txtype transaction_prefix::get_max_type_for_hf(uint8_t hf_version) + constexpr txtype transaction_prefix::get_max_type_for_hf(hf hf_version) { txtype result = txtype::standard; - if (hf_version >= network_version_18_bns) result = txtype::coin_burn; - else if (hf_version >= network_version_16) result = txtype::beldex_name_system; - else if (hf_version >= network_version_15_flash) result = txtype::stake; - else if (hf_version >= network_version_11_infinite_staking) result = txtype::key_image_unlock; - else if (hf_version >= network_version_9_master_nodes) result = txtype::state_change; + if (hf_version >= hf::hf18_bns) result = txtype::coin_burn; + else if (hf_version >= hf::hf16) result = txtype::beldex_name_system; + else if (hf_version >= hf::hf15_flash) result = txtype::stake; + else if (hf_version >= hf::hf11_infinite_staking) result = txtype::key_image_unlock; + else if (hf_version >= hf::hf9_master_nodes) result = txtype::state_change; return result; } @@ -564,12 +567,25 @@ namespace cryptonote } } - inline std::ostream &operator<<(std::ostream &os, txtype t) { + inline std::ostream& operator<<(std::ostream& os, txtype t) { return os << transaction::type_to_string(t); } - inline std::ostream &operator<<(std::ostream &os, txversion v) { + inline std::ostream& operator<<(std::ostream& os, txversion v) { return os << transaction::version_to_string(v); } + + inline std::ostream& operator<<(std::ostream& os, hf v) = delete;/*{ + return os << "HF" << static_cast(v); + }*/ + + // Serialization for the `hf` type; this is simply writing/reading the underlying uint8_t value + template + void serialize_value(Archive& ar, hf& x) { + auto val = static_cast>(x); + serialization::value(ar, val); + if constexpr (Archive::is_deserializer) + x = static_cast(val); + } } namespace std { diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index 48bd65f8d8e..828952d73e5 100755 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -65,40 +65,35 @@ namespace cryptonote { constexpr cryptonote::POS_random_value empty_random_value = {}; bool bitset = blk_header.POS.validator_bitset > 0; bool random_value = !(blk_header.POS.random_value == empty_random_value); - uint8_t hf_version = blk_header.major_version; - bool result = hf_version >= cryptonote::network_version_17_POS && (bitset || random_value); + auto hf_version = blk_header.major_version; + bool result = hf_version >= hf::hf17_POS && (bitset || random_value); return result; } //----------------------------------------------------------------------------------------------- bool block_has_POS_components(block const &blk) { bool signatures = blk.signatures.size(); - uint8_t hf_version = blk.major_version; + auto hf_version = blk.major_version; bool result = - (hf_version >= cryptonote::network_version_17_POS && signatures) || block_header_has_POS_components(blk); + (hf_version >= hf::hf17_POS && signatures) || block_header_has_POS_components(blk); return result; } //----------------------------------------------------------------------------------------------- - size_t get_min_block_weight(uint8_t version) + size_t get_min_block_weight(hf version) { - return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; - } - //----------------------------------------------------------------------------------------------- - size_t get_max_tx_size() - { - return CRYPTONOTE_MAX_TX_SIZE; + return BLOCK_GRANTED_FULL_REWARD_ZONE_V5; } //----------------------------------------------------------------------------------------------- // TODO(beldex): Move into beldex_economy, this will require access to beldex::exp2 - uint64_t block_reward_unpenalized_formula_v7(uint8_t version, uint64_t already_generated_coins, uint64_t height) + uint64_t block_reward_unpenalized_formula_v7(hf version, uint64_t already_generated_coins, uint64_t height) { - const int target = version < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; + const int target = version <= hf::hf1 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; const int target_minutes = target / 60; const int emission_speed_factor = EMISSION_SPEED_FACTOR_PER_MINUTE - (target_minutes-1); - uint64_t result = (MONEY_SUPPLY - already_generated_coins) >> emission_speed_factor; + uint64_t result = (beldex::MONEY_SUPPLY - already_generated_coins) >> emission_speed_factor; - if (version < network_version_7 ) + if (version < hf::hf7) { if (result < FINAL_SUBSIDY_PER_MINUTE*target_minutes) { @@ -119,26 +114,26 @@ namespace cryptonote { return result; } - bool get_base_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint64_t &reward_unpenalized, uint8_t version, uint64_t height) + bool get_base_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint64_t &reward_unpenalized, hf version, uint64_t height) { //premine reward if (height == 1) { - reward = 1400000000000000000; + reward = 1400000000 * beldex::COIN; return true; } - if((height>=56500) && (version=56500) && (version< hf::hf17_POS)) { - reward = COIN * 2; + reward = 2 * beldex::COIN; return true; } - static_assert(TARGET_BLOCK_TIME_OLD % 1 == 0s, "difficulty targets must be a multiple of 60"); + static_assert(old::TARGET_BLOCK_TIME_12 % 1 == 0s, "difficulty targets must be a multiple of 60"); static_assert(TARGET_BLOCK_TIME % 1 == 0s, "difficulty targets must be a multiple of 60"); uint64_t base_reward = - version >= network_version_17_POS ? BLOCK_REWARD_HF17_POS : - version >= network_version_16 ? BLOCK_REWARD_HF16 : + version >= hf::hf17_POS ? beldex::BLOCK_REWARD_HF17_POS : + version >= hf::hf16 ? beldex::BLOCK_REWARD_HF16 : block_reward_unpenalized_formula_v7(version, already_generated_coins, height); uint64_t full_reward_zone = get_min_block_weight(version); @@ -206,8 +201,8 @@ namespace cryptonote { , account_public_address const & adr ) { - uint64_t address_prefix = subaddress ? get_config(nettype).CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : get_config(nettype).CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; - + auto& conf = get_config(nettype); + uint64_t address_prefix = subaddress ? conf.PUBLIC_SUBADDRESS_BASE58_PREFIX : conf.PUBLIC_ADDRESS_BASE58_PREFIX; return tools::base58::encode_addr(address_prefix, t_serializable_object_to_blob(adr)); } //----------------------------------------------------------------------- @@ -217,7 +212,7 @@ namespace cryptonote { , crypto::hash8 const & payment_id ) { - uint64_t integrated_address_prefix = get_config(nettype).CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t integrated_address_prefix = get_config(nettype).PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; integrated_address iadr = { adr, payment_id @@ -236,9 +231,10 @@ namespace cryptonote { , const std::string_view str ) { - uint64_t address_prefix = get_config(nettype).CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; - uint64_t integrated_address_prefix = get_config(nettype).CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; - uint64_t subaddress_prefix = get_config(nettype).CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX; + auto& conf = get_config(nettype); + uint64_t address_prefix = conf.PUBLIC_ADDRESS_BASE58_PREFIX; + uint64_t integrated_address_prefix = conf.PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t subaddress_prefix = conf.PUBLIC_SUBADDRESS_BASE58_PREFIX; blobdata data; uint64_t prefix{0}; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 400f8de054b..7f811789aa4 100755 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -107,11 +107,10 @@ namespace cryptonote { /************************************************************************/ bool block_header_has_POS_components(block_header const &blk_header); bool block_has_POS_components(block const &blk); - size_t get_min_block_weight(uint8_t version); - size_t get_max_tx_size(); + size_t get_min_block_weight(hf version); uint64_t block_reward_unpenalized_formula_v7(uint64_t already_generated_coins, uint64_t height); uint64_t block_reward_unpenalized_formula_v8(uint64_t height); - bool get_base_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint64_t &reward_unpenalized, uint8_t version, uint64_t height); + bool get_base_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint64_t &reward_unpenalized, hf version, uint64_t height); uint8_t get_account_address_checksum(const public_address_outer_blob& bl); uint8_t get_account_integrated_address_checksum(const public_integrated_address_outer_blob& bl); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index a663fd92784..529e1fd1a27 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -91,7 +91,7 @@ namespace cryptonote ++nlr; nlr += 6; const size_t bp_size = 32 * (9 + 2 * nlr); - CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction"); + CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= TX_BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(TX_BULLETPROOF_MAX_OUTPUTS) + " per transaction"); CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs " + std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size)); const uint64_t bp_clawback = (bp_base * n_padded_outputs - bp_size) * 4 / 5; @@ -392,11 +392,11 @@ namespace cryptonote // // TODO: get rid of the user-configurable default_decimal_point nonsense and just multiply // this value by the `COIN` constant. - for (size_t i = 0; i < CRYPTONOTE_DISPLAY_DECIMAL_POINT; i++) + for (size_t i = 0; i < beldex::DISPLAY_DECIMAL_POINT; i++) { if (amount > std::numeric_limits::max() / 10) return false; // would overflow - amount *= 10; + amount *= 10; // have to change } } @@ -407,10 +407,10 @@ namespace cryptonote return false; // fractional part contains non-digit // If too long, but with insignificant 0's, trim them off - while (parts[1].size() > CRYPTONOTE_DISPLAY_DECIMAL_POINT && parts[1].back() == '0') + while (parts[1].size() > beldex::DISPLAY_DECIMAL_POINT && parts[1].back() == '0') parts[1].remove_suffix(1); - if (parts[1].size() > CRYPTONOTE_DISPLAY_DECIMAL_POINT) + if (parts[1].size() > beldex::DISPLAY_DECIMAL_POINT) return false; // fractional part has too many significant digits uint64_t fractional; @@ -419,7 +419,7 @@ namespace cryptonote // Scale up the value if it wasn't a full fractional value, e.g. if we have "10.45" then we // need to convert the 45 we just parsed to 450'000'000. - for (size_t i = parts[1].size(); i < CRYPTONOTE_DISPLAY_DECIMAL_POINT; i++) + for (size_t i = parts[1].size(); i < beldex::DISPLAY_DECIMAL_POINT; i++) fractional *= 10; if (fractional > std::numeric_limits::max() - amount) @@ -637,10 +637,10 @@ namespace cryptonote return true; } - bool add_master_node_state_change_to_tx_extra(std::vector& tx_extra, const tx_extra_master_node_state_change& state_change, const uint8_t hf_version) + bool add_master_node_state_change_to_tx_extra(std::vector& tx_extra, const tx_extra_master_node_state_change& state_change, const hf hf_version) { tx_extra_field field; - if (hf_version < network_version_13_checkpointing) + if (hf_version < hf::hf13_checkpointing) { CHECK_AND_ASSERT_MES(state_change.state == master_nodes::new_state::deregister, false, "internal error: cannot construct an old deregistration for a non-deregistration state change (before hardfork v12)"); @@ -809,9 +809,9 @@ namespace cryptonote add_tx_extra(tx_extra, winner); } //--------------------------------------------------------------- - bool get_master_node_state_change_from_tx_extra(const std::vector& tx_extra, tx_extra_master_node_state_change &state_change, const uint8_t hf_version) + bool get_master_node_state_change_from_tx_extra(const std::vector& tx_extra, tx_extra_master_node_state_change &state_change, const hf hf_version) { - if (hf_version >= cryptonote::network_version_13_checkpointing) { + if (hf_version >= hf::hf13_checkpointing) { // Look for a new-style state change field: return get_field_from_tx_extra(tx_extra, state_change); } @@ -1103,7 +1103,7 @@ namespace cryptonote std::string get_unit(unsigned int decimal_point) { if (decimal_point == (unsigned int)-1) - decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT; + decimal_point = beldex::DISPLAY_DECIMAL_POINT; switch (decimal_point) { case 9: @@ -1122,7 +1122,7 @@ namespace cryptonote std::string print_money(uint64_t amount, unsigned int decimal_point) { if (decimal_point == (unsigned int)-1) - decimal_point = CRYPTONOTE_DISPLAY_DECIMAL_POINT; + decimal_point = beldex::DISPLAY_DECIMAL_POINT; std::string s = std::to_string(amount); if(s.size() < decimal_point+1) { @@ -1386,12 +1386,12 @@ namespace cryptonote LOG_ERROR("get_registration_hash addresses.size() != portions.size()"); return false; } - uint64_t portions_left = STAKING_PORTIONS; + uint64_t portions_left = old::STAKING_PORTIONS; for (uint64_t portion : portions) { if (portion > portions_left) { - LOG_ERROR(tr("Your registration has more than ") << STAKING_PORTIONS << tr(" portions, this registration is invalid!")); + LOG_ERROR(tr("Your registration has more than ") << old::STAKING_PORTIONS << tr(" portions, this registration is invalid!")); return false; } portions_left -= portion; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index aa9f2756e50..ee37b2298e1 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -129,8 +129,8 @@ namespace cryptonote crypto::public_key get_tx_pub_key_from_extra(const std::vector& tx_extra, size_t pk_index = 0); crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0); - bool add_master_node_state_change_to_tx_extra(std::vector& tx_extra, const tx_extra_master_node_state_change& state_change, uint8_t hf_version); - bool get_master_node_state_change_from_tx_extra(const std::vector& tx_extra, tx_extra_master_node_state_change& state_change, uint8_t hf_version); + bool add_master_node_state_change_to_tx_extra(std::vector& tx_extra, const tx_extra_master_node_state_change& state_change, hf hf_version); + bool get_master_node_state_change_from_tx_extra(const std::vector& tx_extra, tx_extra_master_node_state_change& state_change, hf hf_version); bool get_master_node_pubkey_from_tx_extra(const std::vector& tx_extra, crypto::public_key& pubkey); bool get_master_node_contributor_from_tx_extra(const std::vector& tx_extra, cryptonote::account_public_address& address); diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 6745c8e36b6..0eeb3986a2d 100755 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -128,13 +128,13 @@ namespace cryptonote { timestamps.push_back(timestamp); difficulties.push_back(cumulative_difficulty); - bool before_hf16 = !is_hard_fork_at_least(nettype, network_version_17_POS, chain_height); + bool before_hf16 = !is_hard_fork_at_least(nettype, hf::hf17_POS, chain_height); // Trim down arrays - while (timestamps.size() > DIFFICULTY_BLOCKS_COUNT(before_hf16)) + while (timestamps.size() > old::DIFFICULTY_BLOCKS_COUNT(before_hf16)) timestamps.erase(timestamps.begin()); - while (difficulties.size() > DIFFICULTY_BLOCKS_COUNT(before_hf16)) + while (difficulties.size() > old::DIFFICULTY_BLOCKS_COUNT(before_hf16)) difficulties.erase(difficulties.begin()); } @@ -167,15 +167,15 @@ namespace cryptonote { { auto result = difficulty_calc_mode::normal; - if (!is_hard_fork_at_least(nettype, cryptonote::network_version_10_bulletproofs, height)) + if (!is_hard_fork_at_least(nettype, hf::hf10_bulletproofs, height)) result = difficulty_calc_mode::use_old_lwma; // HF12 switches to RandomX with a likely drastically reduced hashrate versus Turtle, so override // difficulty for the first difficulty window blocks: - else if (auto randomx_start_height = get_hard_fork_heights(nettype, network_version_13_checkpointing).first; - randomx_start_height && height >= *randomx_start_height && height <= *randomx_start_height + DIFFICULTY_WINDOW) + else if (auto randomx_start_height = get_hard_fork_heights(nettype, hf::hf13_checkpointing).first; + randomx_start_height && height >= *randomx_start_height && height <= *randomx_start_height + old::DIFFICULTY_WINDOW) result = difficulty_calc_mode::hf12_override; - else if (auto POS_start_height = get_hard_fork_heights(nettype, network_version_17_POS).first; - nettype == MAINNET && POS_start_height && height >= *POS_start_height && height <= *POS_start_height + DIFFICULTY_WINDOW) + else if (auto POS_start_height = get_hard_fork_heights(nettype, hf::hf17_POS).first; + nettype == MAINNET && POS_start_height && height >= *POS_start_height && height <= *POS_start_height + old::DIFFICULTY_WINDOW) result = difficulty_calc_mode::hf16_override; return result; @@ -187,7 +187,7 @@ namespace cryptonote { difficulty_calc_mode mode) { const int64_t T = static_cast(target_seconds); - size_t N = DIFFICULTY_WINDOW; + size_t N = old::DIFFICULTY_WINDOW; // Return a difficulty of 1 for first 4 blocks if it's the start of the chain. if (timestamps.size() < 4) { diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 3818139a1a8..97bfeea0faf 100755 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -36,39 +36,39 @@ namespace cryptonote { // version 7 from the start of the blockchain, inhereted from Monero mainnet static constexpr std::array mainnet_hard_forks = { - hard_fork{1, 0, 1, 1548750273 }, // Beldex 0.1: Beldex is born - hard_fork{7, 0, 10, 1548750283 }, - hard_fork{8, 0, 40000, 1559474448 }, - hard_fork{11, 0, 56240, 1577836800 }, - hard_fork{12, 0, 126874, 1578704502 }, - hard_fork{15, 0, 742420, 1636320320 }, //Friday, December 10, 2021 6:00:00 PM (GMT) - hard_fork{17, 0, 742421, 1636320540 }, - hard_fork{18, 0, 2986890, 1706506200 }, // Monday, January 29, 2024 5:30:00 AM (UTC) - hard_fork{19, 0, 3546545, 1725514200 }, // Thursday, September 5, 2024 5:30:00 AM (UTC) + hard_fork{hf::hf1, 0, 1, 1548750273 }, // Beldex 1.0: Beldex is born + hard_fork{hf::hf7, 0, 10, 1548750283 }, + hard_fork{hf::hf8, 0, 40000, 1559474448 }, + hard_fork{hf::hf11_infinite_staking, 0, 56240, 1577836800 }, + hard_fork{hf::hf12_security_signature,0, 126874, 1578704502 }, + hard_fork{hf::hf15_flash, 0, 742420, 1636320320 }, //Friday, December 10, 2021 6:00:00 PM (GMT) + hard_fork{hf::hf17_POS, 0, 742421, 1636320540 }, + hard_fork{hf::hf18_bns, 0, 2986890, 1706506200 }, // Monday, January 29, 2024 5:30:00 AM (UTC) + hard_fork{hf::hf19_enhance_bns, 0, 3546545, 1725514200 }, // Thursday, September 5, 2024 5:30:00 AM (UTC) }; static constexpr std::array testnet_hard_forks = { - hard_fork{1, 0, 1, 1548474440 }, - hard_fork{7, 0, 10, 1559474448 }, - hard_fork{8, 0, 40000, 1559474448 }, - hard_fork{11, 0, 54288, 1628224369 }, - hard_fork{12, 0, 104832, 1629012232 }, // Sunday, August 15, 2021 7:23:52 AM - hard_fork{15, 0, 169950, 1636391396 }, // Monday, November 8, 2021 5:09:56 PM - hard_fork{17, 0, 169960, 1636391696 }, // Monday, November 8, 2021 5:14:56 PM - hard_fork{18, 0, 1251330, 1701063000 }, // Monday, November 27, 2023 5:30:00 AM - hard_fork{19, 0, 1997558, 1723447800 }, // Monday, Aug 12, 2024 7:30:00 AM + hard_fork{hf::hf1, 0, 1, 1548474440 }, + hard_fork{hf::hf7, 0, 10, 1559474448 }, + hard_fork{hf::hf8, 0, 40000, 1559474448 }, + hard_fork{hf::hf11_infinite_staking, 0, 54288, 1628224369 }, + hard_fork{hf::hf12_security_signature,0, 104832, 1629012232 }, // Sunday, August 15, 2021 7:23:52 AM + hard_fork{hf::hf15_flash, 0, 169950, 1636391396 }, // Monday, November 8, 2021 5:09:56 PM + hard_fork{hf::hf17_POS, 0, 169960, 1636391696 }, // Monday, November 8, 2021 5:14:56 PM + hard_fork{hf::hf18_bns, 0, 1251330, 1701063000 }, // Monday, November 27, 2023 5:30:00 AM + hard_fork{hf::hf19_enhance_bns, 0, 1997558, 1723447800 }, // Monday, Aug 12, 2024 7:30:00 AM }; static constexpr std::array devnet_hard_forks = { - hard_fork{ 7, 0, 0, 1599848400 }, - hard_fork{ 17, 0, 2, 1599848400 }, + hard_fork{hf::hf7, 0, 0, 1599848400 }, + hard_fork{hf::hf17_POS, 0, 2, 1599848400 }, }; template static constexpr bool is_ordered(const std::array& forks) { - if (N == 0 || forks[0].version < 1) + if (N == 0 || forks[0].version < hf::hf1) return false; for (size_t i = 1; i < N; i++) { auto& hf = forks[i]; @@ -102,7 +102,7 @@ std::pair get_hard_forks(network_type type) std::pair, std::optional> -get_hard_fork_heights(network_type nettype, uint8_t version) { +get_hard_fork_heights(network_type nettype, hf version) { std::pair, std::optional> found; for (auto [it, end] = get_hard_forks(nettype); it != end; it++) { if (it->version > version) { // This (and anything else) are in the future @@ -116,7 +116,7 @@ get_hard_fork_heights(network_type nettype, uint8_t version) { return found; } -uint8_t hard_fork_ceil(network_type nettype, uint8_t version) { +hf hard_fork_ceil(network_type nettype, hf version) { auto [it, end] = get_hard_forks(nettype); for (; it != end; it++) if (it->version >= version) @@ -125,9 +125,9 @@ uint8_t hard_fork_ceil(network_type nettype, uint8_t version) { return version; } -std::pair +std::pair get_network_version_revision(network_type nettype, uint64_t height) { - std::pair result; + std::pair result; for (auto [it, end] = get_hard_forks(nettype); it != end; it++) { if (it->height <= height) result = {it->version, it->mnode_revision}; @@ -137,18 +137,22 @@ get_network_version_revision(network_type nettype, uint64_t height) { return result; } -bool is_hard_fork_at_least(network_type type, uint8_t version, uint64_t height) { +bool is_hard_fork_at_least(network_type type, hf version, uint64_t height) { return get_network_version(type, height) >= version; } -std::pair +std::pair get_ideal_block_version(network_type nettype, uint64_t height) { - std::pair result; + std::pair result; for (auto [it, end] = get_hard_forks(nettype); it != end; it++) { - if (it->height <= height) + if (it->height <= height){ result.first = it->version; - result.second = it->version; + result.second = it->mnode_revision; + } + if (result.first < hf::hf20_bulletproof_plusplus) + result.second = static_cast(it->version); + } return result; } diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h index 568a8ee55f2..4dd2571ed70 100755 --- a/src/cryptonote_basic/hardfork.h +++ b/src/cryptonote_basic/hardfork.h @@ -36,7 +36,7 @@ namespace cryptonote // Defines where hard fork (i.e. new minimum network versions) begin struct hard_fork { - uint8_t version; // Blockchain major version + hf version; // Blockchain major version uint8_t mnode_revision; // Mnode revision for enforcing non-blockchain-breaking mandatory mester node updates uint64_t height; time_t time; @@ -54,7 +54,7 @@ namespace cryptonote // outdated), and returns nullopt for B if the version indicates that top network version we know // about (i.e. there is no subsequent hardfork scheduled). std::pair, std::optional> - get_hard_fork_heights(network_type type, uint8_t version); + get_hard_fork_heights(network_type type, hf version); // Returns the lowest network version >= the given version, that is, it rounds up missing hf table // entries to the next largest entry. Typically this returns the network version itself, but if @@ -69,18 +69,18 @@ namespace cryptonote // ... // hard_fork_ceil(14) == 14 // hard_fork_ceil(15) == 15 - uint8_t hard_fork_ceil(network_type type, uint8_t version); + hf hard_fork_ceil(network_type type, hf version); // Returns true if the given height is sufficiently high to be at or after the given hard fork // version. - bool is_hard_fork_at_least(network_type type, uint8_t version, uint64_t height); + bool is_hard_fork_at_least(network_type type, hf version, uint64_t height); // Returns the active network version and mnode revision for the given height. - std::pair + std::pair get_network_version_revision(network_type nettype, uint64_t height); // Returns the network (i.e. block) version for the given height. - inline uint8_t get_network_version(network_type nettype, uint64_t height) { + inline hf get_network_version(network_type nettype, uint64_t height) { return get_network_version_revision(nettype, height).first; } @@ -88,14 +88,14 @@ namespace cryptonote // a shortcut for `get_hard_fork_heights(type, hard_fork_ceil(type, version)).first`, i.e. it // returns the first height at which `version` rules become active (even if they became active at // a hard fork > the given value). - inline std::optional hard_fork_begins(network_type type, uint8_t version) { + inline std::optional hard_fork_begins(network_type type, hf version) { return get_hard_fork_heights(type, hard_fork_ceil(type, version)).first; } // Returns the "ideal" network version that we want to use on blocks we create, which is to use // the required major version for major version and the maximum major version we know about as // minor version. If this seems a bit silly, it is, and will be changed in the future. - std::pair get_ideal_block_version(network_type nettype, uint64_t height); + std::pair get_ideal_block_version(network_type nettype, uint64_t height); } // namespace cryptonote diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 6ac596fdaff..ea7b356c064 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -30,6 +30,8 @@ #pragma once +#include +#include #include #include #include @@ -40,187 +42,267 @@ #include using namespace std::literals; -#define EMISSION_SPEED_FACTOR_PER_MINUTE (28) - -#define CRYPTONOTE_MAX_BLOCK_NUMBER 500000000 -#define CRYPTONOTE_MAX_TX_SIZE 1000000 -#define CRYPTONOTE_MAX_TX_PER_BLOCK 0x10000000 -#define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 -#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 -#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2 60*10 -#define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 10 -#define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17 2 -#define CRYPTONOTE_DEFAULT_TX_MIXIN 9 -#define FINAL_SUBSIDY_PER_MINUTE ((uint64_t)500000000) // 3 * pow(10, 7) - -#define STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS 20 -#define STAKING_PORTIONS UINT64_C(0xfffffffffffffffc) -#define MAX_NUMBER_OF_CONTRIBUTORS 4 -#define MIN_PORTIONS (STAKING_PORTIONS / MAX_NUMBER_OF_CONTRIBUTORS) - -static_assert(STAKING_PORTIONS % 12 == 0, "Use a multiple of twelve, so that it divides evenly by two, three, or four contributors."); - -#define STAKING_AUTHORIZATION_EXPIRATION_WINDOW (60*60*24*7*2) // 2 weeks - -#define BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW 11 - -#define CRYPTONOTE_REWARD_BLOCKS_WINDOW 100 -#define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 20000 // NOTE(BDX): For testing suite, //size of block (bytes) after which reward for block calculated using block size - before first fork -#define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 300000 //size of block (bytes) after which reward for block calculated using block size - second change, from v5 -#define CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE 100000 // size in blocks of the long term block weight median window -#define CRYPTONOTE_SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR 50 -#define CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE 600 -#define CRYPTONOTE_DISPLAY_DECIMAL_POINT 9 - -#define FEE_PER_KB ((uint64_t)2000000000) // 2 BDX (= 2 * pow(10, 9)) -#define FEE_PER_BYTE ((uint64_t)215) // Fallback used in wallet if no fee is available from RPC -#define FEE_PER_BYTE_V12 ((uint64_t)17200) // Higher fee (and fallback) in v12 (only, v13 switches back) -#define FEE_PER_OUTPUT ((uint64_t)20000000) // 0.02 BDX per tx output (in addition to the per-byte fee), starting in v13 -#define FEE_PER_OUTPUT_V17 ((uint64_t)100000) // 0.0001 BDX per tx output -#define DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD ((uint64_t)10000000000) // 10 * pow(10,12) -#define DYNAMIC_FEE_PER_KB_BASE_FEE_V5 ((uint64_t)400000000) -#define DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT ((uint64_t)300000) -#define DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT_V17 ((uint64_t)30000) // Only v17 - -#define DIFFICULTY_TARGET_V2 120 // seconds -#define DIFFICULTY_TARGET_V1 60 // seconds - before first fork -constexpr auto TARGET_BLOCK_TIME_OLD = 2min; -constexpr uint64_t BLOCKS_PER_HOUR_OLD = 1h / TARGET_BLOCK_TIME_OLD; -constexpr uint64_t BLOCKS_PER_DAY_OLD = 24h / TARGET_BLOCK_TIME_OLD; - -constexpr auto TARGET_BLOCK_TIME = 30s; -constexpr uint64_t BLOCKS_PER_HOUR = 1h / TARGET_BLOCK_TIME; -constexpr uint64_t BLOCKS_PER_DAY = 24h / TARGET_BLOCK_TIME; - -constexpr uint64_t DIFFICULTY_WINDOW = 59; -constexpr uint64_t DIFFICULTY_BLOCKS_COUNT(bool before_hf16) -{ - // NOTE: We used to have a different setup here where, - // DIFFICULTY_WINDOW = 60 - // DIFFICULTY_BLOCKS_COUNT = 61 - // next_difficulty_v2's N = DIFFICULTY_WINDOW - 1 - // - // And we resized timestamps/difficulties to (N+1) (chopping off the latest timestamp). - // - // Now we re-adjust DIFFICULTY_WINDOW to 59. To preserve the old behaviour we - // add +2. After HF16 we avoid trimming the top block and just add +1. - // - // Ideally, we just set DIFFICULTY_BLOCKS_COUNT to DIFFICULTY_WINDOW - // + 1 for before and after HF16 (having one unified constant) but this - // requires some more investigation to get it working with pre HF16 blocks and - // alt chain code without bugs. - uint64_t result = (before_hf16) ? DIFFICULTY_WINDOW + 2 : DIFFICULTY_WINDOW + 1; - return result; +namespace cryptonote { + +/// Cryptonote protocol related constants: + +inline constexpr uint64_t EMISSION_SPEED_FACTOR_PER_MINUTE = 28; + +inline constexpr uint64_t MAX_BLOCK_NUMBER = 500000000; +inline constexpr size_t MAX_TX_SIZE = 1000000; +inline constexpr uint64_t MAX_TX_PER_BLOCK = 0x10000000; +inline constexpr uint64_t MINED_MONEY_UNLOCK_WINDOW = 60; +inline constexpr uint64_t DEFAULT_TX_SPENDABLE_AGE_V17 = 2; +inline constexpr uint64_t TX_OUTPUT_DECOYS = 9; +inline constexpr size_t TX_BULLETPROOF_MAX_OUTPUTS = 16; +inline constexpr uint64_t PUBLIC_ADDRESS_TEXTBLOB_VER = 0; + +inline constexpr uint64_t FINAL_SUBSIDY_PER_MINUTE = 500000000; // 3 * pow(10, 7) + +inline constexpr uint64_t BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW = 11; + + // #define MAX_NUMBER_OF_CONTRIBUTORS 4 + // #define MIN_PORTIONS (STAKING_PORTIONS / MAX_NUMBER_OF_CONTRIBUTORS) + + // static_assert(STAKING_PORTIONS % 12 == 0, "Use a multiple of twelve, so that it divides evenly by two, three, or four contributors."); + + +inline constexpr uint64_t REWARD_BLOCKS_WINDOW = 100; +inline constexpr uint64_t BLOCK_GRANTED_FULL_REWARD_ZONE_V1 = 20000; // NOTE(oxen): For testing suite, //size of block (bytes) after which reward for block calculated using block size - before first fork +inline constexpr uint64_t BLOCK_GRANTED_FULL_REWARD_ZONE_V5 = 300000; //size of block (bytes) after which reward for block calculated using block size - second change, from v5 +inline constexpr uint64_t LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE = 100000; // size in blocks of the long term block weight median window +inline constexpr uint64_t SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR = 50; +inline constexpr uint64_t COINBASE_BLOB_RESERVED_SIZE = 600; + +inline constexpr uint64_t LOCKED_TX_ALLOWED_DELTA_BLOCKS = 1; + + // #define CRYPTONOTE_DISPLAY_DECIMAL_POINT 9 + #define DIFFICULTY_TARGET_V2 120 // seconds + #define DIFFICULTY_TARGET_V1 60 // seconds - before first fork + +inline constexpr auto TARGET_BLOCK_TIME = 30s; +inline constexpr uint64_t BLOCKS_PER_HOUR = 1h / TARGET_BLOCK_TIME; +inline constexpr uint64_t BLOCKS_PER_DAY = 24h / TARGET_BLOCK_TIME; + +inline constexpr auto MEMPOOL_TX_LIVETIME = 3 * 24h; +inline constexpr auto MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME = 7 * 24h; +inline constexpr auto MEMPOOL_PRUNE_NON_STANDARD_TX_LIFETIME = 2h; +inline constexpr size_t DEFAULT_MEMPOOL_MAX_WEIGHT = 72h / TARGET_BLOCK_TIME * 300'000; // 3 days worth of full 300kB blocks + +inline constexpr uint64_t FEE_PER_BYTE = 215; // Fallback used in wallet if no fee is available from RPC +inline constexpr uint64_t FEE_PER_OUTPUT_V17 = 100000; // 0.0001 BDX per tx output +inline constexpr uint64_t DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT = 300000; +inline constexpr uint64_t FEE_QUANTIZATION_DECIMALS = 8; + +inline constexpr size_t BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT = 10000; // by default, blocks ids count in synchronizing +inline constexpr size_t BLOCKS_SYNCHRONIZING_DEFAULT_COUNT = 100; // by default, blocks count in blocks downloading +inline constexpr size_t BLOCKS_SYNCHRONIZING_MAX_COUNT = 2048; //must be a power of 2, greater than 128, equal to SEEDHASH_EPOCH_BLOCKS in rx-slow-hash.c + +inline constexpr size_t HASH_OF_HASHES_STEP = 256; + +// Hash domain separators +namespace hashkey { + inline constexpr std::string_view BULLETPROOF_EXPONENT = "bulletproof"sv; + inline constexpr std::string_view RINGDB = "ringdsb\0"sv; + inline constexpr std::string_view SUBADDRESS = "SubAddr\0"sv; + inline constexpr unsigned char ENCRYPTED_PAYMENT_ID = 0x8d; + inline constexpr unsigned char WALLET = 0x8c; + inline constexpr unsigned char WALLET_CACHE = 0x8d; + inline constexpr unsigned char RPC_PAYMENT_NONCE = 0x58; + inline constexpr unsigned char MEMORY = 'k'; + inline constexpr std::string_view MULTISIG = "Multisig\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"sv; + inline constexpr std::string_view CLSAG_ROUND = "CLSAG_round"sv; + inline constexpr std::string_view CLSAG_AGG_0 = "CLSAG_agg_0"sv; + inline constexpr std::string_view CLSAG_AGG_1 = "CLSAG_agg_1"sv; } +// Maximum allowed stake contribution, as a fraction of the available contribution room. This +// should generally be slightly larger than 1. This is used to disallow large overcontributions +// which can happen when there are competing stakes submitted at the same time for the same +// master node. +using MAXIMUM_ACCEPTABLE_STAKE = std::ratio<101, 100>; + // #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 + // #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 old::TARGET_BLOCK_TIME_12 * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS + // #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V3 TARGET_BLOCK_TIME * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 TARGET_BLOCK_TIME_OLD * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V3 TARGET_BLOCK_TIME * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 - +// see src/cryptonote_protocol/levin_notify.cpp +inline constexpr auto NOISE_MIN_EPOCH = 5min; +inline constexpr auto NOISE_EPOCH_RANGE = 30s; +inline constexpr auto NOISE_MIN_DELAY = 10s; +inline constexpr auto NOISE_DELAY_RANGE = 5s; +inline constexpr uint64_t NOISE_BYTES = 3 * 1024; // 3 kiB +inline constexpr size_t NOISE_CHANNELS = 2; +inline constexpr size_t MAX_FRAGMENTS = 20; // ~20 * NOISE_BYTES max payload size for covert/noise send + + +// p2p-specific constants: +namespace p2p { + + inline constexpr size_t LOCAL_WHITE_PEERLIST_LIMIT = 1000; + inline constexpr size_t LOCAL_GRAY_PEERLIST_LIMIT = 5000; + + inline constexpr int64_t DEFAULT_CONNECTIONS_COUNT_OUT = 8; + inline constexpr int64_t DEFAULT_CONNECTIONS_COUNT_IN = 32; + inline constexpr auto DEFAULT_HANDSHAKE_INTERVAL = 60s; + inline constexpr uint32_t DEFAULT_PACKET_MAX_SIZE = 50000000; + inline constexpr uint32_t DEFAULT_PEERS_IN_HANDSHAKE = 250; + inline constexpr auto DEFAULT_CONNECTION_TIMEOUT = 5s; + inline constexpr auto DEFAULT_SOCKS_CONNECT_TIMEOUT = 45s; + inline constexpr auto DEFAULT_PING_CONNECTION_TIMEOUT = 2s; + inline constexpr auto DEFAULT_INVOKE_TIMEOUT = 2min; + inline constexpr auto DEFAULT_HANDSHAKE_INVOKE_TIMEOUT = 5s; + inline constexpr int DEFAULT_WHITELIST_CONNECTIONS_PERCENT = 70; + inline constexpr size_t DEFAULT_ANCHOR_CONNECTIONS_COUNT = 2; + inline constexpr size_t DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT = 2; + inline constexpr int64_t DEFAULT_LIMIT_RATE_UP = 2048; // kB/s + inline constexpr int64_t DEFAULT_LIMIT_RATE_DOWN = 8192; // kB/s + inline constexpr auto FAILED_ADDR_FORGET = 1h; + inline constexpr auto IP_BLOCK_TIME = 24h; + inline constexpr size_t IP_FAILS_BEFORE_BLOCK = 10; + inline constexpr auto IDLE_CONNECTION_KILL_INTERVAL = 5min; + inline constexpr uint32_t SUPPORT_FLAG_FLUFFY_BLOCKS = 0x01; + inline constexpr uint32_t SUPPORT_FLAGS = SUPPORT_FLAG_FLUFFY_BLOCKS; + +} // namespace p2p + +// filename constants: +inline constexpr auto DATA_DIRNAME = +#ifdef _WIN32 + "beldex"sv; // Buried in some windows filesystem maze location +#else + ".beldex"sv; // ~/.beldex +#endif + +inline constexpr auto CONF_FILENAME = "beldex.conf"sv; +inline constexpr auto SOCKET_FILENAME = "beldexd.sock"sv; +inline constexpr auto LOG_FILENAME = "beldex.log"sv; +inline constexpr auto POOLDATA_FILENAME = "poolstate.bin"sv; +inline constexpr auto BLOCKCHAINDATA_FILENAME = "data.mdb"sv; +inline constexpr auto BLOCKCHAINDATA_LOCK_FILENAME = "lock.mdb"sv; +inline constexpr auto P2P_NET_DATA_FILENAME = "p2pstate.bin"sv; +inline constexpr auto MINER_CONFIG_FILE_NAME = "miner_conf.json"sv; + +inline constexpr uint64_t PRUNING_STRIPE_SIZE = 4096; // the smaller, the smoother the increase +inline constexpr uint64_t PRUNING_LOG_STRIPES = 3; // the higher, the more space saved +inline constexpr uint64_t PRUNING_TIP_BLOCKS = 5500; // the smaller, the more space saved +inline constexpr bool PRUNING_DEBUG_SPOOF_SEED = false; // For debugging only + +// Constants for hardfork versions: +enum class hf : uint8_t +{ hf1 = 1, + hf7 = 7, + hf8, + hf9_master_nodes, // Proof Of Stake w/ Master Nodes + hf10_bulletproofs, // Bulletproofs, Master Node Grace Registration Period, + hf11_infinite_staking, // Infinite Staking, CN-Turtle + hf12_security_signature, + hf13_checkpointing, // Checkpointing, RandomXL + hf14_enforce_checkpoints, + hf15_flash, // Beldex Storage Server, Belnet + hf16, + hf17_POS, // Proof Of Stake, Batched Governance + hf18_bns, + hf19_enhance_bns, // provided EVM address in BNS + hf20_bulletproof_plusplus, + + _next, + none = 0 + + // `hf` serialization is in cryptonote_basic/cryptonote_basic.h +}; + +constexpr auto hf_max = static_cast(static_cast(hf::_next) - 1); +constexpr auto hf_prev(hf x) { + if (x <= hf::hf7 || x > hf_max) return hf::none; + return static_cast(static_cast(x) - 1); +} -#define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 //by default, blocks ids count in synchronizing -#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 100 //by default, blocks count in blocks downloading -#define BLOCKS_SYNCHRONIZING_MAX_COUNT 2048 //must be a power of 2, greater than 128, equal to SEEDHASH_EPOCH_BLOCKS +// Constants for which hardfork activates various features: +namespace feature { + constexpr auto PER_BYTE_FEE = hf::hf10_bulletproofs; + constexpr auto SMALLER_BP = hf::hf11_infinite_staking; + constexpr auto LONG_TERM_BLOCK_WEIGHT = hf::hf11_infinite_staking; + constexpr auto PER_OUTPUT_FEE = hf::hf14_enforce_checkpoints; + constexpr auto ED25519_KEY = hf::hf14_enforce_checkpoints; + constexpr auto FEE_BURNING = hf::hf15_flash; + constexpr auto FLASH = hf::hf15_flash; + constexpr auto REDUCE_FEE = hf::hf17_POS; + constexpr auto MIN_2_OUTPUTS = hf::hf17_POS; + constexpr auto REJECT_SIGS_IN_COINBASE = hf::hf17_POS; + constexpr auto ENFORCE_MIN_AGE = hf::hf17_POS; + constexpr auto EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY = hf::hf17_POS; + constexpr auto POS = hf::hf17_POS; + constexpr auto CLSAG = hf::hf15_flash; + constexpr auto PROOF_BTENC = hf::hf18_bns; +} -#define CRYPTONOTE_MEMPOOL_TX_LIVETIME (86400*3) //seconds, three days -#define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME (86400*7) //seconds, one week +enum network_type : uint8_t +{ + MAINNET = 0, + TESTNET, + DEVNET, + FAKECHAIN, + UNDEFINED = 255 +}; + +// Constants for older hard-forks that are mostly irrelevant now, but are still needed to sync the +// older parts of the blockchain: +namespace old { + + // block time future time limit used in the mining difficulty algorithm: + inline constexpr uint64_t BLOCK_FUTURE_TIME_LIMIT_V2 = 60*10; + // Re-registration grace period (not used since HF11 infinite staking): + inline constexpr uint64_t STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS = 20; + // Before HF19, staking portions and fees (in SN registrations) are encoded as a numerator value + // with this implied denominator: + inline constexpr uint64_t STAKING_PORTIONS = UINT64_C(0xfffffffffffffffc); + // Before HF19 signed registrations were only valid for two weeks: + // TODO: After HF19 we eliminate the window-checking code entirely (as long as no expired + // registration has ever been sent to the blockchain then it should still sync fine). + inline constexpr std::chrono::seconds STAKING_AUTHORIZATION_EXPIRATION_WINDOW = 14 * 24h; + + inline constexpr uint64_t DEFAULT_TX_SPENDABLE_AGE = 10; + + inline constexpr uint64_t FEE_PER_BYTE_V12 = 17200; // Higher fee (and fallback) in v12 (only, v13 switches back) + inline constexpr uint64_t FEE_PER_OUTPUT = 20000000; // 0.02 BDX per tx output (in addition to the per-byte fee), starting in v13 + inline constexpr uint64_t DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT_V17 = 30000; // Only v17 (v18 switches back) + + // Dynamic fee calculations used before HF10: + inline constexpr uint64_t DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD = UINT64_C(10000000000); // 10 * pow(10,9) + inline constexpr uint64_t DYNAMIC_FEE_PER_KB_BASE_FEE_V5 = 400000000; + + inline constexpr uint64_t DIFFICULTY_WINDOW = 59; + inline constexpr uint64_t DIFFICULTY_BLOCKS_COUNT(bool before_hf16) + { + // NOTE: We used to have a different setup here where, + // DIFFICULTY_WINDOW = 60 + // DIFFICULTY_BLOCKS_COUNT = 61 + // next_difficulty_v2's N = DIFFICULTY_WINDOW - 1 + // + // And we resized timestamps/difficulties to (N+1) (chopping off the latest timestamp). + // + // Now we re-adjust DIFFICULTY_WINDOW to 59. To preserve the old behaviour we + // add +2. After HF16 we avoid trimming the top block and just add +1. + // + // Ideally, we just set DIFFICULTY_BLOCKS_COUNT to DIFFICULTY_WINDOW + // + 1 for before and after HF16 (having one unified constant) but this + // requires some more investigation to get it working with pre HF16 blocks and + // alt chain code without bugs. + uint64_t result = (before_hf16) ? DIFFICULTY_WINDOW + 2 : DIFFICULTY_WINDOW + 1; + return result; + } + + inline constexpr auto TARGET_BLOCK_TIME_12 = 2min; + inline constexpr uint64_t BLOCKS_PER_HOUR_12 = 1h / TARGET_BLOCK_TIME_12; + inline constexpr uint64_t BLOCKS_PER_DAY_12 = 24h / TARGET_BLOCK_TIME_12; -#define MEMPOOL_PRUNE_NON_STANDARD_TX_LIFETIME (2 * 60 * 60) // seconds, 2 hours -// see src/cryptonote_protocol/levin_notify.cpp -#define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes -#define CRYPTONOTE_NOISE_EPOCH_RANGE 30 // seconds -#define CRYPTONOTE_NOISE_MIN_DELAY 10 // seconds -#define CRYPTONOTE_NOISE_DELAY_RANGE 5 // seconds -#define CRYPTONOTE_NOISE_BYTES 3*1024 // 3 KiB -#define CRYPTONOTE_NOISE_CHANNELS 2 // Max outgoing connections per zone used for noise/covert sending - -#define CRYPTONOTE_MAX_FRAGMENTS 20 // ~20 * NOISE_BYTES max payload size for covert/noise send - - -#define P2P_LOCAL_WHITE_PEERLIST_LIMIT 1000 -#define P2P_LOCAL_GRAY_PEERLIST_LIMIT 5000 - -#define P2P_DEFAULT_CONNECTIONS_COUNT_OUT 8 -#define P2P_DEFAULT_CONNECTIONS_COUNT_IN 32 -#define P2P_DEFAULT_HANDSHAKE_INTERVAL 60 //secondes -#define P2P_DEFAULT_PACKET_MAX_SIZE 50000000 //50000000 bytes maximum packet size -#define P2P_DEFAULT_PEERS_IN_HANDSHAKE 250 -#define P2P_DEFAULT_CONNECTION_TIMEOUT 5000 //5 seconds -#define P2P_DEFAULT_SOCKS_CONNECT_TIMEOUT 45 // seconds -#define P2P_DEFAULT_PING_CONNECTION_TIMEOUT 2000 //2 seconds -#define P2P_DEFAULT_INVOKE_TIMEOUT 60*2*1000 //2 minutes -#define P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 5000 //5 seconds -#define P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT 70 -#define P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT 2 -#define P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT 2 -#define P2P_DEFAULT_LIMIT_RATE_UP 2048 // kB/s -#define P2P_DEFAULT_LIMIT_RATE_DOWN 8192 // kB/s - -#define P2P_FAILED_ADDR_FORGET_SECONDS (60*60) //1 hour -#define P2P_IP_BLOCKTIME (60*60*24) //24 hour -#define P2P_IP_FAILS_BEFORE_BLOCK 10 -#define P2P_IDLE_CONNECTION_KILL_INTERVAL (5*60) //5 minutes - -// TODO(doyle): Deprecate after checkpointing hardfork, remove notion of being -// able to sync non-fluffy blocks, keep here so we can still accept blocks -// pre-hardfork -#define P2P_SUPPORT_FLAG_FLUFFY_BLOCKS 0x01 -#define P2P_SUPPORT_FLAGS P2P_SUPPORT_FLAG_FLUFFY_BLOCKS - -#define CRYPTONOTE_NAME "beldex" -#define CRYPTONOTE_POOLDATA_FILENAME "poolstate.bin" -#define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "data.mdb" -#define CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME "lock.mdb" -#define P2P_NET_DATA_FILENAME "p2pstate.bin" -#define MINER_CONFIG_FILE_NAME "miner_conf.json" - -#define THREAD_STACK_SIZE 5 * 1024 * 1024 - -#define HF_VERSION_PER_BYTE_FEE cryptonote::network_version_10_bulletproofs -#define HF_VERSION_SMALLER_BP cryptonote::network_version_11_infinite_staking -#define HF_VERSION_LONG_TERM_BLOCK_WEIGHT cryptonote::network_version_11_infinite_staking -#define HF_VERSION_PER_OUTPUT_FEE cryptonote::network_version_14_enforce_checkpoints -#define HF_VERSION_ED25519_KEY cryptonote::network_version_14_enforce_checkpoints -#define HF_VERSION_FEE_BURNING cryptonote::network_version_15_flash -#define HF_VERSION_FLASH cryptonote::network_version_15_flash -#define HF_VERSION_REDUCE_FEE cryptonote::network_version_17_POS -#define HF_VERSION_MIN_2_OUTPUTS cryptonote::network_version_17_POS -#define HF_VERSION_REJECT_SIGS_IN_COINBASE cryptonote::network_version_17_POS -#define HF_VERSION_ENFORCE_MIN_AGE cryptonote::network_version_17_POS -#define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY cryptonote::network_version_17_POS -#define HF_VERSION_POS cryptonote::network_version_17_POS -#define HF_VERSION_CLSAG cryptonote::network_version_15_flash -#define HF_VERSION_PROOF_BTENC cryptonote::network_version_18_bns - -#define PER_KB_FEE_QUANTIZATION_DECIMALS 8 - -#define HASH_OF_HASHES_STEP 256 - -#define DEFAULT_TXPOOL_MAX_WEIGHT 648000000ull // 3 days at 300000, in bytes - -#define BULLETPROOF_MAX_OUTPUTS 16 - -#define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase -#define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved -#define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved -//#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED +} // namespace old // New constants are intended to go here namespace config { - inline constexpr auto DNS_TIMEOUT = 20s; - inline constexpr uint64_t DEFAULT_FEE_ATOMIC_XMR_PER_KB = 500; // Just a placeholder! Change me! - inline constexpr uint8_t FEE_CALCULATION_MAX_RETRIES = 10; inline constexpr uint64_t DEFAULT_DUST_THRESHOLD = 2000000000; // 2 * pow(10, 9) - inline constexpr uint64_t BASE_REWARD_CLAMP_THRESHOLD = 100000000; // pow(10, 8) - - // Maximum allowed stake contribution, as a fraction of the available contribution room. This - // should generally be slightly larger than 1. This is used to disallow large overcontributions - // which can happen when there are competing stakes submitted at the same time for the same - // master node. - using MAXIMUM_ACCEPTABLE_STAKE = std::ratio<101, 100>; // Used to estimate the blockchain height from a timestamp, with some grace time. This can drift // slightly over time (because average block time is not typically *exactly* @@ -229,9 +311,9 @@ namespace config inline constexpr uint64_t BNS_VALIDATION_HEIGHT = 2068850; inline constexpr time_t HEIGHT_ESTIMATE_TIMESTAMP = 1639187815; - inline constexpr uint64_t CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 0xd1; - inline constexpr uint64_t CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19; - inline constexpr uint64_t CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 42; + inline constexpr uint64_t PUBLIC_ADDRESS_BASE58_PREFIX = 0xd1; + inline constexpr uint64_t PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19; + inline constexpr uint64_t PUBLIC_SUBADDRESS_BASE58_PREFIX = 42; inline constexpr uint16_t P2P_DEFAULT_PORT = 19090; inline constexpr uint16_t RPC_DEFAULT_PORT = 19091; inline constexpr uint16_t ZMQ_RPC_DEFAULT_PORT = 19092; @@ -242,11 +324,11 @@ namespace config inline constexpr std::string_view GENESIS_TX = "013c01ff0005978c390224a302c019c844f7141f35bf7f0fc5b02ada055e4ba897557b17ac6ccf88f0a2c09fab030276d443549feee11fe325048eeea083fcb7535312572d255ede1ecb58f84253b480e89226023b7d7c5e6eff4da699393abf12b6e3d04eae7909ae21932520fb3166b8575bb180cab5ee0102e93beb645ce7d5574d6a5ed5d9b8aadec7368342d08a7ca7b342a428353a10df80e497d01202b6e6844c1e9a478d0e4f7f34e455b26077a51f0005357aa19a49ca16eb373f622101f7c2a3a2ed7011b61998b1cd4f45b4d3c1daaa82908a10ca191342297eef1cf8"sv; inline constexpr uint32_t GENESIS_NONCE = 11011; - inline constexpr uint64_t GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS = 7 * BLOCKS_PER_DAY_OLD;//Governance added from V17 + inline constexpr uint64_t GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS = 7 * old::BLOCKS_PER_DAY_12;//Governance added from V17 inline constexpr std::array GOVERNANCE_WALLET_ADDRESS = { - "bxcguQiBhYaDW5wAdPLSwRHA6saX1nCEYUF89SPKZfBY1BENdLQWjti59aEtAEgrVZjnCJEVFoCDrG1DCoz2HeeN2pxhxL9xa"sv, // = cryptonote::network_version_17_POS ? 1 : 0]; + inline constexpr std::string_view governance_wallet_address(hf hard_fork_version) const { + return GOVERNANCE_WALLET_ADDRESS[hard_fork_version >= hf::hf17_POS ? 1 : 0]; } }; + inline constexpr network_config mainnet_config{ MAINNET, - ::config::HEIGHT_ESTIMATE_HEIGHT, - ::config::BNS_VALIDATION_HEIGHT, - ::config::HEIGHT_ESTIMATE_TIMESTAMP, - ::config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, - ::config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, - ::config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX, - ::config::P2P_DEFAULT_PORT, - ::config::RPC_DEFAULT_PORT, - ::config::ZMQ_RPC_DEFAULT_PORT, - ::config::QNET_DEFAULT_PORT, - ::config::NETWORK_ID, - ::config::GENESIS_TX, - ::config::GENESIS_NONCE, - ::config::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, - ::config::GOVERNANCE_WALLET_ADDRESS, + config::HEIGHT_ESTIMATE_HEIGHT, + config::BNS_VALIDATION_HEIGHT, + config::HEIGHT_ESTIMATE_TIMESTAMP, + config::PUBLIC_ADDRESS_BASE58_PREFIX, + config::PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, + config::PUBLIC_SUBADDRESS_BASE58_PREFIX, + config::P2P_DEFAULT_PORT, + config::RPC_DEFAULT_PORT, + config::ZMQ_RPC_DEFAULT_PORT, + config::QNET_DEFAULT_PORT, + config::NETWORK_ID, + config::GENESIS_TX, + config::GENESIS_NONCE, + config::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, + config::GOVERNANCE_WALLET_ADDRESS, config::UPTIME_PROOF_TOLERANCE, config::UPTIME_PROOF_STARTUP_DELAY, config::UPTIME_PROOF_CHECK_INTERVAL, @@ -431,21 +458,21 @@ namespace cryptonote }; inline constexpr network_config testnet_config{ TESTNET, - ::config::testnet::HEIGHT_ESTIMATE_HEIGHT, - ::config::testnet::BNS_VALIDATION_HEIGHT, - ::config::testnet::HEIGHT_ESTIMATE_TIMESTAMP, - ::config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, - ::config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, - ::config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX, - ::config::testnet::P2P_DEFAULT_PORT, - ::config::testnet::RPC_DEFAULT_PORT, - ::config::testnet::ZMQ_RPC_DEFAULT_PORT, - ::config::testnet::QNET_DEFAULT_PORT, - ::config::testnet::NETWORK_ID, - ::config::testnet::GENESIS_TX, - ::config::testnet::GENESIS_NONCE, - ::config::testnet::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, - ::config::testnet::GOVERNANCE_WALLET_ADDRESS, + config::testnet::HEIGHT_ESTIMATE_HEIGHT, + config::testnet::BNS_VALIDATION_HEIGHT, + config::testnet::HEIGHT_ESTIMATE_TIMESTAMP, + config::testnet::PUBLIC_ADDRESS_BASE58_PREFIX, + config::testnet::PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, + config::testnet::PUBLIC_SUBADDRESS_BASE58_PREFIX, + config::testnet::P2P_DEFAULT_PORT, + config::testnet::RPC_DEFAULT_PORT, + config::testnet::ZMQ_RPC_DEFAULT_PORT, + config::testnet::QNET_DEFAULT_PORT, + config::testnet::NETWORK_ID, + config::testnet::GENESIS_TX, + config::testnet::GENESIS_NONCE, + config::testnet::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, + config::testnet::GOVERNANCE_WALLET_ADDRESS, config::UPTIME_PROOF_TOLERANCE, config::UPTIME_PROOF_STARTUP_DELAY, config::UPTIME_PROOF_CHECK_INTERVAL, @@ -454,21 +481,21 @@ namespace cryptonote }; inline constexpr network_config devnet_config{ DEVNET, - ::config::devnet::HEIGHT_ESTIMATE_HEIGHT, - ::config::devnet::BNS_VALIDATION_HEIGHT, - ::config::devnet::HEIGHT_ESTIMATE_TIMESTAMP, - ::config::devnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, - ::config::devnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, - ::config::devnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX, - ::config::devnet::P2P_DEFAULT_PORT, - ::config::devnet::RPC_DEFAULT_PORT, - ::config::devnet::ZMQ_RPC_DEFAULT_PORT, - ::config::devnet::QNET_DEFAULT_PORT, - ::config::devnet::NETWORK_ID, - ::config::devnet::GENESIS_TX, - ::config::devnet::GENESIS_NONCE, - ::config::devnet::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, - ::config::devnet::GOVERNANCE_WALLET_ADDRESS, + config::devnet::HEIGHT_ESTIMATE_HEIGHT, + config::devnet::BNS_VALIDATION_HEIGHT, + config::devnet::HEIGHT_ESTIMATE_TIMESTAMP, + config::devnet::PUBLIC_ADDRESS_BASE58_PREFIX, + config::devnet::PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, + config::devnet::PUBLIC_SUBADDRESS_BASE58_PREFIX, + config::devnet::P2P_DEFAULT_PORT, + config::devnet::RPC_DEFAULT_PORT, + config::devnet::ZMQ_RPC_DEFAULT_PORT, + config::devnet::QNET_DEFAULT_PORT, + config::devnet::NETWORK_ID, + config::devnet::GENESIS_TX, + config::devnet::GENESIS_NONCE, + config::devnet::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, + config::devnet::GOVERNANCE_WALLET_ADDRESS, config::UPTIME_PROOF_TOLERANCE, config::UPTIME_PROOF_STARTUP_DELAY, config::UPTIME_PROOF_CHECK_INTERVAL, @@ -477,21 +504,21 @@ namespace cryptonote }; inline constexpr network_config fakenet_config{ FAKECHAIN, - ::config::HEIGHT_ESTIMATE_HEIGHT, - ::config::BNS_VALIDATION_HEIGHT, - ::config::HEIGHT_ESTIMATE_TIMESTAMP, - ::config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, - ::config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, - ::config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX, - ::config::P2P_DEFAULT_PORT, - ::config::RPC_DEFAULT_PORT, - ::config::ZMQ_RPC_DEFAULT_PORT, - ::config::QNET_DEFAULT_PORT, - ::config::NETWORK_ID, - ::config::GENESIS_TX, - ::config::GENESIS_NONCE, - 100, //::config::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, - ::config::GOVERNANCE_WALLET_ADDRESS, + config::HEIGHT_ESTIMATE_HEIGHT, + config::BNS_VALIDATION_HEIGHT, + config::HEIGHT_ESTIMATE_TIMESTAMP, + config::PUBLIC_ADDRESS_BASE58_PREFIX, + config::PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX, + config::PUBLIC_SUBADDRESS_BASE58_PREFIX, + config::P2P_DEFAULT_PORT, + config::RPC_DEFAULT_PORT, + config::ZMQ_RPC_DEFAULT_PORT, + config::QNET_DEFAULT_PORT, + config::NETWORK_ID, + config::GENESIS_TX, + config::GENESIS_NONCE, + 100, //config::GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS, + config::GOVERNANCE_WALLET_ADDRESS, config::UPTIME_PROOF_TOLERANCE, config::fakechain::UPTIME_PROOF_STARTUP_DELAY, config::fakechain::UPTIME_PROOF_CHECK_INTERVAL, @@ -499,15 +526,16 @@ namespace cryptonote config::fakechain::UPTIME_PROOF_VALIDITY, }; - inline constexpr const network_config& get_config(network_type nettype) +inline constexpr const network_config& get_config(network_type nettype) +{ + switch (nettype) { - switch (nettype) - { - case MAINNET: return mainnet_config; - case TESTNET: return testnet_config; - case DEVNET: return devnet_config; - case FAKECHAIN: return fakenet_config; - default: throw std::runtime_error{"Invalid network type"}; - } + case MAINNET: return mainnet_config; + case TESTNET: return testnet_config; + case DEVNET: return devnet_config; + case FAKECHAIN: return fakenet_config; + default: throw std::runtime_error{"Invalid network type"}; } } + +} \ No newline at end of file diff --git a/src/cryptonote_core/beldex_name_system.cpp b/src/cryptonote_core/beldex_name_system.cpp index 60caac947e8..46a0dbc8b5b 100755 --- a/src/cryptonote_core/beldex_name_system.cpp +++ b/src/cryptonote_core/beldex_name_system.cpp @@ -4,6 +4,7 @@ #include #include #include "common/hex.h" +#include "cryptonote_config.h" #include "beldex_name_system.h" #include "common/beldex.h" @@ -36,6 +37,8 @@ extern "C" #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "bns" +using cryptonote::hf; + namespace bns { @@ -653,14 +656,14 @@ sqlite3 *init_beldex_name_system(const fs::path& file_path, bool read_only) return result; } -std::vector all_mapping_types(uint8_t hf_version) { +std::vector all_mapping_types(hf hf_version) { std::vector result; result.reserve(2); - if (hf_version >= cryptonote::network_version_16) + if (hf_version >= hf::hf16) result.push_back(mapping_type::bchat); - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) result.push_back(mapping_type::belnet); - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) result.push_back(mapping_type::wallet); return result; } @@ -669,7 +672,7 @@ std::optional expiry_blocks(cryptonote::network_type nettype, mapping_ { std::optional result; - result = BLOCKS_PER_DAY * REGISTRATION_YEAR_DAYS * ( + result = cryptonote::BLOCKS_PER_DAY * REGISTRATION_YEAR_DAYS * ( map_years == mapping_years::bns_1year ? 1 : map_years == mapping_years::bns_2years ? 2 : map_years == mapping_years::bns_5years ? 5 : @@ -677,10 +680,10 @@ std::optional expiry_blocks(cryptonote::network_type nettype, mapping_ assert(result && *result); - if (nettype == cryptonote::TESTNET) // For testnet we shorten 1/2/5/10 years renewals to 1/2/5 days. + if (nettype == cryptonote::network_type::TESTNET) // For testnet we shorten 1/2/5/10 years renewals to 1/2/5 days. *result /= REGISTRATION_YEAR_DAYS; - else if (nettype == cryptonote::FAKECHAIN) // For fakenet testing we shorten 1/2/5/10 years to 2/4/10/20 blocks - *result /= (BLOCKS_PER_DAY * REGISTRATION_YEAR_DAYS / 2); + else if (nettype == cryptonote::network_type::FAKECHAIN) // For fakenet testing we shorten 1/2/5/10 years to 2/4/10/20 blocks + *result /= (cryptonote::BLOCKS_PER_DAY * REGISTRATION_YEAR_DAYS / 2); return result; } @@ -1229,7 +1232,7 @@ static bool validate_against_previous_mapping(bns::name_system_db &bns_db, uint6 // Sanity check value to disallow the empty name hash static const crypto::hash null_name_hash = name_to_hash(""); -bool name_system_db::validate_bns_tx(uint8_t hf_version, uint64_t blockchain_height, cryptonote::transaction const &tx, cryptonote::tx_extra_beldex_name_system &bns_extra, std::string *reason) +bool name_system_db::validate_bns_tx(hf hf_version, uint64_t blockchain_height, cryptonote::transaction const &tx, cryptonote::tx_extra_beldex_name_system &bns_extra, std::string *reason) { // ----------------------------------------------------------------------------------------------- // Pull out BNS Extra from TX @@ -1320,7 +1323,7 @@ bool name_system_db::validate_bns_tx(uint8_t hf_version, uint64_t blockchain_hei if (bns_extra.field_is_set(bns::extra_field::encrypted_eth_addr_value)) { // BNS Allowed type Validation - if (check_condition(hf_version < cryptonote::network_version_19, reason, tx, ", ", bns_extra_string(nettype, bns_extra)," specifying eth_addr is disallowed in HF", +static_cast(hf_version))) + if (check_condition(hf_version < cryptonote::hf::hf19_enhance_bns, reason, tx, ", ", bns_extra_string(nettype, bns_extra)," specifying eth_addr is disallowed in HF", +static_cast(hf_version))) return false; if (!mapping_value::validate_encrypted(mapping_type::eth_addr, bns_extra.encrypted_eth_addr_value, nullptr, reason)) @@ -1356,7 +1359,7 @@ bool name_system_db::validate_bns_tx(uint8_t hf_version, uint64_t blockchain_hei return true; } -bool validate_mapping_type(std::string_view mapping_type_str, uint8_t hf_version, bns::mapping_type *mapping_type, std::string *reason) +bool validate_mapping_type(std::string_view mapping_type_str, hf hf_version, bns::mapping_type *mapping_type, std::string *reason) { std::string mapping = tools::lowercase_ascii_string(mapping_type_str); std::optional mapping_type_; @@ -2195,7 +2198,7 @@ bool name_system_db::add_block(const cryptonote::block &block, const std::vector return false; bool bns_parsed_from_block = false; - if (block.major_version >= cryptonote::network_version_18_bns) + if (block.major_version >= hf::hf18_bns) { for (cryptonote::transaction const &tx : txs) { diff --git a/src/cryptonote_core/beldex_name_system.h b/src/cryptonote_core/beldex_name_system.h index 4bcb91bdad3..32b5638e0cd 100755 --- a/src/cryptonote_core/beldex_name_system.h +++ b/src/cryptonote_core/beldex_name_system.h @@ -132,13 +132,13 @@ inline std::string_view mapping_type_str(mapping_type type) } inline std::ostream &operator<<(std::ostream &os, mapping_type type) { return os << mapping_type_str(type); } -constexpr bool mapping_type_allowed(uint8_t hf_version, mapping_type type) { - return (type == mapping_type::bchat && hf_version >= cryptonote::network_version_16) - || (type == mapping_type::belnet && hf_version >= cryptonote::network_version_17_POS) || (type == mapping_type::wallet && hf_version >= cryptonote::network_version_17_POS); +constexpr bool mapping_type_allowed(cryptonote::hf hf_version, mapping_type type) { + return (type == mapping_type::bchat && hf_version >= cryptonote::hf::hf16) + || (type == mapping_type::belnet && hf_version >= cryptonote::hf::hf17_POS) || (type == mapping_type::wallet && hf_version >= cryptonote::hf::hf17_POS); } // Returns all mapping types supported for lookup as of the given hardfork. -std::vector all_mapping_types(uint8_t hf_version); +std::vector all_mapping_types(cryptonote::hf hf_version); sqlite3 *init_beldex_name_system(const fs::path& file_path, bool read_only); @@ -191,7 +191,7 @@ enum struct bns_tx_type { lookup, buy, update, renew }; // Currently accepts "bchat" or "belnet" for lookups, buys, updates, and renewals; for buys and renewals also accepts "belnet_Ny[ear]" for N=2,5,10 // Lookups are implied by none of buy/update/renew. // mapping_type: (optional) if function returns true, the uint16_t value of the 'type' will be set -bool validate_mapping_type(std::string_view type, uint8_t hf_version, mapping_type *mapping_type, std::string *reason); +bool validate_mapping_type(std::string_view type, cryptonote::hf hf_version, mapping_type *mapping_type, std::string *reason); // Hashes an BNS name. The name must already be lower-case (but this is only checked in debug builds). crypto::hash name_to_hash(std::string_view name, const std::optional& key = std::nullopt); // Takes a human readable name and hashes it. Takes an optional value to use as a key to produce a keyed hash. @@ -320,7 +320,7 @@ struct name_system_db // Validates an BNS transaction. If the function returns true then entry will be populated with // the BNS details. On a false return, `reason` is instead populated with the failure reason. - bool validate_bns_tx(uint8_t hf_version, uint64_t blockchain_height, cryptonote::transaction const &tx, cryptonote::tx_extra_beldex_name_system &entry, std::string *reason); + bool validate_bns_tx(cryptonote::hf hf_version, uint64_t blockchain_height, cryptonote::transaction const &tx, cryptonote::tx_extra_beldex_name_system &entry, std::string *reason); // Destructor; closes the sqlite3 database if one is open ~name_system_db(); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index e122470db9f..4aba3c60b7d 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -113,10 +113,10 @@ Blockchain::block_extended_info::block_extended_info(const alt_block_data_t &src Blockchain::Blockchain(tx_memory_pool& tx_pool, master_nodes::master_node_list& master_node_list): m_db(), m_tx_pool(tx_pool), m_current_block_cumul_weight_limit(0), m_current_block_cumul_weight_median(0), m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false), - m_long_term_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), + m_long_term_block_weights_window(LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), m_long_term_effective_median_block_weight(0), m_long_term_block_weights_cache_tip_hash(crypto::null_hash), - m_long_term_block_weights_cache_rolling_median(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), + m_long_term_block_weights_cache_rolling_median(LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE), m_master_node_list(master_node_list), m_btc_valid(false), m_batch_success(true), @@ -300,8 +300,8 @@ uint64_t Blockchain::get_current_blockchain_height(bool lock) const //------------------------------------------------------------------ bool Blockchain::load_missing_blocks_into_beldex_subsystems() { - uint64_t const mnl_height = std::max(hard_fork_begins(m_nettype, network_version_9_master_nodes).value_or(0), m_master_node_list.height() + 1); - uint64_t const bns_height = std::max(hard_fork_begins(m_nettype, network_version_18_bns).value_or(0), m_bns_db.height() + 1); + uint64_t const mnl_height = std::max(hard_fork_begins(m_nettype, hf::hf9_master_nodes).value_or(0), m_master_node_list.height() + 1); + uint64_t const bns_height = std::max(hard_fork_begins(m_nettype, hf::hf18_bns).value_or(0), m_bns_db.height() + 1); uint64_t const end_height = m_db->height(); uint64_t const start_height = std::min(end_height, std::min(bns_height, mnl_height)); @@ -369,7 +369,7 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() checkpoint_t *checkpoint_ptr = nullptr; checkpoint_t checkpoint; - if (blk.major_version >= cryptonote::network_version_14_enforce_checkpoints && get_checkpoint(block_height, checkpoint)) + if (blk.major_version >= hf::hf14_enforce_checkpoints && get_checkpoint(block_height, checkpoint)) checkpoint_ptr = &checkpoint; try { @@ -412,7 +412,7 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett { LOG_PRINT_L3("Blockchain::" << __func__); - CHECK_AND_ASSERT_MES(nettype != FAKECHAIN || test_options, false, "fake chain network type used without options"); + CHECK_AND_ASSERT_MES(nettype != network_type::FAKECHAIN || test_options, false, "fake chain network type used without options"); auto lock = tools::unique_locks(m_tx_pool, *this); @@ -439,7 +439,7 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett // in integration mode or --regtest m_nettype = nettype; #else - m_nettype = test_options != NULL ? FAKECHAIN : nettype; + m_nettype = test_options != NULL ? network_type::FAKECHAIN : nettype; #endif if (!m_checkpoints.init(m_nettype, m_db)) @@ -471,7 +471,7 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett { } - if (m_nettype != FAKECHAIN) + if (m_nettype != network_type::FAKECHAIN) m_db->fixup(m_nettype); db_rtxn_guard rtxn_guard(m_db); @@ -490,7 +490,7 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett m_async_thread = std::thread{[this] { m_async_service.run(); }}; #if defined(PER_BLOCK_CHECKPOINT) - if (m_nettype != FAKECHAIN) + if (m_nettype != network_type::FAKECHAIN) load_compiled_in_block_hashes(get_checkpoints); #endif @@ -503,8 +503,8 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett uint64_t top_height; const crypto::hash top_id = m_db->top_block_hash(&top_height); const block top_block = m_db->get_top_block(); - const uint8_t ideal_hf_version = get_network_version(top_height); - if (ideal_hf_version <= 1 || ideal_hf_version == top_block.major_version) + const auto ideal_hf_version = get_network_version(top_height); + if (ideal_hf_version <= hf::hf1 || ideal_hf_version == top_block.major_version) { if (num_popped_blocks > 0) MGINFO("Initial popping done, top block: " << top_id << ", top height: " << top_height << ", block version: " << (uint64_t)top_block.major_version); @@ -649,8 +649,8 @@ void Blockchain::pop_blocks(uint64_t nblocks) auto lock = tools::unique_locks(m_tx_pool, *this); bool stop_batch = m_db->batch_start(); - uint8_t hf_version = get_network_version(); - uint64_t blocks_expected_per_day = (hf_version>=cryptonote::network_version_17_POS) ? BLOCKS_PER_DAY : BLOCKS_PER_DAY_OLD ; + auto hf_version = get_network_version(); + uint64_t blocks_expected_per_day = (hf_version >= hf::hf17_POS) ? BLOCKS_PER_DAY : old::BLOCKS_PER_DAY_12 ; try { const uint64_t blockchain_height = m_db->height(); @@ -736,7 +736,7 @@ block Blockchain::pop_block_from_blockchain() { cryptonote::tx_verification_context tvc{}; - uint8_t version = get_network_version(m_db->height()); + hf version = get_network_version(m_db->height()); // We assume that if they were in a block, the transactions are already // known to the network as a whole. However, if we had mined that block, @@ -933,7 +933,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block(bool POS) if (POS) return POS_FIXED_DIFFICULTY; - uint8_t const hf_version = get_network_version(); + const auto hf_version = get_network_version(); crypto::hash top_hash = get_tail_id(); { std::unique_lock diff_lock{m_cache.m_difficulty_lock}; @@ -954,7 +954,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block(bool POS) m_nettype, m_cache.m_timestamps, m_cache.m_difficulties, chain_height, m_cache.m_timestamps_and_difficulties_height); uint64_t diff = next_difficulty_v2(m_cache.m_timestamps, m_cache.m_difficulties, - tools::to_seconds((hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)), + tools::to_seconds((hf_version >= hf::hf17_POS ? TARGET_BLOCK_TIME: old::TARGET_BLOCK_TIME_12)), difficulty_mode(m_nettype, chain_height)); m_cache.m_timestamps_and_difficulties_height = chain_height; @@ -1154,11 +1154,11 @@ difficulty_type Blockchain::get_difficulty_for_alternative_chain(const std::list { bool before_hf16 = true; if (alt_chain.size()) - before_hf16 = alt_chain.back().bl.major_version < network_version_17_POS; + before_hf16 = alt_chain.back().bl.major_version < hf::hf17_POS; else - before_hf16 = !is_hard_fork_at_least(m_nettype, cryptonote::network_version_17_POS, get_current_blockchain_height()); + before_hf16 = !is_hard_fork_at_least(m_nettype, hf::hf17_POS, get_current_blockchain_height()); - block_count = DIFFICULTY_BLOCKS_COUNT(before_hf16); + block_count = old::DIFFICULTY_BLOCKS_COUNT(before_hf16); } std::vector timestamps; @@ -1219,7 +1219,7 @@ difficulty_type Blockchain::get_difficulty_for_alternative_chain(const std::list auto hf_version = cryptonote::get_network_version(m_nettype, height); return next_difficulty_v2(timestamps, cumulative_difficulties, - tools::to_seconds((hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)), + tools::to_seconds((hf_version>=hf::hf17_POS?TARGET_BLOCK_TIME:old::TARGET_BLOCK_TIME_12)), difficulty_mode(m_nettype, height)); } //------------------------------------------------------------------ @@ -1228,7 +1228,7 @@ difficulty_type Blockchain::get_difficulty_for_alternative_chain(const std::list // 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) -bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, uint8_t hf_version) +bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, hf hf_version) { LOG_PRINT_L3("Blockchain::" << __func__); CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs"); @@ -1239,9 +1239,9 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, return false; } MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx)); - CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + MINED_MONEY_UNLOCK_WINDOW); - if (hf_version >= cryptonote::network_version_13_checkpointing) + if (hf_version >= hf::hf13_checkpointing) { if (b.miner_tx.type != txtype::standard) { @@ -1253,12 +1253,12 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, txversion max_version = transaction::get_min_version_for_hf(hf_version); if (b.miner_tx.version < min_version || b.miner_tx.version > max_version) { - MERROR_VER("Coinbase invalid version: " << b.miner_tx.version << " for hardfork: " << hf_version << " min/max version: " << min_version << "/" << max_version); + MERROR_VER("Coinbase invalid version: " << b.miner_tx.version << " for hardfork: " << static_cast(hf_version) << " min/max version: " << min_version << "/" << max_version); return false; } } - if (hf_version >= HF_VERSION_REJECT_SIGS_IN_COINBASE) // Enforce empty rct signatures for miner transactions, + if (hf_version >= feature::REJECT_SIGS_IN_COINBASE) // Enforce empty rct signatures for miner transactions, CHECK_AND_ASSERT_MES(b.miner_tx.rct_signatures.type == rct::RCTType::Null, false, "RingCT signatures not allowed in coinbase transactions"); //check outs overflow @@ -1275,7 +1275,7 @@ bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, } //------------------------------------------------------------------ // This function validates the miner transaction reward -bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, uint8_t version) +bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, hf version) { LOG_PRINT_L3("Blockchain::" << __func__); //validate reward @@ -1286,14 +1286,14 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl } uint64_t median_weight; - if (version >= HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY) + if (version >= feature::EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY) { median_weight = m_current_block_cumul_weight_median; } else { std::vector last_blocks_weights; - get_last_n_blocks_weights(last_blocks_weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + get_last_n_blocks_weights(last_blocks_weights, REWARD_BLOCKS_WINDOW); median_weight = tools::median(std::move(last_blocks_weights)); } @@ -1301,7 +1301,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl beldex_block_reward_context block_reward_context = {}; block_reward_context.fee = fee; block_reward_context.height = height; - block_reward_context.testnet_override = nettype() == TESTNET && height < 386000; + block_reward_context.testnet_override = nettype() == network_type::TESTNET && height < 386000; if (!calc_batched_governance_reward(height, block_reward_context.batched_governance)) { MERROR_VER("Failed to calculate batched governance reward"); @@ -1327,9 +1327,9 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl } } - if (already_generated_coins != 0 && block_has_governance_output(nettype(), b)) + if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version < hf::hf20_bulletproof_plusplus) { - if (version >= network_version_17_POS && reward_parts.governance_paid == 0) + if (version >= hf::hf17_POS && reward_parts.governance_paid == 0) { MERROR("Governance reward should not be 0 after hardfork v17 if this height has a governance output because it is the batched payout height"); return false; @@ -1357,7 +1357,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl // TODO(beldex): eliminate all floating point math in reward calculations. uint64_t max_base_reward = reward_parts.base_miner + reward_parts.governance_paid + reward_parts.master_node_total + 1; uint64_t max_money_in_use = max_base_reward + reward_parts.miner_fee; - if (money_in_use > max_money_in_use) + if (money_in_use > max_money_in_use && version < hf::hf20_bulletproof_plusplus) { MERROR_VER("coinbase transaction spends too much money (" << print_money(money_in_use) << "). Maximum block reward is " << print_money(max_money_in_use) << " (= " << print_money(max_base_reward) << " base + " << print_money(reward_parts.miner_fee) << " fees)"); @@ -1573,7 +1573,7 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight - uint8_t hf_version = b.major_version; + auto hf_version = b.major_version; auto miner_tx_context = info.is_miner ? beldex_miner_tx_context::miner_block(m_nettype, info.miner_address, m_master_node_list.get_block_leader()) @@ -1585,7 +1585,7 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr } crypto::signature security_signature; - if ((hf_version >= network_version_12_security_signature) && info.is_miner){ + if ((hf_version >= hf::hf12_security_signature) && info.is_miner){ crypto::hash hash = cryptonote::make_security_hash_from(height, b); const std::string skey_string = "8616b3fbc071ba5ed64e50cd4350691fa8fb07610fb61b698f2c989d1b30ea08"; @@ -1960,7 +1960,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id uint64_t block_reward = get_outs_money_amount(b.miner_tx); const uint64_t prev_generated_coins = alt_chain.size() ? prev_data.already_generated_coins : m_db->get_block_already_generated_coins(blk_height - 1); - alt_data.already_generated_coins = (block_reward < (MONEY_SUPPLY - prev_generated_coins)) ? prev_generated_coins + block_reward : MONEY_SUPPLY; + alt_data.already_generated_coins = (block_reward < (beldex::MONEY_SUPPLY - prev_generated_coins)) ? prev_generated_coins + block_reward : beldex::MONEY_SUPPLY; m_db->add_alt_block(id, alt_data, cryptonote::block_to_blob(b), checkpoint_blob.empty() ? nullptr : &checkpoint_blob); // Check current height for pre-existing checkpoint @@ -2038,7 +2038,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id bool const alt_chain_has_more_checkpoints = (num_checkpoints_on_alt_chain > num_checkpoints_on_chain); bool const alt_chain_has_equal_checkpoints = (num_checkpoints_on_alt_chain == num_checkpoints_on_chain); - if (b.major_version >= cryptonote::network_version_17_POS) + if (b.major_version >= hf::hf17_POS) { // In POS, we move away from the concept of difficulty to solve ties // between chains. We calculate the preferred chain using a simpler system. @@ -2110,7 +2110,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id difficulty_type const main_chain_cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); bool const alt_chain_has_greater_pow = alt_data.cumulative_difficulty > main_chain_cumulative_difficulty; - if (b.major_version >= network_version_14_enforce_checkpoints) + if (b.major_version >= hf::hf14_enforce_checkpoints) { if (alt_chain_has_more_checkpoints || (alt_chain_has_greater_pow && alt_chain_has_equal_checkpoints)) { @@ -2402,8 +2402,8 @@ uint64_t Blockchain::get_num_mature_outputs(uint64_t amount) const { const tx_out_index toi = m_db->get_output_tx_and_index(amount, num_outs - 1); const uint64_t height = m_db->get_tx_block_height(toi.first); - const uint8_t hf_version = get_network_version(height); - if ((height + (hf_version>=cryptonote::network_version_17_POS?CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17:CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE) )<= blockchain_height) + const hf hf_version = get_network_version(height); + if ((height + (hf_version>=hf::hf17_POS?DEFAULT_TX_SPENDABLE_AGE_V17:old::DEFAULT_TX_SPENDABLE_AGE) )<= blockchain_height) break; --num_outs; } @@ -2763,7 +2763,7 @@ bool Blockchain::find_blockchain_supplement(const std::list& qbloc stop_height = tools::get_next_pruned_block_height(start_height, current_height, pruning_seed); } size_t count = 0; - hashes.reserve(std::min((size_t)(stop_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT)); + hashes.reserve(std::min((size_t)(stop_height - start_height), BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT)); for(size_t i = start_height; i < stop_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) { hashes.push_back(m_db->get_block_hash_from_height(i)); @@ -2857,7 +2857,7 @@ bool Blockchain::add_block_as_invalid(cryptonote::block const &block) return true; } -uint8_t Blockchain::get_network_version(std::optional height) const { +hf Blockchain::get_network_version(std::optional height) const { if (!height) height = get_current_blockchain_height(); return cryptonote::get_network_version(m_nettype, *height); } @@ -3068,8 +3068,8 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // from v10, allow bulletproofs auto height = get_current_blockchain_height(); - const uint8_t hf_version = get_network_version(height); - if (hf_version < network_version_8) { + const hf hf_version = get_network_version(height); + if (hf_version < hf::hf8) { const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); if (bulletproof || !tx.rct_signatures.p.bulletproofs.empty()) { @@ -3083,7 +3083,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // The HF10 block height itself was allowed to (and did) have a Borromean tx as an exception // to the HF10 rules so that a borderline tx didn't end up unmineable, hence the strict `>` // here: - if (auto hf10_height = hard_fork_begins(m_nettype, network_version_10_bulletproofs); + if (auto hf10_height = hard_fork_begins(m_nettype, hf::hf10_bulletproofs); hf10_height && height > *hf10_height) { MERROR_VER("Borromean range proofs are not allowed after v10"); @@ -3092,21 +3092,21 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } - if (hf_version < HF_VERSION_SMALLER_BP) { + if (hf_version < feature::SMALLER_BP) { if (tx.rct_signatures.type == rct::RCTType::Bulletproof2) { - MERROR_VER("Ringct type " << (unsigned)rct::RCTType::Bulletproof2 << " is not allowed before v" << HF_VERSION_SMALLER_BP); + MERROR_VER("Ringct type " << (unsigned)rct::RCTType::Bulletproof2 << " is not allowed before v" << static_cast(feature::SMALLER_BP)); tvc.m_invalid_output = true; return false; } } - if (hf_version > HF_VERSION_SMALLER_BP) { + if (hf_version > feature::SMALLER_BP) { if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { if (tx.rct_signatures.type == rct::RCTType::Bulletproof) { - MERROR_VER("Ringct type " << (unsigned)rct::RCTType::Bulletproof << " is not allowed from v" << (HF_VERSION_SMALLER_BP + 1)); + MERROR_VER("Ringct type " << (unsigned)rct::RCTType::Bulletproof << " is not allowed after v" << static_cast(feature::SMALLER_BP)); tvc.m_invalid_output = true; return false; } @@ -3114,11 +3114,11 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } // Disallow CLSAGs before the CLSAG hardfork - if (hf_version < HF_VERSION_CLSAG) { + if (hf_version < feature::CLSAG) { if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { if (tx.rct_signatures.type == rct::RCTType::CLSAG) { - MERROR_VER("Ringct type " << (unsigned)rct::RCTType::CLSAG << " is not allowed before v" << HF_VERSION_CLSAG); + MERROR_VER("Ringct type " << (unsigned)rct::RCTType::CLSAG << " is not allowed before v" << static_cast(feature::CLSAG)); tvc.m_invalid_output = true; return false; } @@ -3128,12 +3128,12 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context // Require CLSAGs starting 10 blocks after the CLSAG-enabling hard fork (the 10 block buffer is to // allow staggling txes around fork time to still make it into a block). // NB: there *are* such txes on mainnet in this 10-block window so this code has to stay. - if (hf_version >= HF_VERSION_CLSAG + if (hf_version >= feature::CLSAG && tx.rct_signatures.type < rct::RCTType::CLSAG && tx.version >= txversion::v4_tx_types && tx.is_transfer() - && (hf_version > HF_VERSION_CLSAG || height >= 10 + *hard_fork_begins(m_nettype, HF_VERSION_CLSAG))) + && (hf_version > feature::CLSAG || height >= 10 + *hard_fork_begins(m_nettype, feature::CLSAG))) { - MERROR_VER("Ringct type " << (unsigned)tx.rct_signatures.type << " is not allowed from v" << HF_VERSION_CLSAG); + MERROR_VER("Ringct type " << (unsigned)tx.rct_signatures.type << " is not allowed from v" << static_cast(feature::CLSAG)); tvc.m_invalid_output = true; return false; } @@ -3267,9 +3267,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if (tx.is_transfer()) { - if (tx.type != txtype::beldex_name_system && tx.type != txtype::coin_burn && hf_version >= HF_VERSION_MIN_2_OUTPUTS && tx.vout.size() < 2) + if (tx.type != txtype::beldex_name_system && tx.type != txtype::coin_burn && hf_version >= feature::MIN_2_OUTPUTS && tx.vout.size() < 2) { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has fewer than two outputs, which is not allowed as of hardfork " << +HF_VERSION_MIN_2_OUTPUTS); + MERROR_VER("Tx " << get_transaction_hash(tx) << " has fewer than two outputs, which is not allowed as of hardfork " << static_cast(feature::MIN_2_OUTPUTS)); tvc.m_too_few_outputs = true; return false; } @@ -3294,11 +3294,11 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "empty in_to_key.key_offsets in transaction with id " << get_transaction_hash(tx)); // Mixin Check, from hard fork 7, we require mixin at least 9, always. - if (((hf_version <=7) && (in_to_key.key_offsets.size() - 1 < 6) && tx.version == txversion::v2_ringct) || - ((hf_version ==8) && (in_to_key.key_offsets.size() - 1 < 7) ) || - ((hf_version >8 ) && (in_to_key.key_offsets.size() - 1 != CRYPTONOTE_DEFAULT_TX_MIXIN))) + if (((hf_version <= hf::hf7 ) && (in_to_key.key_offsets.size() - 1 < 6) && tx.version == txversion::v2_ringct) || + ((hf_version == hf::hf8) && (in_to_key.key_offsets.size() - 1 < 7) ) || + ((hf_version > hf::hf8 ) && (in_to_key.key_offsets.size() - 1 != cryptonote::TX_OUTPUT_DECOYS))) { - MERROR_VER("Tx " << get_transaction_hash(tx) << " has incorrect ring size (" << in_to_key.key_offsets.size() - 1 << ", expected (" << CRYPTONOTE_DEFAULT_TX_MIXIN << ")"); + MERROR_VER("Tx " << get_transaction_hash(tx) << " has incorrect ring size (" << in_to_key.key_offsets.size() - 1 << ", expected (" << cryptonote::TX_OUTPUT_DECOYS << ")"); tvc.m_low_mixin = true; return false; } @@ -3343,7 +3343,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, // // Master Node Checks // - if (hf_version >= cryptonote::network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) { const auto &blacklist = m_master_node_list.get_blacklisted_key_images(); for (const auto &entry : blacklist) @@ -3366,9 +3366,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, } } - if (hf_version >= HF_VERSION_ENFORCE_MIN_AGE) + if (hf_version >= feature::ENFORCE_MIN_AGE) { - const uint8_t spendable_age = (hf_version>=cryptonote::network_version_17_POS?CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17:CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE); + const uint8_t spendable_age = (hf_version >= hf::hf17_POS ? DEFAULT_TX_SPENDABLE_AGE_V17: old::DEFAULT_TX_SPENDABLE_AGE); CHECK_AND_ASSERT_MES((*pmax_used_block_height + spendable_age) <= m_db->height(), false, "Transaction spends at least one output which is too young"); } @@ -3522,7 +3522,7 @@ if (tx.version >= cryptonote::txversion::v2_ringct) } // for bulletproofs, check they're only multi-output after v8 - if (rct::is_rct_bulletproof(rv.type) && hf_version < network_version_8) + if (rct::is_rct_bulletproof(rv.type) && hf_version < hf::hf8) { for (const rct::Bulletproof &proof: rv.p.bulletproofs) { @@ -3537,7 +3537,7 @@ if (tx.version >= cryptonote::txversion::v2_ringct) if (tx.type == txtype::beldex_name_system) { uint64_t height = get_current_blockchain_height(); - if (hf_version >= network_version_18_bns) + if (hf_version >= hf::hf18_bns) { cryptonote::tx_extra_beldex_name_system data; std::string fail_reason; @@ -3618,7 +3618,7 @@ if (tx.version >= cryptonote::txversion::v2_ringct) if (master_node_array.empty()) { MERROR_VER("Master Node no longer exists on the network, state change can be ignored"); - return hf_version < cryptonote::network_version_13_checkpointing; // NOTE: Used to be allowed pre HF12. + return hf_version < hf::hf13_checkpointing; // NOTE: Used to be allowed pre HF12. } master_nodes::master_node_info const &master_node_info = *master_node_array[0].info; @@ -3693,14 +3693,14 @@ uint64_t Blockchain::get_fee_quantization_mask() if (mask == 0) { mask = 1; - for (size_t n = PER_KB_FEE_QUANTIZATION_DECIMALS; n < CRYPTONOTE_DISPLAY_DECIMAL_POINT; ++n) + for (size_t n = FEE_QUANTIZATION_DECIMALS; n < beldex::DISPLAY_DECIMAL_POINT; ++n) mask *= 10; } return mask; } //------------------------------------------------------------------ -byte_and_output_fees Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, uint8_t version) +byte_and_output_fees Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, hf version) { const uint64_t min_block_weight = get_min_block_weight(version); if (median_block_weight < min_block_weight) @@ -3708,7 +3708,7 @@ byte_and_output_fees Blockchain::get_dynamic_base_fee(uint64_t block_reward, siz byte_and_output_fees fees{0, 0}; uint64_t hi, &lo = fees.first; - if (version >= HF_VERSION_PER_BYTE_FEE) + if (version >= feature::PER_BYTE_FEE) { // fee = block_reward * DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT / min_block_weight / median_block_weight / 5 // (but done in 128-bit math). Note that the wallet uses FEE_PER_BYTE as a fallback if it can't @@ -3724,30 +3724,30 @@ byte_and_output_fees Blockchain::get_dynamic_base_fee(uint64_t block_reward, siz // This calculation was painful for large txes (in particular sweeps and MN stakes), which // wasn't intended, so in v13 we reduce the reference tx fee back to what it was before and // introduce a per-output fee instead. (This is why this is an hard == instead of a >=). - const uint64_t reference_fee = version == HF_VERSION_REDUCE_FEE ? DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT_V17 : DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT; + const uint64_t reference_fee = version != feature::REDUCE_FEE ? DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT : old::DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT_V17; lo = mul128(block_reward, reference_fee, &hi); div128_32(hi, lo, min_block_weight, &hi, &lo); div128_32(hi, lo, median_block_weight, &hi, &lo); assert(hi == 0); lo /= 5; - if (version >= cryptonote::network_version_17_POS) + if (version >= hf::hf17_POS) fees.second = FEE_PER_OUTPUT_V17; - else if (version >= HF_VERSION_PER_OUTPUT_FEE) - fees.second = FEE_PER_OUTPUT; + else if (version >= feature::PER_OUTPUT_FEE) + fees.second = old::FEE_PER_OUTPUT; return fees; } - constexpr uint64_t fee_base = DYNAMIC_FEE_PER_KB_BASE_FEE_V5; + constexpr uint64_t fee_base = old::DYNAMIC_FEE_PER_KB_BASE_FEE_V5; uint64_t unscaled_fee_base = (fee_base * min_block_weight / median_block_weight); lo = mul128(unscaled_fee_base, block_reward, &hi); - static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000"); - static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large"); + static_assert(old::DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000"); + static_assert(old::DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large"); - // divide in two steps, since the divisor must be 32 bits, but DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't - div128_32(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo); + // divide in two steps, since the divisor must be 32 bits, but old::DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't + div128_32(hi, lo, old::DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo); div128_32(hi, lo, 1000000, &hi, &lo); assert(hi == 0); @@ -3763,7 +3763,7 @@ byte_and_output_fees Blockchain::get_dynamic_base_fee(uint64_t block_reward, siz //------------------------------------------------------------------ bool Blockchain::check_fee(size_t tx_weight, size_t tx_outs, uint64_t fee, uint64_t burned, const tx_pool_options &opts) const { - const uint8_t version = get_network_version(); + const hf version = get_network_version(); const uint64_t blockchain_height = get_current_blockchain_height(); uint64_t median = m_current_block_cumul_weight_limit / 2; @@ -3773,9 +3773,9 @@ bool Blockchain::check_fee(size_t tx_weight, size_t tx_outs, uint64_t fee, uint6 return false; uint64_t needed_fee; - if (version >= HF_VERSION_PER_BYTE_FEE) + if (version >= feature::PER_BYTE_FEE) { - const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; + const bool use_long_term_median_in_fee = version >= feature::LONG_TERM_BLOCK_WEIGHT; auto fees = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? std::min(median, m_long_term_effective_median_block_weight) : median, version); MDEBUG("Using " << print_money(fees.first) << "/byte + " << print_money(fees.second) << "/out fee"); needed_fee = tx_weight * fees.first + tx_outs * fees.second; @@ -3822,15 +3822,15 @@ bool Blockchain::check_fee(size_t tx_weight, size_t tx_outs, uint64_t fee, uint6 //------------------------------------------------------------------ byte_and_output_fees Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const { - const uint8_t version = get_network_version(); + const hf version = get_network_version(); const uint64_t db_height = m_db->height(); - if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) - grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; + if (grace_blocks >= REWARD_BLOCKS_WINDOW) + grace_blocks = REWARD_BLOCKS_WINDOW - 1; const uint64_t min_block_weight = get_min_block_weight(version); std::vector weights; - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); + get_last_n_blocks_weights(weights, REWARD_BLOCKS_WINDOW - grace_blocks); weights.reserve(grace_blocks); for (size_t i = 0; i < grace_blocks; ++i) weights.push_back(min_block_weight); @@ -3847,10 +3847,10 @@ byte_and_output_fees Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_bl base_reward = BLOCK_REWARD_OVERESTIMATE; } - const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; + const bool use_long_term_median_in_fee = version >= feature::LONG_TERM_BLOCK_WEIGHT; const uint64_t use_median_value = use_long_term_median_in_fee ? std::min(median, m_long_term_effective_median_block_weight) : median; auto fee = get_dynamic_base_fee(base_reward, use_median_value, version); - const bool per_byte = version < HF_VERSION_PER_BYTE_FEE; + const bool per_byte = version < feature::PER_BYTE_FEE; MDEBUG("Estimating " << grace_blocks << "-block fee at " << print_money(fee.first) << "/" << (per_byte ? "byte" : "kB") << " + " << print_money(fee.second) << "/out"); return fee; @@ -3954,8 +3954,7 @@ bool Blockchain::check_block_timestamp(std::vector timestamps, const b bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const { LOG_PRINT_L3("Blockchain::" << __func__); - uint64_t cryptonote_block_future_time_limit = CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2; - if(b.timestamp > get_adjusted_time() + cryptonote_block_future_time_limit) + if(b.timestamp > get_adjusted_time() + old::BLOCK_FUTURE_TIME_LIMIT_V2) { MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"); return false; @@ -3984,7 +3983,7 @@ bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) cons //------------------------------------------------------------------ void Blockchain::return_tx_to_pool(std::vector> &txs) { - uint8_t version = get_network_version(); + hf version = get_network_version(); for (auto& tx : txs) { cryptonote::tx_verification_context tvc{}; @@ -4035,7 +4034,7 @@ Blockchain::block_pow_verified Blockchain::verify_block_pow(cryptonote::block co if (alt_block) { randomx_longhash_context randomx_context = {}; - if (blk.major_version >= cryptonote::network_version_13_checkpointing) + if (blk.major_version >= hf::hf13_checkpointing) { randomx_context.current_blockchain_height = chain_height; randomx_context.seed_height = rx_seedheight(blk_height); @@ -4112,7 +4111,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block const crypto::hash blk_hash = cryptonote::get_block_hash(blk); const uint64_t blk_height = cryptonote::get_block_height(blk); const uint64_t chain_height = get_current_blockchain_height(); - const uint8_t hf_version = get_network_version(); + const hf hf_version = get_network_version(); if (alt_block) { @@ -4130,10 +4129,10 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block // this is a cheap test // HF19 TODO: remove the requirement that minor_version must be >= network version - if (auto v = get_network_version(blk_height); (v>0) && (blk.major_version != v || blk.minor_version < v)) + if (auto v = get_network_version(blk_height); (v > hf::none) && (blk.major_version != v || (blk.major_version < hf::hf20_bulletproof_plusplus && blk.minor_version < static_cast(v)))) { - LOG_PRINT_L1("Block with id: " << blk_hash << ", has invalid version " << +blk.major_version << "." << +blk.minor_version << - "; current: " << +v << "." << +v << " for height " << blk_height); + LOG_PRINT_L1("Block with id: " << blk_hash << ", has invalid version " << static_cast(blk.major_version) << "." << +blk.minor_version << + "; current: " << static_cast(v) << "." << static_cast(v) << " for height " << blk_height); return false; } } @@ -4172,10 +4171,10 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block } // HF19 TODO: remove the requirement that minor_version must be >= network version - if (required_major_version>0 && (blk.major_version != required_major_version || blk.minor_version < required_major_version)) + if (required_major_version > hf::none && blk.major_version != required_major_version || (blk.major_version < hf::hf20_bulletproof_plusplus && blk.minor_version < static_cast(required_major_version))) { - MGINFO_RED("Block with id: " << blk_hash << ", has invalid version " << +blk.major_version << "." << +blk.minor_version << - "; current: " << +required_major_version << "." << +required_major_version << " for height " << blk_height); + MGINFO_RED("Block with id: " << blk_hash << ", has invalid version " << static_cast(blk.major_version) << "." << +blk.minor_version << + "; current: " << static_cast(required_major_version) << "." << static_cast(required_major_version) << " for height " << blk_height); return false; } @@ -4186,7 +4185,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block bool master_node_checkpoint = false; if(!m_checkpoints.check_block(chain_height, blk_hash, nullptr, &master_node_checkpoint)) { - if (!master_node_checkpoint || (master_node_checkpoint && blk.major_version >= cryptonote::network_version_14_enforce_checkpoints)) + if (!master_node_checkpoint || (master_node_checkpoint && blk.major_version >= hf::hf14_enforce_checkpoints)) { MGINFO_RED("CHECKPOINT VALIDATION FAILED"); return false; @@ -4406,10 +4405,10 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& difficulty_type cumulative_difficulty = current_diffic; // In the "tail" state when the minimum subsidy (implemented in get_block_reward) is in effect, the number of - // coins will eventually exceed MONEY_SUPPLY and overflow a uint64. To prevent overflow, cap already_generated_coins - // at MONEY_SUPPLY. already_generated_coins is only used to compute the block subsidy and MONEY_SUPPLY yields a + // coins will eventually exceed beldex::MONEY_SUPPLY and overflow a uint64. To prevent overflow, cap already_generated_coins + // at beldex::MONEY_SUPPLY. already_generated_coins is only used to compute the block subsidy and beldex::MONEY_SUPPLY yields a // subsidy of 0 under the base formula and therefore the minimum subsidy >0 in the tail state. - already_generated_coins = base_reward < (MONEY_SUPPLY-already_generated_coins) ? already_generated_coins + base_reward : MONEY_SUPPLY; + already_generated_coins = base_reward < (beldex::MONEY_SUPPLY-already_generated_coins) ? already_generated_coins + base_reward : beldex::MONEY_SUPPLY; if(chain_height) cumulative_difficulty += m_db->get_block_cumulative_difficulty(chain_height - 1); @@ -4512,7 +4511,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& { MINFO("+++++ POS BLOCK SUCCESSFULLY ADDED" "\n\tid: " << id << - "\n\tHEIGHT: " << new_height-1 << ", v" << +bl.major_version << '.' << +bl.minor_version << + "\n\tHEIGHT: " << new_height-1 << ", v" << static_cast(bl.major_version) << '.' << +bl.minor_version << "\n\tblock reward: " << print_money(fee_after_penalty + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_after_penalty) << ")" ", coinbase_weight: " << coinbase_weight << ", cumulative weight: " << cumulative_block_weight << @@ -4524,7 +4523,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& MINFO("+++++ MINER BLOCK SUCCESSFULLY ADDED\n" "\n\tid: " << id << "\n\tPoW: " << miner.blk_pow.proof_of_work << - "\n\tHEIGHT: " << new_height - 1 << ", v" << +bl.major_version << '.' << +bl.minor_version << ", difficulty: " << current_diffic << + "\n\tHEIGHT: " << new_height - 1 << ", v" << static_cast(bl.major_version) << '.' << +bl.minor_version << ", difficulty: " << current_diffic << "\n\tblock reward: " << print_money(fee_after_penalty + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_after_penalty) << ")" ", coinbase_weight: " << coinbase_weight << ", cumulative weight: " << cumulative_block_weight << @@ -4582,11 +4581,11 @@ uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) cons const uint64_t db_height = m_db->height(); const uint64_t nblocks = std::min(m_long_term_block_weights_window, db_height); - if (!is_hard_fork_at_least(m_nettype, HF_VERSION_LONG_TERM_BLOCK_WEIGHT, get_current_blockchain_height())) + if (!is_hard_fork_at_least(m_nettype, feature::LONG_TERM_BLOCK_WEIGHT, get_current_blockchain_height())) return block_weight; 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(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); + uint64_t long_term_effective_median_block_weight = std::max(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 long_term_block_weight = std::min(block_weight, short_term_constraint); @@ -4602,13 +4601,13 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti // when we reach this, the last hf version is not yet written to the db const uint64_t db_height = m_db->height(); - const uint8_t hf_version = get_network_version(); + const hf hf_version = get_network_version(); uint64_t full_reward_zone = get_min_block_weight(hf_version); - if (hf_version < HF_VERSION_LONG_TERM_BLOCK_WEIGHT) + if (hf_version < feature::LONG_TERM_BLOCK_WEIGHT) { std::vector weights; - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + get_last_n_blocks_weights(weights, REWARD_BLOCKS_WINDOW); m_current_block_cumul_weight_median = tools::median(std::move(weights)); } else @@ -4618,7 +4617,7 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti uint64_t long_term_median; if (db_height == 1) { - long_term_median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; + long_term_median = BLOCK_GRANTED_FULL_REWARD_ZONE_V5; } else { @@ -4628,7 +4627,7 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti long_term_median = get_long_term_block_weight_median(db_height - nblocks - 1, nblocks); } - m_long_term_effective_median_block_weight = std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); + m_long_term_effective_median_block_weight = std::max(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 long_term_block_weight = std::min(block_weight, short_term_constraint); @@ -4643,13 +4642,13 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti m_long_term_block_weights_cache_rolling_median.insert(long_term_block_weight); long_term_median = m_long_term_block_weights_cache_rolling_median.median(); } - m_long_term_effective_median_block_weight = std::max(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); + m_long_term_effective_median_block_weight = std::max(BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); std::vector weights; - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + get_last_n_blocks_weights(weights, REWARD_BLOCKS_WINDOW); uint64_t short_term_median = tools::median(std::move(weights)); - uint64_t effective_median_block_weight = std::min(std::max(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 = std::min(std::max(BLOCK_GRANTED_FULL_REWARD_ZONE_V5, short_term_median), SHORT_TERM_BLOCK_WEIGHT_SURGE_FACTOR * m_long_term_effective_median_block_weight); m_current_block_cumul_weight_median = effective_median_block_weight; } @@ -4683,9 +4682,9 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc, return false; } - const int hf_version = get_network_version(); + const hf hf_version = get_network_version(); bool const POS_block = cryptonote::block_has_POS_components(bl); - if (hf_version >= network_version_12_security_signature and !POS_block){ + if (hf_version >= hf::hf12_security_signature and !POS_block){ crypto::signature security_signature; const bool has_security_signature = cryptonote::get_security_signature_from_tx_extra(bl.miner_tx.extra, security_signature); @@ -5034,7 +5033,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar { reward = 0; auto hard_fork_version = get_network_version(height); - if (hard_fork_version <= network_version_9_master_nodes) + if (hard_fork_version <= hf::hf9_master_nodes) { return true; } @@ -5046,7 +5045,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar if(height == 742425) { - reward = 8500000000 * COIN; //mint 8.5 billion bdx governance in this block + reward = 850000000 * beldex::COIN; //mint 8.5 billion bdx governance in this block return true; } // Ignore governance reward and payout instead the last @@ -5058,9 +5057,9 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar size_t num_blocks = cryptonote::get_config(nettype()).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; // governance reward starting at HF17 - if (hard_fork_version >= network_version_17_POS) + if (hard_fork_version >= hf::hf17_POS) { - reward = num_blocks * FOUNDATION_REWARD_HF17; + reward = num_blocks * beldex::FOUNDATION_REWARD_HF17; return true; } @@ -5080,7 +5079,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar for (const auto &block : blocks) { - if (block.major_version >= network_version_10_bulletproofs) + if (block.major_version >= hf::hf10_bulletproofs) reward += derive_governance_from_block_reward(nettype(), block, hard_fork_version); } @@ -5594,7 +5593,7 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get if (!checkpoints.empty()) { MINFO("Loading precomputed blocks (" << checkpoints.size() << " bytes)"); - if (m_nettype == MAINNET) + if (m_nettype == network_type::MAINNET) { // first check hash crypto::hash hash; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 55db47f7456..bfffd80be53 100755 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -639,7 +639,7 @@ namespace cryptonote * * @return pair of {per-size, per-output} fees */ - static byte_and_output_fees get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, uint8_t version); + static byte_and_output_fees get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, hf version); /** * @brief get dynamic per kB or byte fee estimate for the next few blocks @@ -801,7 +801,7 @@ namespace cryptonote * * @return the version */ - uint8_t get_network_version(std::optional height = std::nullopt) const; + hf get_network_version(std::optional height = std::nullopt) const; /** * @brief remove transactions from the transaction pool (if present) @@ -1312,7 +1312,7 @@ namespace cryptonote * * @return false if anything is found wrong with the miner transaction, otherwise true */ - bool prevalidate_miner_transaction(const block& b, uint64_t height, uint8_t hf_version); + bool prevalidate_miner_transaction(const block& b, uint64_t height, hf hf_version); /** * @brief validates a miner (coinbase) transaction @@ -1329,7 +1329,7 @@ namespace cryptonote * * @return false if anything is found wrong with the miner transaction, otherwise true */ - bool validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, uint8_t version); + bool validate_miner_transaction(const block& b, size_t cumulative_block_weight, uint64_t fee, uint64_t& base_reward, uint64_t already_generated_coins, hf version); /** * @brief reverts the blockchain to its previous state following a failed switch diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 0ff4213f17b..e4bf0b69849 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -177,7 +177,7 @@ namespace cryptonote static const command_line::arg_descriptor arg_max_txpool_weight = { "max-txpool-weight" , "Set maximum txpool weight in bytes." - , DEFAULT_TXPOOL_MAX_WEIGHT + , DEFAULT_MEMPOOL_MAX_WEIGHT }; static const command_line::arg_descriptor arg_master_node = { "master-node" @@ -272,7 +272,7 @@ namespace cryptonote , m_starter_message_showed(false) , m_target_blockchain_height(0) , m_last_json_checkpoints_update(0) - , m_nettype(UNDEFINED) + , m_nettype(network_type::UNDEFINED) , m_last_storage_server_ping(0) , m_last_belnet_ping(0) , m_pad_transactions(false) @@ -362,11 +362,11 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::handle_command_line(const boost::program_options::variables_map& vm) { - if (m_nettype != FAKECHAIN) + if (m_nettype != network_type::FAKECHAIN) { const bool testnet = command_line::get_arg(vm, arg_testnet_on); const bool devnet = command_line::get_arg(vm, arg_devnet_on); - m_nettype = testnet ? TESTNET : devnet ? DEVNET : MAINNET; + m_nettype = testnet ? network_type::TESTNET : devnet ? network_type::DEVNET : network_type::MAINNET; } m_check_uptime_proof_interval.interval(get_net_config().UPTIME_PROOF_CHECK_INTERVAL); @@ -594,7 +594,7 @@ namespace cryptonote const bool regtest = command_line::get_arg(vm, arg_regtest_on); if (test_options != NULL || regtest) { - m_nettype = FAKECHAIN; + m_nettype = network_type::FAKECHAIN; } bool r = handle_command_line(vm); @@ -620,7 +620,7 @@ namespace cryptonote } auto folder = m_config_folder; - if (m_nettype == FAKECHAIN) + if (m_nettype == network_type::FAKECHAIN) folder /= "fake"; // make sure the data directory exists, and try to lock it @@ -648,7 +648,7 @@ namespace cryptonote uint64_t sync_threshold = 1; #if !defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) // In integration mode, don't delete the DB. This should be explicitly done in the tests. Otherwise the more likely behaviour is persisting the DB across multiple daemons in the same test. - if (m_nettype == FAKECHAIN && !keep_fakechain) + if (m_nettype == network_type::FAKECHAIN && !keep_fakechain) { // reset the db by removing the database file before opening it if (!db->remove_data_file(folder)) @@ -1142,7 +1142,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- void core::parse_incoming_tx_pre(tx_verification_batch_info &tx_info) { - if(tx_info.blob->size() > get_max_tx_size()) + if(tx_info.blob->size() > MAX_TX_SIZE) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_info.blob->size() << ", rejected"); tx_info.tvc.m_verifivation_failed = true; @@ -1196,7 +1196,7 @@ namespace cryptonote if (proofs.size() != 1) return false; const size_t sz = proofs[0].V.size(); - if (sz == 0 || sz > BULLETPROOF_MAX_OUTPUTS) + if (sz == 0 || sz > TX_BULLETPROOF_MAX_OUTPUTS) return false; return true; } @@ -1346,7 +1346,7 @@ namespace cryptonote { // Caller needs to do this around both this *and* parse_incoming_txs //auto lock = incoming_tx_lock(); - uint8_t version = m_blockchain_storage.get_network_version(); + hf version = m_blockchain_storage.get_network_version(); bool ok = true; if (flash_rollback_height) *flash_rollback_height = 0; @@ -1413,7 +1413,7 @@ namespace cryptonote auto &new_flashes = results.first; auto &missing_txs = results.second; - if (m_blockchain_storage.get_network_version() < HF_VERSION_FLASH) + if (m_blockchain_storage.get_network_version() < feature::FLASH) return results; std::vector want(flashes.size(), false); // Really bools, but std::vector is broken. @@ -1624,9 +1624,9 @@ namespace cryptonote } } - if(!keeped_by_block && get_transaction_weight(tx) >= m_blockchain_storage.get_current_cumulative_block_weight_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE) + if(!keeped_by_block && get_transaction_weight(tx) >= m_blockchain_storage.get_current_cumulative_block_weight_limit() - COINBASE_BLOB_RESERVED_SIZE) { - MERROR_VER("tx is too large " << get_transaction_weight(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_cumulative_block_weight_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); + MERROR_VER("tx is too large " << get_transaction_weight(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_cumulative_block_weight_limit() - COINBASE_BLOB_RESERVED_SIZE); return false; } @@ -1723,9 +1723,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- size_t core::get_block_sync_size(uint64_t height) const { - if (block_sync_size > 0) - return block_sync_size; - return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + return block_sync_size > 0 ? block_sync_size : BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } //----------------------------------------------------------------------------------------------- bool core::are_key_images_spent_in_pool(const std::vector& key_im, std::vector &spent) const @@ -1820,8 +1818,8 @@ namespace cryptonote int64_t tx_fee_amount = 0; for(const auto& tx: txs) { - tx_fee_amount += static_cast(get_tx_miner_fee(tx, b.major_version >= HF_VERSION_FEE_BURNING)); - if(b.major_version >= HF_VERSION_FEE_BURNING) + tx_fee_amount += static_cast(get_tx_miner_fee(tx, b.major_version >= feature::FEE_BURNING)); + if(b.major_version >= feature::FEE_BURNING) { burnt_beldex += static_cast(get_burned_amount_from_tx_extra(tx.extra)); } @@ -1867,17 +1865,14 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const { - const uint8_t version = m_blockchain_storage.get_network_version(); - if (version >= 6) + for(const auto& in: tx.vin) { - for(const auto& in: tx.vin) - { - CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, tokey_in, false); - for (size_t n = 1; n < tokey_in.key_offsets.size(); ++n) - if (tokey_in.key_offsets[n] == 0) - return false; - } + CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, tokey_in, false); + for (size_t n = 1; n < tokey_in.key_offsets.size(); ++n) + if (tokey_in.key_offsets[n] == 0) + return false; } + return true; } //----------------------------------------------------------------------------------------------- @@ -2340,7 +2335,7 @@ namespace cryptonote }); } - if (m_nettype != DEVNET) + if (m_nettype != network_type::DEVNET) { if (!check_external_ping(m_last_storage_server_ping, get_net_config().UPTIME_PROOF_FREQUENCY, "the storage server")) { @@ -2461,7 +2456,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::check_block_rate() { - if (m_offline || m_nettype == FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height() || m_target_blockchain_height == 0) + if (m_offline || m_nettype == network_type::FAKECHAIN || m_target_blockchain_height > get_current_blockchain_height() || m_target_blockchain_height == 0) { MDEBUG("Not checking block rate, offline or syncing"); return true; @@ -2474,7 +2469,7 @@ namespace cryptonote const auto hf_version = get_network_version(m_nettype, m_target_blockchain_height); - static double threshold = 1. / ((24h * 10) / (hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)); // one false positive every 10 days + static double threshold = 1. / ((24h * 10) / (hf_version >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)); // one false positive every 10 days static constexpr unsigned int max_blocks_checked = 150; const time_t now = time(NULL); @@ -2486,7 +2481,7 @@ namespace cryptonote unsigned int b = 0; const time_t time_boundary = now - static_cast(seconds[n]); for (time_t ts: timestamps) b += ts >= time_boundary; - const double p = probability(b, seconds[n] / tools::to_seconds((hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD))); + const double p = probability(b, seconds[n] / tools::to_seconds((hf_version >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12))); MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")"); if (p < threshold) { diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index fc3c9fdab0e..7c4606fa9a4 100755 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -133,14 +133,14 @@ namespace cryptonote const uint64_t MASTER_NODE_BASE_REWARD_PERCENTAGE = 95; - uint64_t governance_reward_formula(uint64_t base_reward, uint8_t hf_version) + uint64_t governance_reward_formula(uint64_t base_reward, hf hf_version) { - return hf_version >= network_version_17_POS ? FOUNDATION_REWARD_HF17 : 0;// governance planned at V17 + return hf_version >= hf::hf17_POS ? beldex::FOUNDATION_REWARD_HF17 : 0;// governance planned at V17 } - uint64_t derive_governance_from_block_reward(network_type nettype, const cryptonote::block &block, uint8_t hf_version) + uint64_t derive_governance_from_block_reward(network_type nettype, const cryptonote::block &block, hf hf_version) { - if (hf_version >= network_version_17_POS) + if (hf_version >= hf::hf17_POS) return governance_reward_formula(0, hf_version); uint64_t result = 0; uint64_t mnode_reward = 0; @@ -175,9 +175,9 @@ namespace cryptonote return result; } - bool height_has_governance_output(network_type nettype, uint8_t hard_fork_version, uint64_t height) + bool height_has_governance_output(network_type nettype, hf hard_fork_version, uint64_t height) { - if (hard_fork_version < network_version_17_POS) + if (hard_fork_version < hf::hf17_POS) return false; if(height == 742425) @@ -193,11 +193,11 @@ namespace cryptonote } - uint64_t master_node_reward_formula(uint64_t base_reward, uint8_t hard_fork_version) + uint64_t master_node_reward_formula(uint64_t base_reward, hf hard_fork_version) { return - hard_fork_version >= network_version_17_POS ? MN_REWARD_HF17_POS : - hard_fork_version >= network_version_11_infinite_staking ? (base_reward / 10) * (MASTER_NODE_BASE_REWARD_PERCENTAGE/10) : // 90% of base reward up until HF15's fixed payout + hard_fork_version >= hf::hf17_POS ? beldex::MN_REWARD_HF17_POS : + hard_fork_version >= hf::hf11_infinite_staking ? (base_reward / 10) * (MASTER_NODE_BASE_REWARD_PERCENTAGE/10) : // 90% of base reward up until HF15's fixed payout 0; } @@ -205,7 +205,7 @@ namespace cryptonote { uint64_t hi, lo, rewardhi, rewardlo; lo = mul128(total_master_node_reward, portions, &hi); - div128_64(hi, lo, STAKING_PORTIONS, &rewardhi, &rewardlo); + div128_64(hi, lo, old::STAKING_PORTIONS, &rewardhi, &rewardlo); return rewardlo; } @@ -263,7 +263,7 @@ namespace cryptonote transaction& tx, const beldex_miner_tx_context &miner_tx_context, const blobdata& extra_nonce, - uint8_t hard_fork_version, + hf hard_fork_version, const crypto::signature security_signature) { tx.vin.clear(); @@ -347,7 +347,7 @@ namespace cryptonote size_t rewards_length = 0; std::array rewards = {}; - if (hard_fork_version >= cryptonote::network_version_9_master_nodes) + if (hard_fork_version >= hf::hf9_master_nodes) CHECK_AND_ASSERT_MES(miner_tx_context.block_leader.payouts.size(), false, "Constructing a block leader reward for block but no payout entries specified"); // NOTE: Add Block Producer Reward @@ -356,7 +356,7 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(miner_tx_context.POS_block_producer.payouts.size(), false, "Constructing a reward for block produced by POS but no payout entries specified"); CHECK_AND_ASSERT_MES(miner_tx_context.POS_block_producer.key, false, "Null Key given for POS Block Producer"); - CHECK_AND_ASSERT_MES(hard_fork_version >= cryptonote::network_version_17_POS, false, "POS Block Producer is not valid until HF16, current HF" << hard_fork_version); + CHECK_AND_ASSERT_MES(hard_fork_version >= hf::hf17_POS , false, "POS Block Producer is not valid until HF16, current HF" << static_cast(hard_fork_version)); uint64_t leader_reward = reward_parts.master_node_total; if (miner_tx_context.block_leader.key == miner_tx_context.POS_block_producer.key) @@ -385,23 +385,23 @@ namespace cryptonote if (uint64_t miner_amount = reward_parts.base_miner + reward_parts.miner_fee; miner_amount) rewards[rewards_length++] = {reward_type::miner, miner_tx_context.miner_block_producer, miner_amount}; - if (hard_fork_version >= cryptonote::network_version_9_master_nodes) + if (hard_fork_version >= hf::hf9_master_nodes) { std::vector split_rewards = distribute_reward_by_portions(leader.payouts, reward_parts.master_node_total, - hard_fork_version >= cryptonote::network_version_17_POS /*distribute_remainder*/); + hard_fork_version >= hf::hf17_POS /*distribute_remainder*/); for (size_t i = 0; i < leader.payouts.size(); i++) rewards[rewards_length++] = {reward_type::mnode, leader.payouts[i].address, split_rewards[i]}; } } // NOTE: Add Governance Payout - if (hard_fork_version >= network_version_17_POS && already_generated_coins != 0) + if (hard_fork_version >= hf::hf17_POS && already_generated_coins != 0) { if (reward_parts.governance_paid == 0) { - CHECK_AND_ASSERT_MES(hard_fork_version >= network_version_17_POS, false, "Governance reward can NOT be 0 before hardfork 17, hard_fork_version: " << hard_fork_version); + CHECK_AND_ASSERT_MES(hard_fork_version >= hf::hf17_POS, false, "Governance reward can NOT be 0 after hardfork 17, hard_fork_version: " << static_cast(hard_fork_version)); } else { @@ -441,12 +441,12 @@ namespace cryptonote out.target = tk; out.amount = amount; tx.vout.push_back(out); - tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + tx.output_unlock_times.push_back(height + MINED_MONEY_UNLOCK_WINDOW); summary_amounts += amount; } uint64_t expected_amount = 0; - if (hard_fork_version <= cryptonote::network_version_16) + if (hard_fork_version <= hf::hf16) { // NOTE: Use the amount actually paid out when we split the master node // reward (across up to 4 recipients) which may actually pay out less than @@ -469,21 +469,21 @@ namespace cryptonote CHECK_AND_ASSERT_MES(tx.vout.size() == rewards_length, false, "TX output mis-match with rewards expected: " << rewards_length << ", tx outputs: " << tx.vout.size()); //lock - tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + tx.unlock_time = height + MINED_MONEY_UNLOCK_WINDOW; tx.vin.push_back(txin_gen{height}); tx.invalidate_hashes(); //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) // << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2); - if ((hard_fork_version>=network_version_12_security_signature) && !miner_tx_context.POS) { + if ((hard_fork_version >= hf::hf12_security_signature) && !miner_tx_context.POS) { add_security_signature_to_tx_extra(tx.extra, security_signature); } LOG_PRINT_L2("MINER_TX generated ok"); return true; } - bool get_beldex_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, int hard_fork_version, block_reward_parts &result, const beldex_block_reward_context &beldex_context) + bool get_beldex_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, hf hard_fork_version, block_reward_parts &result, const beldex_block_reward_context &beldex_context) { result = {}; uint64_t base_reward, base_reward_unpenalized; @@ -508,19 +508,19 @@ namespace cryptonote // We base governance fees and MN rewards based on the block reward formula. (Prior to HF13, // however, they were accidentally based on the block reward formula *after* subtracting a // potential penalty if the block producer includes txes beyond the median size limit). - //result.original_base_reward = hard_fork_version >= network_version_14_enforce_checkpoints ? base_reward_unpenalized : base_reward; + //result.original_base_reward = hard_fork_version >= hf::hf14_enforce_checkpoints ? base_reward_unpenalized : base_reward; result.original_base_reward = base_reward; // There is a goverance fee due every block. Beginning in hardfork 10 this is still subtracted // from the block reward as if it was paid, but the actual payments get batched into rare, large // accumulated payments. (Before hardfork 10 they are included in every block, unbatched). result.governance_due = governance_reward_formula(result.original_base_reward, hard_fork_version); - result.governance_paid = hard_fork_version >= network_version_10_bulletproofs + result.governance_paid = hard_fork_version >= hf::hf10_bulletproofs ? beldex_context.batched_governance : result.governance_due; uint64_t const master_node_reward = master_node_reward_formula(result.original_base_reward, hard_fork_version); - if (hard_fork_version < cryptonote::network_version_17_POS) + if (hard_fork_version < hf::hf17_POS) { result.master_node_total = calculate_sum_of_portions(beldex_context.block_leader_payouts, master_node_reward); // The base_miner amount is everything left in the base reward after subtracting off the master @@ -880,7 +880,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(key_image_proofs.proofs.size() >= 1, false, "No key image proofs were generated for staking tx"); add_tx_key_image_proofs_to_tx_extra(tx.extra, key_image_proofs); - if (tx_params.hf_version <= cryptonote::network_version_14_enforce_checkpoints) + if (tx_params.hf_version <= hf::hf14_enforce_checkpoints) tx.type = txtype::standard; } @@ -1131,8 +1131,8 @@ namespace cryptonote // they are now for our fake networks (which we need to do because we no longer have pre-CLSAG // tx generation code). rct::RCTConfig rct_config{ - tx_params.hf_version < network_version_10_bulletproofs ? rct::RangeProofType::Borromean : rct::RangeProofType::PaddedBulletproof, - tx_params.hf_version >= HF_VERSION_CLSAG ? 3 : tx_params.hf_version >= HF_VERSION_SMALLER_BP ? 2 : 1 + tx_params.hf_version < hf::hf10_bulletproofs ? rct::RangeProofType::Borromean : rct::RangeProofType::PaddedBulletproof, + tx_params.hf_version >= feature::CLSAG ? 3 : tx_params.hf_version >= feature::SMALLER_BP ? 2 : 1 }; 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, rct_config, NULL, tx_params); @@ -1148,12 +1148,12 @@ namespace cryptonote std::string tx_bl = oxenc::from_hex(conf.GENESIS_TX); bool r = parse_and_validate_tx_from_blob(tx_bl, bl.miner_tx); CHECK_AND_ASSERT_MES(r, false, "failed to parse coinbase tx from hard coded blob"); - bl.major_version = 1; + bl.major_version = hf::hf1; bl.minor_version = 0; bl.timestamp = 0; bl.nonce = conf.GENESIS_NONCE; miner::find_nonce_for_given_block([](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ - hash = cryptonote::get_block_longhash(UNDEFINED, cryptonote::randomx_longhash_context(NULL, b, height), b, height, threads); + hash = cryptonote::get_block_longhash(network_type::UNDEFINED, cryptonote::randomx_longhash_context(NULL, b, height), b, height, threads); return true; }, bl, 1, 0); bl.invalidate_hashes(); @@ -1163,7 +1163,7 @@ namespace cryptonote crypto::hash get_altblock_longhash(cryptonote::network_type nettype, randomx_longhash_context const &randomx_context, const block& b, uint64_t height) { crypto::hash result = {}; - if (nettype == FAKECHAIN || b.major_version < network_version_13_checkpointing) + if (nettype == network_type::FAKECHAIN || b.major_version < hf::hf13_checkpointing) { result = get_block_longhash(nettype, randomx_context, b, height, 0); } @@ -1181,7 +1181,7 @@ namespace cryptonote const uint64_t height) { *this = {}; - if (b.major_version >= network_version_13_checkpointing) + if (b.major_version >= hf::hf13_checkpointing) { if (pbc) // null only happens when generating genesis block, 0 init randomx is ok { @@ -1196,20 +1196,20 @@ namespace cryptonote { crypto::hash result = {}; const blobdata bd = get_block_hashing_blob(b); - const uint8_t hf_version = b.major_version; + const auto hf_version = b.major_version; #if defined(BELDEX_INTEGRATION_TESTS) miners = 0; #endif crypto::cn_slow_hash_type cn_type = cn_slow_hash_type::heavy_v1; - if (nettype == FAKECHAIN) + if (nettype == network_type::FAKECHAIN) { cn_type = cn_slow_hash_type::turtle_lite_v2; } else { - if (hf_version >= network_version_13_checkpointing) + if (hf_version >= hf::hf13_checkpointing) { rx_slow_hash(randomx_context.current_blockchain_height, randomx_context.seed_height, @@ -1222,9 +1222,9 @@ namespace cryptonote return result; } - if (hf_version >= network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) cn_type = cn_slow_hash_type::turtle_lite_v2; - else if (hf_version >= network_version_7) + else if (hf_version >= hf::hf7) cn_type = crypto::cn_slow_hash_type::heavy_v2; } diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 4afb43be06d..6a24c2e8698 100755 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -42,14 +42,14 @@ namespace cryptonote bool get_deterministic_output_key (const account_public_address& address, const keypair& tx_key, size_t output_index, crypto::public_key& output_key); bool validate_governance_reward_key (uint64_t height, std::string_view governance_wallet_address_str, size_t output_index, const crypto::public_key& output_key, const cryptonote::network_type nettype); - uint64_t governance_reward_formula (uint64_t base_reward, uint8_t hf_version); + uint64_t governance_reward_formula (uint64_t base_reward, hf hf_version); bool block_has_governance_output (network_type nettype, cryptonote::block const &block); - bool height_has_governance_output (network_type nettype, uint8_t hard_fork_version, uint64_t height); - uint64_t derive_governance_from_block_reward (network_type nettype, const cryptonote::block &block, uint8_t hf_version); + bool height_has_governance_output (network_type nettype, hf hard_fork_version, uint64_t height); + uint64_t derive_governance_from_block_reward (network_type nettype, const cryptonote::block &block, hf hf_version); std::vector distribute_reward_by_portions(const std::vector& payout, uint64_t total_reward, bool distribute_remainder); uint64_t get_portion_of_reward (uint64_t portions, uint64_t total_master_node_reward); - uint64_t master_node_reward_formula (uint64_t base_reward, uint8_t hard_fork_version); + uint64_t master_node_reward_formula (uint64_t base_reward, hf hard_fork_version); struct beldex_miner_tx_context { @@ -76,7 +76,7 @@ namespace cryptonote return result; } - network_type nettype = MAINNET; + network_type nettype = network_type::MAINNET; bool POS; // If true, POS_.* varables are set, otherwise miner_block_producer is set, determining who should get the coinbase reward. master_nodes::payout POS_block_producer; // Can be different from the leader in POS if the original leader fails to complete the round, the block producer changes. @@ -95,7 +95,7 @@ namespace cryptonote transaction& tx, const beldex_miner_tx_context &miner_context, const blobdata& extra_nonce = blobdata(), - uint8_t hard_fork_version = 1, + hf hard_fork_version = hf::hf1, const crypto::signature security_signature={} ); struct block_reward_parts @@ -129,7 +129,7 @@ namespace cryptonote // cryptonote_core since it would have a circular dependency on Blockchain // NOTE: Block reward function that should be called after hard fork v10 - bool get_beldex_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, int hard_fork_version, block_reward_parts &result, const beldex_block_reward_context &beldex_context); + bool get_beldex_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, hf hard_fork_version, block_reward_parts &result, const beldex_block_reward_context &beldex_context); struct tx_source_entry { @@ -206,7 +206,7 @@ namespace cryptonote struct beldex_construct_tx_params { - uint8_t hf_version = cryptonote::network_version_7; + hf hf_version = hf::hf7; txtype tx_type = txtype::standard; // Can be set to non-zero values to have the tx be constructed specifying required burn amounts diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index 8d467dbdc85..7aa4cd534a2 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "cryptonote_config.h" +#include "beldex_economy.h" #include "ringct/rctTypes.h" #include #include @@ -65,18 +66,20 @@ extern "C" { #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "master_nodes" +using cryptonote::hf; + namespace master_nodes { size_t constexpr STORE_LONG_TERM_STATE_INTERVAL = 10000; constexpr auto X25519_MAP_PRUNING_INTERVAL = 5min; constexpr auto X25519_MAP_PRUNING_LAG = 24h; - static_assert(X25519_MAP_PRUNING_LAG > config::UPTIME_PROOF_VALIDITY, "x25519 map pruning lag is too short!"); + static_assert(X25519_MAP_PRUNING_LAG > cryptonote::config::UPTIME_PROOF_VALIDITY, "x25519 map pruning lag is too short!"); - static uint64_t short_term_state_cull_height(uint8_t hf_version, uint64_t block_height) + static uint64_t short_term_state_cull_height(hf hf_version, uint64_t block_height) { size_t constexpr DEFAULT_SHORT_TERM_STATE_HISTORY = 6 * STATE_CHANGE_TX_LIFETIME_IN_BLOCKS; - static_assert(DEFAULT_SHORT_TERM_STATE_HISTORY >= 12 * BLOCKS_PER_HOUR, // Arbitrary, but raises a compilation failure if it gets shortened. + static_assert(DEFAULT_SHORT_TERM_STATE_HISTORY >= 12 * cryptonote::BLOCKS_PER_HOUR, // Arbitrary, but raises a compilation failure if it gets shortened. "not enough short term state storage for blink quorum retrieval!"); uint64_t result = (block_height < DEFAULT_SHORT_TERM_STATE_HISTORY) ? 0 : block_height - DEFAULT_SHORT_TERM_STATE_HISTORY; @@ -93,7 +96,7 @@ namespace master_nodes void master_node_list::init() { std::lock_guard lock(m_mn_mutex); - if (m_blockchain.get_network_version() < cryptonote::network_version_9_master_nodes) + if (m_blockchain.get_network_version() < hf::hf9_master_nodes) { reset(true); return; @@ -327,15 +330,15 @@ namespace master_nodes return result; } - void validate_contributor_args(uint8_t hf_version, contributor_args_t const &contributor_args) + void validate_contributor_args(hf hf_version, contributor_args_t const &contributor_args) { if (contributor_args.portions.empty()) throw invalid_contributions{"No portions given"}; if (contributor_args.portions.size() != contributor_args.addresses.size()) throw invalid_contributions{"Number of portions (" + std::to_string(contributor_args.portions.size()) + ") doesn't match the number of addresses (" + std::to_string(contributor_args.portions.size()) + ")"}; - if (contributor_args.portions.size() > MAX_NUMBER_OF_CONTRIBUTORS) + if (contributor_args.portions.size() > beldex::MAX_NUMBER_OF_CONTRIBUTORS) throw invalid_contributions{"Too many contributors"}; - if (contributor_args.portions_for_operator > STAKING_PORTIONS) + if (contributor_args.portions_for_operator > cryptonote::old::STAKING_PORTIONS) throw invalid_contributions{"Operator portions are too high"}; if (!check_master_node_portions(hf_version, contributor_args.portions)) @@ -436,7 +439,7 @@ namespace master_nodes } bool tx_get_staking_components_and_amounts(cryptonote::network_type nettype, - uint8_t hf_version, + hf hf_version, cryptonote::transaction const &tx, uint64_t block_height, staking_components *contribution) @@ -471,7 +474,7 @@ namespace master_nodes hw::device &hwdev = hw::get_device("default"); contribution->transferred = 0; bool stake_decoded = true; - if (hf_version >= cryptonote::network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) { // In Infinite Staking, we lock the key image that would be generated if // you tried to send your stake and prevent it from being transacted on @@ -552,7 +555,7 @@ namespace master_nodes } } - if (hf_version < cryptonote::network_version_11_infinite_staking) + if (hf_version < hf::hf11_infinite_staking) { // Pre Infinite Staking, we only need to prove the amount sent is // sufficient to become a contributor to the Master Node and that there @@ -566,7 +569,7 @@ namespace master_nodes unlock_time = tx.output_unlock_times[i]; uint64_t min_height = block_height + staking_num_lock_blocks(nettype,hf_version); - has_correct_unlock_time = unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && unlock_time >= min_height; + has_correct_unlock_time = unlock_time < cryptonote::MAX_BLOCK_NUMBER && unlock_time >= min_height; } if (has_correct_unlock_time) @@ -599,7 +602,7 @@ namespace master_nodes if (tx.type != cryptonote::txtype::state_change) return false; - uint8_t const hf_version = block.major_version; + const auto hf_version = block.major_version; cryptonote::tx_extra_master_node_state_change state_change; if (!cryptonote::get_master_node_state_change_from_tx_extra(tx.extra, state_change, hf_version)) { @@ -670,7 +673,7 @@ namespace master_nodes else LOG_PRINT_L1("Deregistration for master node: " << key); - if (hf_version >= cryptonote::network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) { for (const auto &contributor : info.contributors) { @@ -689,7 +692,7 @@ namespace master_nodes return true; case new_state::decommission: - if (hf_version < cryptonote::network_version_13_checkpointing) { + if (hf_version < hf::hf13_checkpointing) { MERROR("Invalid decommission transaction seen before network v12"); return false; } @@ -710,7 +713,7 @@ namespace master_nodes info.last_decommission_reason_consensus_any = state_change.reason_consensus_any; info.decommission_count++; - if (hf_version >= cryptonote::network_version_14_enforce_checkpoints) { + if (hf_version >= hf::hf14_enforce_checkpoints) { // Assigning invalid swarm id effectively kicks the node off // its current swarm; it will be assigned a new swarm id when it // gets recommissioned. Prior to HF13 this step was incorrectly @@ -727,7 +730,7 @@ namespace master_nodes return true; case new_state::recommission: { - if (hf_version < cryptonote::network_version_13_checkpointing) { + if (hf_version < hf::hf13_checkpointing) { MERROR("Invalid recommission transaction seen before network v12"); return false; } @@ -772,7 +775,7 @@ namespace master_nodes return true; } case new_state::ip_change_penalty: - if (hf_version < cryptonote::network_version_13_checkpointing) { + if (hf_version < hf::hf13_checkpointing) { MERROR("Invalid ip_change_penalty transaction seen before network v12"); return false; } @@ -801,7 +804,7 @@ namespace master_nodes } } - bool master_node_list::state_t::process_key_image_unlock_tx(cryptonote::network_type nettype, uint64_t block_height, const cryptonote::transaction &tx,uint8_t version) + bool master_node_list::state_t::process_key_image_unlock_tx(cryptonote::network_type nettype, uint64_t block_height, const cryptonote::transaction &tx,hf version) { crypto::public_key mnode_key; if (!cryptonote::get_master_node_pubkey_from_tx_extra(tx.extra, mnode_key)) @@ -839,9 +842,9 @@ namespace master_nodes if (cit != contributor.locked_contributions.end()) { // NOTE(beldex): This should be checked in blockchain check_tx_inputs already - if(version >= cryptonote::network_version_18_bns) + if(version >= hf::hf18_bns) { - if (cit->amount < (master_nodes::SMALL_CONTRIBUTOR_THRESHOLD*COIN) && (block_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER) + if (cit->amount < (master_nodes::SMALL_CONTRIBUTOR_THRESHOLD * beldex::COIN) && (block_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER) { LOG_PRINT_L1("Unlock TX: small contributor trying to unlock node before " << std::to_string(master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER) @@ -875,7 +878,7 @@ namespace master_nodes return *it->second; } - bool is_registration_tx(cryptonote::network_type nettype, uint8_t hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, master_node_info& info) + bool is_registration_tx(cryptonote::network_type nettype, hf hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, master_node_info& info) { contributor_args_t contributor_args = {}; crypto::public_key master_node_key; @@ -917,7 +920,7 @@ namespace master_nodes return false; } - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) { // In HF16 we start enforcing three things that were always done but weren't actually enforced: // 1. the staked amount in the tx must be a single output. @@ -954,10 +957,10 @@ namespace master_nodes // Don't need this check for HF16+ because the number of reserved spots is already checked in // the registration details, and we disallow a non-operator registration. - if (total_num_of_addr > MAX_NUMBER_OF_CONTRIBUTORS) + if (total_num_of_addr > beldex::MAX_NUMBER_OF_CONTRIBUTORS) { LOG_PRINT_L1("Register TX: Number of participants: " << total_num_of_addr << - " exceeded the max number of contributors: " << MAX_NUMBER_OF_CONTRIBUTORS << + " exceeded the max number of contributors: " << beldex::MAX_NUMBER_OF_CONTRIBUTORS << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx)); return false; @@ -978,7 +981,7 @@ namespace master_nodes info.swarm_id = UNASSIGNED_SWARM_ID; info.last_ip_change_height = block_height; - if(hf_version >= cryptonote::network_version_18_bns) + if(hf_version >= hf::hf18_bns) info.recommission_credit = DECOMMISSION_INITIAL_CREDIT_V18; for (size_t i = 0; i < contributor_args.addresses.size(); i++) @@ -993,7 +996,7 @@ namespace master_nodes uint64_t hi, lo, resulthi, resultlo; lo = mul128(info.staking_requirement, contributor_args.portions[i], &hi); - div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo); + div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); info.contributors.emplace_back(); auto &contributor = info.contributors.back(); @@ -1005,7 +1008,7 @@ namespace master_nodes // In HF16 we require that the amount staked in the registration tx be at least the amount // reserved for the operator. Before HF16 it only had to be >= 25%, even if the operator // reserved amount was higher (though wallets would never actually do this). - if (hf_version >= cryptonote::network_version_17_POS && stake.transferred < info.contributors[0].reserved) + if (hf_version >= hf::hf17_POS && stake.transferred < info.contributors[0].reserved) { LOG_PRINT_L1("Register TX rejected: TX does not have sufficient operator stake"); return false; @@ -1016,7 +1019,7 @@ namespace master_nodes bool master_node_list::state_t::process_registration_tx(cryptonote::network_type nettype, const cryptonote::block &block, const cryptonote::transaction& tx, uint32_t index, const master_node_keys *my_keys) { - uint8_t const hf_version = block.major_version; + const auto hf_version = block.major_version; uint64_t const block_timestamp = block.timestamp; uint64_t const block_height = cryptonote::get_block_height(block); @@ -1026,7 +1029,7 @@ namespace master_nodes if (!is_registration_tx(nettype, hf_version, tx, block_timestamp, block_height, index, key, info)) return false; - if (hf_version >= cryptonote::network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) { // NOTE(beldex): Grace period is not used anymore with infinite staking. So, if someone somehow reregisters, we just ignore it const auto iter = master_nodes_infos.find(key); @@ -1053,7 +1056,7 @@ namespace master_nodes const auto iter = master_nodes_infos.find(key); if (iter != master_nodes_infos.end()) { - if (hf_version >= cryptonote::network_version_10_bulletproofs) + if (hf_version >= hf::hf10_bulletproofs) { master_node_info const &old_info = *iter->second; uint64_t expiry_height = old_info.registration_height + staking_num_lock_blocks(nettype,hf_version); @@ -1095,7 +1098,7 @@ namespace master_nodes bool master_node_list::state_t::process_contribution_tx(cryptonote::network_type nettype, const cryptonote::block &block, const cryptonote::transaction& tx, uint32_t index) { uint64_t const block_height = cryptonote::get_block_height(block); - uint8_t const hf_version = block.major_version; + const auto hf_version = block.major_version; staking_components stake = {}; if (!tx_get_staking_components_and_amounts(nettype, hf_version, tx, block_height, &stake)) @@ -1151,7 +1154,7 @@ namespace master_nodes other_reservations++; } - if (hf_version >= cryptonote::network_version_17_POS && stake.locked_contributions.size() != 1) + if (hf_version >= hf::hf17_POS && stake.locked_contributions.size() != 1) { // Nothing has ever created stake txes with multiple stake outputs, but we start enforcing // that in HF16. @@ -1162,20 +1165,20 @@ namespace master_nodes // Check node contributor counts { bool too_many_contributions = false; - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) // Before HF16 we didn't properly take into account unfilled reservation spots - too_many_contributions = existing_contributions + other_reservations + 1 > MAX_NUMBER_OF_CONTRIBUTORS; - else if (hf_version >= cryptonote::network_version_11_infinite_staking) + too_many_contributions = existing_contributions + other_reservations + 1 > beldex::MAX_NUMBER_OF_CONTRIBUTORS; + else if (hf_version >= hf::hf11_infinite_staking) // As of HF11 we allow up to 4 stakes total (except for the loophole closed above) - too_many_contributions = existing_contributions + stake.locked_contributions.size() > MAX_NUMBER_OF_CONTRIBUTORS; + too_many_contributions = existing_contributions + stake.locked_contributions.size() > beldex::MAX_NUMBER_OF_CONTRIBUTORS; else // Before HF11 we allowed up to 4 contributors, but each can contribute multiple times - too_many_contributions = new_contributor && contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS; + too_many_contributions = new_contributor && contributors.size() >= beldex::MAX_NUMBER_OF_CONTRIBUTORS; if (too_many_contributions) { LOG_PRINT_L1("TX: Already hit the max number of contributions: " - << MAX_NUMBER_OF_CONTRIBUTORS + << beldex::MAX_NUMBER_OF_CONTRIBUTORS << " for contributor: " << cryptonote::get_account_address_as_str(nettype, false, stake.address) << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx)); return false; @@ -1184,11 +1187,11 @@ namespace master_nodes // Check that the contribution is large enough uint64_t min_contribution; - if (!new_contributor && hf_version < cryptonote::network_version_11_infinite_staking) + if (!new_contributor && hf_version < hf::hf11_infinite_staking) { // Follow-up contributions from an existing contributor could be any size before HF11 min_contribution = 1; } - else if (hf_version < cryptonote::network_version_17_POS) + else if (hf_version < hf::hf17_POS) { // The implementation before HF16 was a bit broken w.r.t. properly handling reserved amounts min_contribution = get_min_node_contribution(hf_version, curinfo.staking_requirement, curinfo.total_reserved, existing_contributions); @@ -1249,7 +1252,7 @@ namespace master_nodes info.last_reward_block_height = block_height; info.last_reward_transaction_index = index; - if (hf_version >= cryptonote::network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) for (const auto &contribution : stake.locked_contributions) contributor.locked_contributions.push_back(contribution); @@ -1342,7 +1345,7 @@ namespace master_nodes // TODO(doyle): Core tests need to generate coherent timestamps with // POS. So we relax the rules here for now. - if (nettype != cryptonote::FAKECHAIN) + if (nettype != cryptonote::network_type::FAKECHAIN) { auto round_begin_timestamp = timings.r0_timestamp + (block.POS.round * POS_ROUND_TIME); auto round_end_timestamp = round_begin_timestamp + POS_ROUND_TIME; @@ -1476,7 +1479,7 @@ namespace master_nodes void master_node_list::verify_block(const cryptonote::block &block, bool alt_block, cryptonote::checkpoint_t const *checkpoint) { - if (block.major_version < cryptonote::network_version_9_master_nodes) + if (block.major_version < hf::hf9_master_nodes) return; std::string_view block_type = alt_block ? "alt block "sv : "block "sv; @@ -1484,7 +1487,7 @@ namespace master_nodes // // NOTE: Verify the checkpoint given on this height that locks in a block in the past. // - if (block.major_version >= cryptonote::network_version_14_enforce_checkpoints && checkpoint) + if (block.major_version >= hf::hf14_enforce_checkpoints && checkpoint) { std::vector> alt_quorums; std::shared_ptr quorum = get_quorum(quorum_type::checkpointing, checkpoint->height, false, alt_block ? &alt_quorums : nullptr); @@ -1516,7 +1519,7 @@ namespace master_nodes // POS::timings timings = {}; uint64_t height = cryptonote::get_block_height(block); - if (block.major_version >= cryptonote::network_version_17_POS) + if (block.major_version >= hf::hf17_POS) { uint64_t prev_timestamp = 0; if (alt_block) @@ -1546,7 +1549,7 @@ namespace master_nodes // std::shared_ptr POS_quorum; std::vector> alt_POS_quorums; - bool POS_hf = block.major_version >= cryptonote::network_version_17_POS; + bool POS_hf = block.major_version >= hf::hf17_POS; if (POS_hf) { @@ -1556,7 +1559,7 @@ namespace master_nodes alt_block ? &alt_POS_quorums : nullptr); } - if (m_blockchain.nettype() != cryptonote::FAKECHAIN) + if (m_blockchain.nettype() != cryptonote::network_type::FAKECHAIN) { // TODO(doyle): Core tests don't generate proper timestamps for detecting // timeout yet. So we don't do a timeout check and assume all blocks @@ -1602,7 +1605,7 @@ namespace master_nodes void master_node_list::block_add(const cryptonote::block& block, const std::vector& txs, cryptonote::checkpoint_t const *checkpoint) { - if (block.major_version < cryptonote::network_version_9_master_nodes) + if (block.major_version < hf::hf9_master_nodes) return; std::lock_guard lock(m_mn_mutex); @@ -1617,8 +1620,8 @@ namespace master_nodes bool newest_block = m_blockchain.get_current_blockchain_height() == (block_height + 1); auto now = POS::clock::now().time_since_epoch(); - auto earliest_time = std::chrono::seconds(block.timestamp) - (block.major_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD); - auto latest_time = std::chrono::seconds(block.timestamp) + (block.major_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD); + auto earliest_time = std::chrono::seconds(block.timestamp) - (block.major_version >= hf::hf17_POS ? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12); + auto latest_time = std::chrono::seconds(block.timestamp) + (block.major_version >= hf::hf17_POS ? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12); if (newest_block && (now >= earliest_time && now <= latest_time)) { @@ -1638,10 +1641,10 @@ namespace master_nodes } } - static std::mt19937_64 quorum_rng(uint8_t hf_version, crypto::hash const &hash, quorum_type type) + static std::mt19937_64 quorum_rng(hf hf_version, crypto::hash const &hash, quorum_type type) { std::mt19937_64 result; - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) { std::array src = {static_cast(type)}; std::memcpy(&src[1], &hash, sizeof(hash)); @@ -1662,7 +1665,7 @@ namespace master_nodes } static std::vector generate_shuffled_master_node_index_list( - uint8_t hf_version, + hf hf_version, size_t list_size, crypto::hash const &block_hash, quorum_type type, @@ -1710,7 +1713,7 @@ namespace master_nodes { cryptonote::block const &block = *it; crypto::hash hash = {}; - if (block.major_version >= cryptonote::network_version_17_POS && + if (block.major_version >= hf::hf17_POS && cryptonote::block_has_POS_components(block)) { std::array src = {POS_round}; @@ -1795,7 +1798,7 @@ namespace master_nodes master_nodes::quorum generate_POS_quorum(cryptonote::network_type nettype, crypto::public_key const &block_leader, - uint8_t hf_version, + hf hf_version, std::vector const &active_mnode_list, std::vector const &POS_entropy, uint8_t POS_round) @@ -1874,7 +1877,7 @@ namespace master_nodes return result; } - static void generate_other_quorums(master_node_list::state_t &state, std::vector const &active_mnode_list, cryptonote::network_type nettype, uint8_t hf_version) + static void generate_other_quorums(master_node_list::state_t &state, std::vector const &active_mnode_list, cryptonote::network_type nettype, hf hf_version) { assert(state.block_hash != crypto::null_hash); @@ -1883,7 +1886,7 @@ namespace master_nodes // (i.e. the nodes to be tested) also include decommissioned master nodes. (Prior to v12 there // are no decommissioned nodes, so this distinction is irrelevant for network concensus). std::vector decomm_mnode_list; - if (hf_version >= cryptonote::network_version_13_checkpointing) + if (hf_version >= hf::hf13_checkpointing) decomm_mnode_list = state.decommissioned_master_nodes_infos(); quorum_type const max_quorum_type = max_quorum_type_for_hf(hf_version); @@ -1921,7 +1924,7 @@ namespace master_nodes size_t total_nodes = active_mnode_list.size(); // TODO(beldex): Soft fork, remove when testnet gets reset - if (nettype == cryptonote::TESTNET && state.height < 85357) + if (nettype == cryptonote::network_type::TESTNET && state.height < 85357) total_nodes = active_mnode_list.size() + decomm_mnode_list.size(); if (total_nodes >= CHECKPOINT_QUORUM_SIZE) @@ -2004,7 +2007,7 @@ namespace master_nodes assert(height == block_height); quorums = {}; block_hash = cryptonote::get_block_hash(block); - uint8_t const hf_version = block.major_version; + const auto hf_version = block.major_version; // // Generate POS Quorum before any MN changes are applied to the list because, @@ -2013,7 +2016,7 @@ namespace master_nodes // i.e. before any deregistrations, registrations, decommissions, recommissions. // crypto::public_key winner_pubkey = cryptonote::get_master_node_winner_from_tx_extra(block.miner_tx.extra); - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) { std::vector entropy = get_POS_entropy_for_next_block(db, block.prev_id, block.POS.round); quorum POS_quorum = generate_POS_quorum(nettype, winner_pubkey, hf_version, active_master_nodes_infos(), entropy, block.POS.round); @@ -2036,7 +2039,7 @@ namespace master_nodes // // Remove expired blacklisted key images // - if (hf_version >= cryptonote::network_version_11_infinite_staking) + if (hf_version >= hf::hf11_infinite_staking) { for (auto entry = key_image_blacklist.begin(); entry != key_image_blacklist.end();) { @@ -2132,9 +2135,9 @@ namespace master_nodes void master_node_list::process_block(const cryptonote::block& block, const std::vector& txs) { uint64_t block_height = cryptonote::get_block_height(block); - uint8_t hf_version = block.major_version; + auto hf_version = block.major_version; - if (hf_version < cryptonote::network_version_9_master_nodes) + if (hf_version < hf::hf9_master_nodes) return; // Cull old history @@ -2226,7 +2229,7 @@ namespace master_nodes std::vector master_node_list::state_t::get_expired_nodes(cryptonote::BlockchainDB const &db, cryptonote::network_type nettype, - uint8_t hf_version, + cryptonote::hf hf_version, uint64_t block_height) const { std::vector expired_nodes; @@ -2234,7 +2237,7 @@ namespace master_nodes // TODO(beldex): This should really use the registration height instead of getting the block and expiring nodes. // But there's something subtly off when using registration height causing syncing problems. - if (hf_version == cryptonote::network_version_9_master_nodes) + if (hf_version == hf::hf9_master_nodes) { if (block_height <= lock_blocks) return expired_nodes; @@ -2251,7 +2254,7 @@ namespace master_nodes return expired_nodes; } - if (block.major_version < cryptonote::network_version_9_master_nodes) + if (block.major_version < hf::hf9_master_nodes) return expired_nodes; for (crypto::hash const &hash : block.tx_hashes) @@ -2266,7 +2269,7 @@ namespace master_nodes uint32_t index = 0; crypto::public_key key; master_node_info info = {}; - if (is_registration_tx(nettype, cryptonote::network_version_9_master_nodes, tx, block.timestamp, expired_nodes_block_height, index, key, info)) + if (is_registration_tx(nettype, hf::hf9_master_nodes, tx, block.timestamp, expired_nodes_block_height, index, key, info)) expired_nodes.push_back(key); index++; } @@ -2278,7 +2281,7 @@ namespace master_nodes { crypto::public_key const &mnode_key = it->first; const master_node_info &info = *it->second; - if (info.registration_hf_version >= cryptonote::network_version_11_infinite_staking) + if (info.registration_hf_version >= hf::hf11_infinite_staking) { if (info.requested_unlock_height != KEY_IMAGE_AWAITING_UNLOCK_HEIGHT && block_height > info.requested_unlock_height) expired_nodes.push_back(mnode_key); @@ -2289,7 +2292,7 @@ namespace master_nodes /// registered in hardfork 9 and was scheduled for deregistration in hardfork 10 /// will have its life is slightly prolonged by the "grace period", although it might /// look like we use the registration height to determine the expiry height. - uint64_t node_expiry_height = info.registration_height + lock_blocks + STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS; + uint64_t node_expiry_height = info.registration_height + lock_blocks + cryptonote::old::STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS; if (block_height > node_expiry_height) expired_nodes.push_back(mnode_key); } @@ -2381,8 +2384,8 @@ namespace master_nodes { const auto& block = info.block; const auto& reward_parts = info.reward_parts; - uint8_t const hf_version = block.major_version; - if (hf_version < cryptonote::network_version_9_master_nodes) + const auto hf_version = block.major_version; + if (hf_version < hf::hf9_master_nodes) return; std::lock_guard lock(m_mn_mutex); @@ -2481,7 +2484,7 @@ namespace master_nodes throw std::runtime_error{fmt::format("Expected {} block, the miner TX specifies a different amount of outputs vs the expected: {}, miner tx outputs: {}",type ,expected_vouts_size , miner_tx.vout.size())}; } - if (hf_version >= cryptonote::network_version_17_POS) + if (hf_version >= hf::hf17_POS) { if (reward_parts.base_miner != 0) { @@ -2503,7 +2506,7 @@ namespace master_nodes std::vector split_rewards = cryptonote::distribute_reward_by_portions(block_leader.payouts, reward_parts.master_node_total, - hf_version >= cryptonote::network_version_17_POS /*distribute_remainder*/); + hf_version >= hf::hf17_POS /*distribute_remainder*/); for (size_t i = 0; i < block_leader.payouts.size(); i++) { @@ -2579,7 +2582,7 @@ namespace master_nodes // store into the alt-chain until it gathers enough blocks to cause // a reorganization (more checkpoints/PoW than the main chain). auto& block = info.block; - if (block.major_version < cryptonote::network_version_9_master_nodes) + if (block.major_version < hf::hf9_master_nodes) return; uint64_t block_height = cryptonote::get_block_height(block); @@ -2628,7 +2631,7 @@ namespace master_nodes ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - static master_node_list::quorum_for_serialization serialize_quorum_state(uint8_t hf_version, uint64_t height, quorum_manager const &quorums) + static master_node_list::quorum_for_serialization serialize_quorum_state(hf hf_version, uint64_t height, quorum_manager const &quorums) { master_node_list::quorum_for_serialization result = {}; result.height = height; @@ -2637,7 +2640,7 @@ namespace master_nodes return result; } - static master_node_list::state_serialized serialize_master_node_state_object(uint8_t hf_version, master_node_list::state_t const &state, bool only_serialize_quorums = false) + static master_node_list::state_serialized serialize_master_node_state_object(hf hf_version, master_node_list::state_t const &state, bool only_serialize_quorums = false) { master_node_list::state_serialized result = {}; result.version = master_node_list::state_serialized::get_version(hf_version); @@ -2662,8 +2665,8 @@ namespace master_nodes if (!m_blockchain.has_db()) return false; // Haven't been initialized yet - uint8_t hf_version = m_blockchain.get_network_version(); - if (hf_version < cryptonote::network_version_9_master_nodes) + auto hf_version = m_blockchain.get_network_version(); + if (hf_version < hf::hf9_master_nodes) return true; data_for_serialization *data[] = {&m_transient.cache_long_term_data, &m_transient.cache_short_term_data}; @@ -2903,8 +2906,8 @@ namespace master_nodes bool master_node_list::handle_uptime_proof(cryptonote::NOTIFY_UPTIME_PROOF::request const &proof, bool &my_uptime_proof_confirmation, crypto::x25519_public_key &x25519_pkey) { auto vers = get_network_version_revision(m_blockchain.nettype(), m_blockchain.get_current_blockchain_height()); - if (vers >= std::pair{cryptonote::network_version_17_POS, 1}) - REJECT_PROOF("Old format (non-bt) proofs are not acceptable from v18+1 onwards"); + if (vers >= std::make_pair(hf::hf17_POS, 1)) + REJECT_PROOF("Old format (non-bt) proofs are not acceptable from v17+1 onwards"); auto& netconf = get_config(m_blockchain.nettype()); auto now = std::chrono::system_clock::now(); @@ -2916,7 +2919,7 @@ namespace master_nodes for (auto const &min : MIN_UPTIME_PROOF_VERSIONS) if (vers >= min.hardfork_revision && proof.mnode_version < min.beldexd) - REJECT_PROOF("v" << tools::join(".", min.beldexd) << "+ beldexd version is required for v" << +vers.first << "." << +vers.second << "+ network proofs"); + REJECT_PROOF("v" << tools::join(".", min.beldexd) << "+ beldexd version is required for v" << static_cast(vers.first) << "." << +vers.second << "+ network proofs"); if (!debug_allow_local_ips && !epee::net_utils::is_ip_public(proof.public_ip)) REJECT_PROOF("public_ip is not actually public"); @@ -3010,11 +3013,11 @@ namespace master_nodes for (auto const &min : MIN_UPTIME_PROOF_VERSIONS) { if (vers >= min.hardfork_revision) { if (proof->version < min.beldexd) - REJECT_PROOF("v" << tools::join(".", min.beldexd) << "+ beldexd version is required for v" << +vers.first << "." << +vers.second << "+ network proofs"); + REJECT_PROOF("v" << tools::join(".", min.beldexd) << "+ beldexd version is required for v" << static_cast(vers.first) << "." << +vers.second << "+ network proofs"); if (proof->belnet_version < min.belnet) - REJECT_PROOF("v" << tools::join(".", min.belnet) << "+ belnet version is required for v" << +vers.first << "." << +vers.second << "+ network proofs"); + REJECT_PROOF("v" << tools::join(".", min.belnet) << "+ belnet version is required for v" << static_cast(vers.first) << "." << +vers.second << "+ network proofs"); if (proof->storage_server_version < min.storage_server) - REJECT_PROOF("v" << tools::join(".", min.storage_server) << "+ storage server version is required for v" << +vers.first << "." << +vers.second << "+ network proofs"); + REJECT_PROOF("v" << tools::join(".", min.storage_server) << "+ storage server version is required for v" << static_cast(vers.first) << "." << +vers.second << "+ network proofs"); } } @@ -3241,7 +3244,7 @@ namespace master_nodes std::optional proof_info::reachable_stats::reachable(const std::chrono::steady_clock::time_point& now) const { if (last_reachable >= last_unreachable) return true; - if (last_unreachable > now - config::REACHABLE_MAX_FAILURE_VALIDITY) + if (last_unreachable > now - cryptonote::config::REACHABLE_MAX_FAILURE_VALIDITY) return false; // Last result was a failure, but it was a while ago, so we don't know for sure that it isn't // reachable now: @@ -3557,7 +3560,7 @@ namespace master_nodes m_blockchain.get_db().clear_master_node_data(); } - m_state.height = hard_fork_begins(m_blockchain.nettype(), cryptonote::network_version_9_master_nodes).value_or(1) - 1; + m_state.height = hard_fork_begins(m_blockchain.nettype(), hf::hf9_master_nodes).value_or(1) - 1; } size_t master_node_info::total_num_locked_contributions() const @@ -3571,7 +3574,7 @@ namespace master_nodes contributor_args_t convert_registration_args(cryptonote::network_type nettype, const std::vector &args, uint64_t staking_requirement, - uint8_t hf_version) + hf hf_version) { contributor_args_t result = {}; if (args.size() % 2 == 0 || args.size() < 3) @@ -3580,24 +3583,24 @@ namespace master_nodes return result; } - if ((args.size()-1)/ 2 > MAX_NUMBER_OF_CONTRIBUTORS) + if ((args.size()-1)/ 2 > beldex::MAX_NUMBER_OF_CONTRIBUTORS) { - result.err_msg = tr("Exceeds the maximum number of contributors, which is ") + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS); + result.err_msg = tr("Exceeds the maximum number of contributors, which is ") + std::to_string(beldex::MAX_NUMBER_OF_CONTRIBUTORS); return result; } try { result.portions_for_operator = boost::lexical_cast(args[0]); - if (result.portions_for_operator > STAKING_PORTIONS) + if (result.portions_for_operator > cryptonote::old::STAKING_PORTIONS) { - result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(STAKING_PORTIONS); + result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(cryptonote::old::STAKING_PORTIONS); return result; } } catch (const std::exception &e) { - result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(STAKING_PORTIONS); + result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(cryptonote::old::STAKING_PORTIONS); return result; } @@ -3649,8 +3652,8 @@ namespace master_nodes // This is temporary code to redistribute the insufficient portion dust // amounts between contributors. It should be removed in HF12. // - std::array excess_portions; - std::array min_contributions; + std::array excess_portions; + std::array min_contributions; { // NOTE: Calculate excess portions from each contributor uint64_t beldex_reserved = 0; @@ -3670,7 +3673,7 @@ namespace master_nodes } } - uint64_t portions_left = STAKING_PORTIONS; + uint64_t portions_left = cryptonote::old::STAKING_PORTIONS; uint64_t total_reserved = 0; for (size_t i = 0; i < addr_to_portions.size(); ++i) { @@ -3684,7 +3687,7 @@ namespace master_nodes // the minimum by a dust amount. uint64_t needed = min_portions - addr_to_portion.portions; const uint64_t FUDGE_FACTOR = 10; - const uint64_t DUST_UNIT = (STAKING_PORTIONS / staking_requirement); + const uint64_t DUST_UNIT = (cryptonote::old::STAKING_PORTIONS / staking_requirement); const uint64_t DUST = DUST_UNIT * FUDGE_FACTOR; if (needed > DUST) continue; @@ -3708,7 +3711,7 @@ namespace master_nodes // NOTE: Operator is sending in the minimum amount and it falls below // the minimum by dust, just increase the portions so it passes - if (needed > 0 && addr_to_portions.size() < MAX_NUMBER_OF_CONTRIBUTORS) + if (needed > 0 && addr_to_portions.size() < beldex::MAX_NUMBER_OF_CONTRIBUTORS) addr_to_portion.portions += needed; } @@ -3720,7 +3723,7 @@ namespace master_nodes if (min_portions == UINT64_MAX) { - result.err_msg = tr("Too many contributors specified, you can only split a node with up to: ") + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS) + tr(" people."); + result.err_msg = tr("Too many contributors specified, you can only split a node with up to: ") + std::to_string(beldex::MAX_NUMBER_OF_CONTRIBUTORS) + tr(" people."); return result; } @@ -3737,7 +3740,7 @@ namespace master_nodes } bool make_registration_cmd(cryptonote::network_type nettype, - uint8_t hf_version, + hf hf_version, uint64_t staking_requirement, const std::vector& args, const master_node_keys &keys, @@ -3752,7 +3755,7 @@ namespace master_nodes return false; } - uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW; + uint64_t exp_timestamp = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW); crypto::hash hash; bool hashed = cryptonote::get_registration_hash(contributor_args.addresses, contributor_args.portions_for_operator, contributor_args.portions, exp_timestamp, hash); @@ -3768,7 +3771,9 @@ namespace master_nodes std::stringstream stream; if (make_friendly) { - stream << tr("Run this command in the wallet that will fund this registration:\n\n"); + stream << tr("Run this command in the operator wallet") << " (" << + cryptonote::get_account_address_as_str(nettype, false, contributor_args.addresses[0]) + << "):\n\n"; } stream << "register_master_node"; @@ -3822,9 +3827,9 @@ namespace master_nodes return true; } - bool master_node_info::can_transition_to_state(uint8_t hf_version, uint64_t height, new_state proposed_state) const + bool master_node_info::can_transition_to_state(hf hf_version, uint64_t height, new_state proposed_state) const { - if (hf_version >= cryptonote::network_version_14_enforce_checkpoints) { + if (hf_version >= hf::hf14_enforce_checkpoints) { if (!can_be_voted_on(height)) { MDEBUG("MN state transition invalid: " << height << " is not a valid vote height"); return false; @@ -3874,7 +3879,7 @@ namespace master_nodes // Add contributors and their portions to winners. result.payouts.reserve(info.contributors.size()); - const uint64_t remaining_portions = STAKING_PORTIONS - info.portions_for_operator; + const uint64_t remaining_portions = cryptonote::old::STAKING_PORTIONS - info.portions_for_operator; for (const auto& contributor : info.contributors) { uint64_t hi, lo, resulthi, resultlo; diff --git a/src/cryptonote_core/master_node_list.h b/src/cryptonote_core/master_node_list.h index 44f7f53de24..fee2b7abc06 100755 --- a/src/cryptonote_core/master_node_list.h +++ b/src/cryptonote_core/master_node_list.h @@ -32,6 +32,7 @@ #include #include #include +#include "cryptonote_basic/hardfork.h" #include "serialization/serialization.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_core/master_node_rules.h" @@ -308,7 +309,7 @@ namespace master_nodes cryptonote::account_public_address operator_address{}; uint64_t last_ip_change_height = 0; // The height of the last quorum penalty for changing IPs version_t version = tools::enum_top; - uint8_t registration_hf_version = 0; + cryptonote::hf registration_hf_version = cryptonote::hf::none; POS_sort_key POS_sorter; master_node_info() = default; @@ -316,7 +317,7 @@ namespace master_nodes bool is_decommissioned() const { return active_since_height < 0; } bool is_active() const { return is_fully_funded() && !is_decommissioned(); } - bool can_transition_to_state(uint8_t hf_version, uint64_t block_height, new_state proposed_state) const; + bool can_transition_to_state(cryptonote::hf hf_version, uint64_t block_height, new_state proposed_state) const; bool can_be_voted_on (uint64_t block_height) const; size_t total_num_locked_contributions() const; @@ -627,7 +628,7 @@ namespace master_nodes struct state_serialized { enum struct version_t : uint8_t { version_0, version_1_serialize_hash, count, }; - static version_t get_version(uint8_t /*hf_version*/) { return version_t::version_1_serialize_hash; } + static version_t get_version(cryptonote::hf /*hf_version*/) { return version_t::version_1_serialize_hash; } version_t version; uint64_t height; @@ -653,7 +654,7 @@ namespace master_nodes struct data_for_serialization { enum struct version_t : uint8_t { version_0, count, }; - static version_t get_version(uint8_t /*hf_version*/) { return version_t::version_0; } + static version_t get_version(cryptonote::hf /*hf_version*/) { return version_t::version_0; } version_t version; std::vector quorum_states; @@ -689,7 +690,7 @@ namespace master_nodes std::vector active_master_nodes_infos() const; std::vector decommissioned_master_nodes_infos() const; // return: All nodes that are fully funded *and* decommissioned. - std::vector get_expired_nodes(cryptonote::BlockchainDB const &db, cryptonote::network_type nettype, uint8_t hf_version, uint64_t block_height) const; + std::vector get_expired_nodes(cryptonote::BlockchainDB const &db, cryptonote::network_type nettype, cryptonote::hf hf_version, uint64_t block_height) const; void update_from_block( cryptonote::BlockchainDB const &db, cryptonote::network_type nettype, @@ -713,7 +714,7 @@ namespace master_nodes const cryptonote::block &block, const cryptonote::transaction& tx, const master_node_keys *my_keys); - bool process_key_image_unlock_tx(cryptonote::network_type nettype, uint64_t block_height, const cryptonote::transaction &tx,uint8_t version); + bool process_key_image_unlock_tx(cryptonote::network_type nettype, uint64_t block_height, const cryptonote::transaction &tx,cryptonote::hf version); payout get_block_leader() const; payout get_block_producer(uint8_t POS_round) const; master_node_info get_master_node_details(crypto::public_key mnode_key); @@ -780,7 +781,7 @@ namespace master_nodes }; bool tx_get_staking_components (cryptonote::transaction_prefix const &tx_prefix, staking_components *contribution, crypto::hash const &txid); bool tx_get_staking_components (cryptonote::transaction const &tx, staking_components *contribution); - bool tx_get_staking_components_and_amounts(cryptonote::network_type nettype, uint8_t hf_version, cryptonote::transaction const &tx, uint64_t block_height, staking_components *contribution); + bool tx_get_staking_components_and_amounts(cryptonote::network_type nettype, cryptonote::hf hf_version, cryptonote::transaction const &tx, uint64_t block_height, staking_components *contribution); struct contributor_args_t { @@ -791,22 +792,22 @@ namespace master_nodes std::string err_msg; // if (success == false), this is set to the err msg otherwise empty }; - bool is_registration_tx (cryptonote::network_type nettype, uint8_t hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, master_node_info& info); + bool is_registration_tx (cryptonote::network_type nettype, cryptonote::hf hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, master_node_info& info); bool reg_tx_extract_fields(const cryptonote::transaction& tx, contributor_args_t &contributor_args, uint64_t& expiration_timestamp, crypto::public_key& master_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key); uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height); contributor_args_t convert_registration_args(cryptonote::network_type nettype, const std::vector &args, uint64_t staking_requirement, - uint8_t hf_version); + cryptonote::hf hf_version); // validate_contributors_* functions throws invalid_contributions exception struct invalid_contributions : std::invalid_argument { using std::invalid_argument::invalid_argument; }; - void validate_contributor_args(uint8_t hf_version, contributor_args_t const &contributor_args); + void validate_contributor_args(cryptonote::hf hf_version, contributor_args_t const &contributor_args); void validate_contributor_args_signature(contributor_args_t const &contributor_args, uint64_t const expiration_timestamp, crypto::public_key const &master_node_key, crypto::signature const &signature); bool make_registration_cmd(cryptonote::network_type nettype, - uint8_t hf_version, + cryptonote::hf hf_version, uint64_t staking_requirement, const std::vector& args, const master_node_keys &keys, @@ -815,7 +816,7 @@ namespace master_nodes master_nodes::quorum generate_POS_quorum(cryptonote::network_type nettype, crypto::public_key const &leader, - uint8_t hf_version, + cryptonote::hf hf_version, std::vector const &active_mnode_list, std::vector const &POS_entropy, uint8_t POS_round); @@ -829,6 +830,6 @@ namespace master_nodes payout master_node_info_to_payout(crypto::public_key const &key, master_node_info const &info); - const static payout_entry null_payout_entry = {cryptonote::null_address, STAKING_PORTIONS}; + const static payout_entry null_payout_entry = {cryptonote::null_address, cryptonote::old::STAKING_PORTIONS}; const static payout null_payout = {crypto::null_pkey, {null_payout_entry}}; } diff --git a/src/cryptonote_core/master_node_quorum_cop.cpp b/src/cryptonote_core/master_node_quorum_cop.cpp index d5fcf9a9340..14f07730e70 100755 --- a/src/cryptonote_core/master_node_quorum_cop.cpp +++ b/src/cryptonote_core/master_node_quorum_cop.cpp @@ -44,6 +44,7 @@ #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "quorum_cop" +using cryptonote::hf; namespace master_nodes { std::optional> master_node_test_results::why() const @@ -77,7 +78,7 @@ namespace master_nodes // Perform master node tests -- this returns true if the server node is in a good state, that is, // has submitted uptime proofs, participated in required quorums, etc. - master_node_test_results quorum_cop::check_master_node(std::map>> multi_mns_list, uint8_t hf_version, const crypto::public_key &pubkey, const master_node_info &info) const + master_node_test_results quorum_cop::check_master_node(std::map>> multi_mns_list, hf hf_version, const crypto::public_key &pubkey, const master_node_info &info) const { const auto& netconf = m_core.get_net_config(); @@ -108,7 +109,7 @@ namespace master_nodes }); - if (hf_version >= cryptonote::network_version_19) { + if (hf_version >= hf::hf19_enhance_bns) { std::vector> multi_mns = multi_mns_list[ips[0].first]; @@ -155,7 +156,7 @@ namespace master_nodes - if (hf_version > cryptonote::network_version_12_security_signature) { + if (hf_version > hf::hf12_security_signature) { if (!ss_reachable) { @@ -163,7 +164,7 @@ namespace master_nodes result.storage_server_reachable = false; } // TODO: perhaps come back and make this activate on some "soft fork" height before HF19? - if (!belnet_reachable && hf_version >= cryptonote::network_version_18_bns) { + if (!belnet_reachable && hf_version >= hf::hf18_bns) { LOG_PRINT_L1("Master Node belnet is not reachable for node: " << pubkey); result.belnet_reachable = false; } @@ -213,8 +214,8 @@ namespace master_nodes void quorum_cop::blockchain_detached(uint64_t height, bool by_pop_blocks) { - uint8_t hf_version = get_network_version(m_core.get_nettype(), height); - uint64_t const REORG_SAFETY_BUFFER_BLOCKS = (hf_version >= cryptonote::network_version_13_checkpointing) + auto hf_version = get_network_version(m_core.get_nettype(), height); + uint64_t const REORG_SAFETY_BUFFER_BLOCKS = (hf_version >= hf::hf13_checkpointing) ? REORG_SAFETY_BUFFER_BLOCKS_POST_HF12 : REORG_SAFETY_BUFFER_BLOCKS_PRE_HF12; if (m_obligations_height >= height) @@ -236,7 +237,7 @@ namespace master_nodes m_last_checkpointed_height = height - (height % CHECKPOINT_INTERVAL); } - m_vote_pool.remove_expired_votes(height,hf_version); + m_vote_pool.remove_expired_votes(height); } void quorum_cop::set_votes_relayed(std::vector const &relayed_votes) @@ -244,7 +245,7 @@ namespace master_nodes m_vote_pool.set_relayed(relayed_votes); } - std::vector quorum_cop::get_relayable_votes(uint64_t current_height, uint8_t hf_version, bool quorum_relay) + std::vector quorum_cop::get_relayable_votes(uint64_t current_height, hf hf_version, bool quorum_relay) { return m_vote_pool.get_relayable_votes(current_height, hf_version, quorum_relay); } @@ -258,7 +259,7 @@ namespace master_nodes return result; } - void quorum_cop::handling_master_nodes_states(std::map>> multi_mns_list, uint8_t const obligations_height_hf_version_,uint8_t const hf_version,std::shared_ptr quorum,int index_in_group,uint64_t const latest_height) + void quorum_cop::handling_master_nodes_states(std::map>> multi_mns_list, const cryptonote::hf obligations_height_hf_version_, const cryptonote::hf hf_version,std::shared_ptr quorum,int index_in_group,uint64_t const latest_height) { // // NOTE: I am in the quorum @@ -364,7 +365,7 @@ namespace master_nodes LOG_PRINT_L3(good << " of " << total << " master nodes are active and passing checks; no state change votes required"); } - void quorum_cop::handling_my_master_node_states(std::map>> multi_mns_list, uint8_t const obligations_height_hf_version,uint8_t const hf_version,bool &tested_myself_once_per_block,std::chrono::seconds live_time) + void quorum_cop::handling_my_master_node_states(std::map>> multi_mns_list, const cryptonote::hf obligations_height_hf_version, const cryptonote::hf hf_version, bool &tested_myself_once_per_block, std::chrono::seconds live_time) { const auto& my_keys = m_core.get_master_keys(); const auto states_array = m_core.get_master_node_list_state({my_keys.pub}); @@ -399,9 +400,9 @@ namespace master_nodes } } - void quorum_cop::quorum_checkpoint_handle(uint64_t const start_voting_from_height_,uint64_t const height,uint8_t const hf_version) + void quorum_cop::quorum_checkpoint_handle(uint64_t const start_voting_from_height_,uint64_t const height, const cryptonote::hf hf_version) { - uint64_t const REORG_SAFETY_BUFFER_BLOCKS = (hf_version >= cryptonote::network_version_13_checkpointing) + uint64_t const REORG_SAFETY_BUFFER_BLOCKS = (hf_version >= hf::hf13_checkpointing) ? REORG_SAFETY_BUFFER_BLOCKS_POST_HF12 : REORG_SAFETY_BUFFER_BLOCKS_PRE_HF12; const auto& my_keys = m_core.get_master_keys(); @@ -412,11 +413,11 @@ namespace master_nodes m_last_checkpointed_height = std::max(start_checkpointing_height, m_last_checkpointed_height); for (; - m_last_checkpointed_height <= height; + m_last_checkpointed_height < height; m_last_checkpointed_height += CHECKPOINT_INTERVAL) { - uint8_t checkpointed_height_hf_version = get_network_version(m_core.get_nettype(), m_last_checkpointed_height); - if (checkpointed_height_hf_version <= cryptonote::network_version_11_infinite_staking) + auto checkpointed_height_hf_version = get_network_version(m_core.get_nettype(), m_last_checkpointed_height); + if (checkpointed_height_hf_version <= hf::hf11_infinite_staking) continue; if (m_last_checkpointed_height < REORG_SAFETY_BUFFER_BLOCKS) @@ -446,8 +447,8 @@ namespace master_nodes void quorum_cop::process_quorums(cryptonote::block const &block) { - uint8_t const hf_version = block.major_version; - if (hf_version < cryptonote::network_version_9_master_nodes) + const auto hf_version = block.major_version; + if (hf_version < hf::hf9_master_nodes) return; auto mn_infos = m_core.get_master_node_list_state(); @@ -462,7 +463,7 @@ namespace master_nodes } const auto& netconf = m_core.get_net_config(); - uint64_t const REORG_SAFETY_BUFFER_BLOCKS = (hf_version >= cryptonote::network_version_13_checkpointing) + uint64_t const REORG_SAFETY_BUFFER_BLOCKS = (hf_version >= hf::hf13_checkpointing) ? REORG_SAFETY_BUFFER_BLOCKS_POST_HF12 : REORG_SAFETY_BUFFER_BLOCKS_PRE_HF12; const auto& my_keys = m_core.get_master_keys(); @@ -477,12 +478,12 @@ namespace master_nodes if (height < start_voting_from_height) return; - master_nodes::quorum_type const max_quorum_type = master_nodes::max_quorum_type_for_hf(hf_version); + const auto max_quorum_type = master_nodes::max_quorum_type_for_hf(hf_version); bool tested_myself_once_per_block = false; time_t start_time = m_core.get_start_time(); std::chrono::seconds live_time{time(nullptr) - start_time}; - for (int i = 0; i <= (int)max_quorum_type; i++) + for (int i = 0; i <= static_cast(max_quorum_type); i++) { quorum_type const type = static_cast(i); @@ -505,14 +506,14 @@ namespace master_nodes m_obligations_height = std::max(m_obligations_height, start_voting_from_height); for (; m_obligations_height < (height - REORG_SAFETY_BUFFER_BLOCKS); m_obligations_height++) { - uint8_t const obligations_height_hf_version = get_network_version(m_core.get_nettype(), m_obligations_height); - if (obligations_height_hf_version < cryptonote::network_version_9_master_nodes) continue; + const auto obligations_height_hf_version = get_network_version(m_core.get_nettype(), m_obligations_height); + if (obligations_height_hf_version < hf::hf9_master_nodes) continue; // NOTE: Count checkpoints for other nodes, irrespective of being // a master node or not for statistics. Also count checkpoints // before the minimum lifetime for same purposes, note, we still // don't vote for the first 2 hours so this is purely cosmetic - if (obligations_height_hf_version >= cryptonote::network_version_13_checkpointing) + if (obligations_height_hf_version >= hf::hf13_checkpointing) { master_nodes::master_node_list &node_list = m_core.get_master_node_list(); @@ -593,7 +594,7 @@ namespace master_nodes { process_quorums(block); uint64_t const height = cryptonote::get_block_height(block) + 1; // chain height = new top block height + 1 - m_vote_pool.remove_expired_votes(height,block.major_version); + m_vote_pool.remove_expired_votes(height); m_vote_pool.remove_used_votes(txs, block.major_version); } @@ -639,7 +640,7 @@ namespace master_nodes } using version_t = cryptonote::tx_extra_master_node_state_change::version_t; LOG_PRINT_L3("State not reachable A!?"); - auto ver = net >= HF_VERSION_PROOF_BTENC ? version_t::v4_reasons : version_t::v0; + auto ver = net >= cryptonote::feature::PROOF_BTENC ? version_t::v4_reasons : version_t::v0; cryptonote::tx_extra_master_node_state_change state_change{ ver, @@ -713,7 +714,7 @@ namespace master_nodes std::unique_lock lock{blockchain}; - bool update_checkpoint; + bool update_checkpoint = false; if (blockchain.get_checkpoint(vote.block_height, checkpoint) && checkpoint.block_hash == vote.checkpoint.block_hash) { @@ -745,7 +746,7 @@ namespace master_nodes } } } - else + else if (vote.block_height < core.get_current_blockchain_height()) // Don't accept checkpoints for blocks we don't have yet { update_checkpoint = true; checkpoint = make_empty_master_node_checkpoint(vote.checkpoint.block_hash, vote.block_height); @@ -760,10 +761,10 @@ namespace master_nodes return true; } - bool quorum_cop::handle_vote(quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc,uint8_t hf_version) + bool quorum_cop::handle_vote(quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc, hf hf_version) { vvc = {}; - if (!verify_vote_age(vote, m_core.get_current_blockchain_height(), vvc,hf_version)) { + if (!verify_vote_age(vote, m_core.get_current_blockchain_height(), vvc, hf_version)) { LOG_PRINT_L1("failed verify_vote_age"); return false; } @@ -809,7 +810,7 @@ namespace master_nodes // Calculate the decommission credit for a master node. If the MN is current decommissioned this // accumulated blocks. - int64_t quorum_cop::calculate_decommission_credit(const master_node_info &info, uint64_t current_height,uint8_t hf_version) + int64_t quorum_cop::calculate_decommission_credit(const master_node_info &info, uint64_t current_height, hf hf_version) { // If currently decommissioned, we need to know how long it was up before being decommissioned; // otherwise we need to know how long since it last become active until now (or 0 if not staked @@ -826,7 +827,7 @@ namespace master_nodes int64_t credit = info.recommission_credit; if (blocks_up > 0) { - credit += blocks_up * DECOMMISSION_CREDIT_PER_DAY / BLOCKS_PER_DAY; + credit += blocks_up * DECOMMISSION_CREDIT_PER_DAY / cryptonote::BLOCKS_PER_DAY; } if (credit > DECOMMISSION_MAX_CREDIT) diff --git a/src/cryptonote_core/master_node_quorum_cop.h b/src/cryptonote_core/master_node_quorum_cop.h index 623fd1e8092..16a6da4ff5d 100755 --- a/src/cryptonote_core/master_node_quorum_cop.h +++ b/src/cryptonote_core/master_node_quorum_cop.h @@ -122,17 +122,17 @@ namespace master_nodes void blockchain_detached(uint64_t height, bool by_pop_blocks); void set_votes_relayed (std::vector const &relayed_votes); - std::vector get_relayable_votes(uint64_t current_height, uint8_t hf_version, bool quorum_relay); - bool handle_vote (quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc,uint8_t hf_version); + std::vector get_relayable_votes(uint64_t current_height, cryptonote::hf hf_version, bool quorum_relay); + bool handle_vote (quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc, cryptonote::hf hf_version); - static int64_t calculate_decommission_credit(const master_node_info &info, uint64_t current_height,uint8_t hf_version); + static int64_t calculate_decommission_credit(const master_node_info &info, uint64_t current_height, cryptonote::hf hf_version); private: void process_quorums(cryptonote::block const &block); - void quorum_checkpoint_handle(uint64_t const start_voting_from_height_,uint64_t const height,uint8_t const hf_version); - void handling_master_nodes_states(std::map>> multi_mns_list, uint8_t const obligations_height_hf_version_,uint8_t const hf_version,std::shared_ptr quorum,int index_in_group,uint64_t const latest_height); - void handling_my_master_node_states(std::map>> multi_mns_list, uint8_t const obligations_height_hf_version,uint8_t const hf_version,bool &tested_myself_once_per_block,std::chrono::seconds live_time); - master_node_test_results check_master_node(std::map>> multi_mns_list, uint8_t hf_version, const crypto::public_key &pubkey, const master_node_info &info) const; + void quorum_checkpoint_handle(uint64_t const start_voting_from_height_,uint64_t const height, cryptonote::hf hf_version); + void handling_master_nodes_states(std::map>> multi_mns_list, const cryptonote::hf obligations_height_hf_version_, const cryptonote::hf hf_version, std::shared_ptr quorum, int index_in_group, uint64_t const latest_height); + void handling_my_master_node_states(std::map>> multi_mns_list, const cryptonote::hf obligations_height_hf_version,const cryptonote::hf hf_version, bool &tested_myself_once_per_block, std::chrono::seconds live_time); + master_node_test_results check_master_node(std::map>> multi_mns_list, cryptonote::hf hf_version, const crypto::public_key &pubkey, const master_node_info &info) const; cryptonote::core& m_core; voting_pool m_vote_pool; diff --git a/src/cryptonote_core/master_node_rules.cpp b/src/cryptonote_core/master_node_rules.cpp index 7bb1ab8a057..cf15e2a3712 100755 --- a/src/cryptonote_core/master_node_rules.cpp +++ b/src/cryptonote_core/master_node_rules.cpp @@ -7,15 +7,17 @@ #include #include +#include "beldex_economy.h" #include "master_node_rules.h" +using cryptonote::hf; namespace master_nodes { // TODO(beldex): Move to beldex_economy, this will also need access to beldex::exp2 uint64_t get_staking_requirement(uint64_t height) { - uint64_t result = COIN * 100000; - if(height>=MODIFIED_STAKING_REQUIREMENT_HEIGHT) result = COIN * 10000; + uint64_t result = 100000 * beldex::COIN; + if(height >= beldex::MODIFIED_STAKING_REQUIREMENT_HEIGHT) result = 10000 * beldex::COIN; return result; } @@ -23,23 +25,23 @@ uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement) { uint64_t hi, lo, resulthi, resultlo; lo = mul128(staking_requirement, portions, &hi); - div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo); + div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); return resultlo; } -bool check_master_node_portions(uint8_t hf_version, const std::vector& portions) +bool check_master_node_portions(hf hf_version, const std::vector& portions) { - if (portions.size() > MAX_NUMBER_OF_CONTRIBUTORS) return false; + if (portions.size() > beldex::MAX_NUMBER_OF_CONTRIBUTORS) return false; uint64_t reserved = 0; for (auto i = 0u; i < portions.size(); ++i) { - const uint64_t min_portions = get_min_node_contribution(hf_version, STAKING_PORTIONS, reserved, i); + const uint64_t min_portions = get_min_node_contribution(hf_version, cryptonote::old::STAKING_PORTIONS, reserved, i); if (portions[i] < min_portions) return false; reserved += portions[i]; } - return reserved <= STAKING_PORTIONS; + return reserved <= cryptonote::old::STAKING_PORTIONS; } crypto::hash generate_request_stake_unlock_hash(uint32_t nonce) @@ -52,7 +54,7 @@ crypto::hash generate_request_stake_unlock_hash(uint32_t nonce) return result; } -uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t curr_height,uint8_t version) +uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t curr_height,hf version) { uint64_t blocks_to_lock = staking_num_lock_blocks(nettype,version); uint64_t result = curr_height + (blocks_to_lock / 2); @@ -61,31 +63,31 @@ uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, ui static uint64_t get_min_node_contribution_pre_v11(uint64_t staking_requirement, uint64_t total_reserved) { - return std::min(staking_requirement - total_reserved, staking_requirement / MAX_NUMBER_OF_CONTRIBUTORS); + return std::min(staking_requirement - total_reserved, staking_requirement / beldex::MAX_NUMBER_OF_CONTRIBUTORS); } -uint64_t get_max_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved) +uint64_t get_max_node_contribution(hf version, uint64_t staking_requirement, uint64_t total_reserved) { - if (version >= cryptonote::network_version_17_POS) - return (staking_requirement - total_reserved) * config::MAXIMUM_ACCEPTABLE_STAKE::num - / config::MAXIMUM_ACCEPTABLE_STAKE::den; + if (version >= hf::hf17_POS) + return (staking_requirement - total_reserved) * cryptonote::MAXIMUM_ACCEPTABLE_STAKE::num + / cryptonote::MAXIMUM_ACCEPTABLE_STAKE::den; return std::numeric_limits::max(); } -uint64_t get_min_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions) +uint64_t get_min_node_contribution(hf version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions) { - if (version < cryptonote::network_version_11_infinite_staking) + if (version < hf::hf11_infinite_staking) return get_min_node_contribution_pre_v11(staking_requirement, total_reserved); const uint64_t needed = staking_requirement - total_reserved; - assert(MAX_NUMBER_OF_CONTRIBUTORS > num_contributions); - if (MAX_NUMBER_OF_CONTRIBUTORS <= num_contributions) return UINT64_MAX; + assert(beldex::MAX_NUMBER_OF_CONTRIBUTORS > num_contributions); + if (beldex::MAX_NUMBER_OF_CONTRIBUTORS <= num_contributions) return UINT64_MAX; - const size_t num_contributions_remaining_avail = MAX_NUMBER_OF_CONTRIBUTORS - num_contributions; + const size_t num_contributions_remaining_avail = beldex::MAX_NUMBER_OF_CONTRIBUTORS - num_contributions; return needed / num_contributions_remaining_avail; } -uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions) +uint64_t get_min_node_contribution_in_portions(hf version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions) { uint64_t atomic_amount = get_min_node_contribution(version, staking_requirement, total_reserved, num_contributions); uint64_t result = (atomic_amount == UINT64_MAX) ? UINT64_MAX : (get_portions_to_make_amount(staking_requirement, atomic_amount)); @@ -109,11 +111,11 @@ static bool get_portions_from_percent(double cur_percent, uint64_t& portions) { // Fix for truncation issue when operator cut = 100 for a pool Master Node. if (cur_percent == 100.0) { - portions = STAKING_PORTIONS; + portions = cryptonote::old::STAKING_PORTIONS; } else { - portions = (cur_percent / 100.0) * (double)STAKING_PORTIONS; + portions = (cur_percent / 100.0) * (double)cryptonote::old::STAKING_PORTIONS; } return true; diff --git a/src/cryptonote_core/master_node_rules.h b/src/cryptonote_core/master_node_rules.h index 2cabac14103..efde8e2ca74 100755 --- a/src/cryptonote_core/master_node_rules.h +++ b/src/cryptonote_core/master_node_rules.h @@ -6,34 +6,34 @@ #include namespace master_nodes { - constexpr size_t POS_QUORUM_ENTROPY_LAG = 21; // How many blocks back from the tip of the Blockchain to source entropy for the POS quorums. + inline constexpr size_t POS_QUORUM_ENTROPY_LAG = 21; // How many blocks back from the tip of the Blockchain to source entropy for the POS quorums. #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) - constexpr auto POS_ROUND_TIME = 20s; - constexpr auto POS_WAIT_FOR_HANDSHAKES_DURATION = 3s; - constexpr auto POS_WAIT_FOR_OTHER_VALIDATOR_HANDSHAKES_DURATION = 3s; - constexpr auto POS_WAIT_FOR_BLOCK_TEMPLATE_DURATION = 3s; - constexpr auto POS_WAIT_FOR_RANDOM_VALUE_HASH_DURATION = 3s; - constexpr auto POS_WAIT_FOR_RANDOM_VALUE_DURATION = 3s; - constexpr auto POS_WAIT_FOR_SIGNED_BLOCK_DURATION = 5s; - - constexpr size_t POS_QUORUM_NUM_VALIDATORS = 7; - constexpr size_t POS_BLOCK_REQUIRED_SIGNATURES = 6; // A block must have exactly N signatures to be considered properly + inline constexpr auto POS_ROUND_TIME = 20s; + inline constexpr auto POS_WAIT_FOR_HANDSHAKES_DURATION = 3s; + inline constexpr auto POS_WAIT_FOR_OTHER_VALIDATOR_HANDSHAKES_DURATION = 3s; + inline constexpr auto POS_WAIT_FOR_BLOCK_TEMPLATE_DURATION = 3s; + inline constexpr auto POS_WAIT_FOR_RANDOM_VALUE_HASH_DURATION = 3s; + inline constexpr auto POS_WAIT_FOR_RANDOM_VALUE_DURATION = 3s; + inline constexpr auto POS_WAIT_FOR_SIGNED_BLOCK_DURATION = 5s; + + inline constexpr size_t POS_QUORUM_NUM_VALIDATORS = 7; + inline constexpr size_t POS_BLOCK_REQUIRED_SIGNATURES = 6; // A block must have exactly N signatures to be considered properly #else - constexpr auto POS_ROUND_TIME = 60s; - constexpr auto POS_WAIT_FOR_HANDSHAKES_DURATION = 10s; - constexpr auto POS_WAIT_FOR_OTHER_VALIDATOR_HANDSHAKES_DURATION = 10s; - constexpr auto POS_WAIT_FOR_BLOCK_TEMPLATE_DURATION = 10s; - constexpr auto POS_WAIT_FOR_RANDOM_VALUE_HASH_DURATION = 10s; - constexpr auto POS_WAIT_FOR_RANDOM_VALUE_DURATION = 10s; - constexpr auto POS_WAIT_FOR_SIGNED_BLOCK_DURATION = 10s; - - constexpr size_t POS_QUORUM_NUM_VALIDATORS = 11; - constexpr size_t POS_BLOCK_REQUIRED_SIGNATURES = 7; // A block must have exactly N signatures to be considered properly + inline constexpr auto POS_ROUND_TIME = 60s; + inline constexpr auto POS_WAIT_FOR_HANDSHAKES_DURATION = 10s; + inline constexpr auto POS_WAIT_FOR_OTHER_VALIDATOR_HANDSHAKES_DURATION = 10s; + inline constexpr auto POS_WAIT_FOR_BLOCK_TEMPLATE_DURATION = 10s; + inline constexpr auto POS_WAIT_FOR_RANDOM_VALUE_HASH_DURATION = 10s; + inline constexpr auto POS_WAIT_FOR_RANDOM_VALUE_DURATION = 10s; + inline constexpr auto POS_WAIT_FOR_SIGNED_BLOCK_DURATION = 10s; + + inline constexpr size_t POS_QUORUM_NUM_VALIDATORS = 11; + inline constexpr size_t POS_BLOCK_REQUIRED_SIGNATURES = 7; // A block must have exactly N signatures to be considered properly #endif - constexpr auto POS_MIN_TARGET_BLOCK_TIME = TARGET_BLOCK_TIME - 15s; - constexpr auto POS_MAX_TARGET_BLOCK_TIME = TARGET_BLOCK_TIME + 15s; - constexpr size_t POS_QUORUM_SIZE = POS_QUORUM_NUM_VALIDATORS + 1 /*Leader*/; + inline constexpr auto POS_MIN_TARGET_BLOCK_TIME = cryptonote::TARGET_BLOCK_TIME - 15s; + inline constexpr auto POS_MAX_TARGET_BLOCK_TIME = cryptonote::TARGET_BLOCK_TIME + 15s; + inline constexpr size_t POS_QUORUM_SIZE = POS_QUORUM_NUM_VALIDATORS + 1 /*Leader*/; static_assert(POS_ROUND_TIME >= POS_WAIT_FOR_HANDSHAKES_DURATION + @@ -48,10 +48,10 @@ namespace master_nodes { constexpr size_t POS_min_master_nodes(cryptonote::network_type nettype) { - return (nettype == cryptonote::MAINNET) ? 50 : POS_QUORUM_SIZE; + return (nettype == cryptonote::network_type::MAINNET) ? 50 : POS_QUORUM_SIZE; } - static_assert(POS_min_master_nodes(cryptonote::MAINNET) >= POS_QUORUM_SIZE); - static_assert(POS_min_master_nodes(cryptonote::TESTNET) >= POS_QUORUM_SIZE); + static_assert(POS_min_master_nodes(cryptonote::network_type::MAINNET) >= POS_QUORUM_SIZE); + static_assert(POS_min_master_nodes(cryptonote::network_type::TESTNET) >= POS_QUORUM_SIZE); constexpr uint16_t POS_validator_bit_mask() { @@ -82,11 +82,11 @@ namespace master_nodes { // decommission time: the first quorum test after the credit expires determines whether the server // gets recommissioned or decommissioned). - inline constexpr int64_t DECOMMISSION_CREDIT_PER_DAY = BLOCKS_PER_DAY / 30; - inline constexpr int64_t DECOMMISSION_INITIAL_CREDIT = BLOCKS_PER_HOUR * 2; - inline constexpr int64_t DECOMMISSION_MAX_CREDIT = BLOCKS_PER_DAY * 2; - inline constexpr int64_t DECOMMISSION_MINIMUM = BLOCKS_PER_HOUR * 2; - inline constexpr int64_t DECOMMISSION_INITIAL_CREDIT_V18 = BLOCKS_PER_HOUR * 3; + inline constexpr int64_t DECOMMISSION_CREDIT_PER_DAY = cryptonote::BLOCKS_PER_DAY / 30; + inline constexpr int64_t DECOMMISSION_INITIAL_CREDIT = cryptonote::BLOCKS_PER_HOUR * 2; + inline constexpr int64_t DECOMMISSION_MAX_CREDIT = cryptonote::BLOCKS_PER_DAY * 2; + inline constexpr int64_t DECOMMISSION_MINIMUM = cryptonote::BLOCKS_PER_HOUR * 2; + inline constexpr int64_t DECOMMISSION_INITIAL_CREDIT_V18 = cryptonote::BLOCKS_PER_HOUR * 3; static_assert(DECOMMISSION_INITIAL_CREDIT <= DECOMMISSION_MAX_CREDIT, "Initial registration decommission credit cannot be larger than the maximum decommission credit"); static_assert(DECOMMISSION_INITIAL_CREDIT_V18 <= DECOMMISSION_MAX_CREDIT, "Initial registration decommission credit cannot be larger than the maximum decommission credit"); @@ -133,51 +133,51 @@ namespace master_nodes { "Recommission credit should not be negative"); - constexpr uint64_t CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY = 2; // Number of consecutive checkpoints before, blocks preceeding the N checkpoints are locked in - constexpr uint64_t CHECKPOINT_INTERVAL = 4; // Checkpoint every 4 blocks and prune when too old except if (height % CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0) - constexpr uint64_t CHECKPOINT_STORE_PERSISTENTLY_INTERVAL = 60; // Persistently store the checkpoints at these intervals - constexpr uint64_t CHECKPOINT_VOTE_LIFETIME = CHECKPOINT_STORE_PERSISTENTLY_INTERVAL; // Keep the last 60 blocks worth of votes + inline constexpr uint64_t CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY = 2; // Number of consecutive checkpoints before, blocks preceeding the N checkpoints are locked in + inline constexpr uint64_t CHECKPOINT_INTERVAL = 4; // Checkpoint every 4 blocks and prune when too old except if (height % CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0) + inline constexpr uint64_t CHECKPOINT_STORE_PERSISTENTLY_INTERVAL = 60; // Persistently store the checkpoints at these intervals + inline constexpr uint64_t CHECKPOINT_VOTE_LIFETIME = CHECKPOINT_STORE_PERSISTENTLY_INTERVAL; // Keep the last 60 blocks worth of votes - constexpr int16_t QUORUM_VOTE_CHECK_COUNT = 8; - constexpr int16_t POS_MAX_MISSABLE_VOTES = 4; - constexpr int16_t CHECKPOINT_MAX_MISSABLE_VOTES = 4; - constexpr int16_t TIMESTAMP_MAX_MISSABLE_VOTES = 4; - constexpr int16_t TIMESYNC_MAX_UNSYNCED_VOTES = 4; + inline constexpr int16_t QUORUM_VOTE_CHECK_COUNT = 8; + inline constexpr int16_t POS_MAX_MISSABLE_VOTES = 4; + inline constexpr int16_t CHECKPOINT_MAX_MISSABLE_VOTES = 4; + inline constexpr int16_t TIMESTAMP_MAX_MISSABLE_VOTES = 4; + inline constexpr int16_t TIMESYNC_MAX_UNSYNCED_VOTES = 4; static_assert(CHECKPOINT_MAX_MISSABLE_VOTES < QUORUM_VOTE_CHECK_COUNT, "The maximum number of votes a master node can miss cannot be greater than the amount of checkpoint " "quorums they must participate in before we check if they should be deregistered or not."); - constexpr int FLASH_QUORUM_INTERVAL = 5; // We generate a new sub-quorum every N blocks (two consecutive quorums are needed for a flash signature) - constexpr int FLASH_QUORUM_LAG = 7 * FLASH_QUORUM_INTERVAL; // The lag (which must be a multiple of FLASH_QUORUM_INTERVAL) in determining the base flash quorum height - constexpr int FLASH_EXPIRY_BUFFER = FLASH_QUORUM_LAG + 10; // We don't select any MNs that have a scheduled unlock within this many blocks (measured from the lagged height) + inline constexpr int FLASH_QUORUM_INTERVAL = 5; // We generate a new sub-quorum every N blocks (two consecutive quorums are needed for a flash signature) + inline constexpr int FLASH_QUORUM_LAG = 7 * FLASH_QUORUM_INTERVAL; // The lag (which must be a multiple of FLASH_QUORUM_INTERVAL) in determining the base flash quorum height + inline constexpr int FLASH_EXPIRY_BUFFER = FLASH_QUORUM_LAG + 10; // We don't select any MNs that have a scheduled unlock within this many blocks (measured from the lagged height) static_assert(FLASH_QUORUM_LAG % FLASH_QUORUM_INTERVAL == 0, "FLASH_QUORUM_LAG must be an integral multiple of FLASH_QUORUM_INTERVAL"); static_assert(FLASH_EXPIRY_BUFFER > FLASH_QUORUM_LAG + FLASH_QUORUM_INTERVAL, "FLASH_EXPIRY_BUFFER is too short to cover a flash quorum height range"); // State change quorums are in charge of policing the network by changing the state of a master // node on the network: temporary decommissioning, recommissioning, and permanent deregistration. - constexpr size_t STATE_CHANGE_NTH_OF_THE_NETWORK_TO_TEST = 100; - constexpr size_t STATE_CHANGE_MIN_NODES_TO_TEST = 50; - constexpr uint64_t VOTE_LIFETIME = BLOCKS_PER_HOUR * 2; + inline constexpr size_t STATE_CHANGE_NTH_OF_THE_NETWORK_TO_TEST = 100; + inline constexpr size_t STATE_CHANGE_MIN_NODES_TO_TEST = 50; + inline constexpr uint64_t VOTE_LIFETIME = cryptonote::BLOCKS_PER_HOUR * 2; // Small Stake prevented from unlocking stake until a certain number of blocks have passed - constexpr uint64_t SMALL_CONTRIBUTOR_UNLOCK_TIMER = 2880 * 30; - constexpr uint64_t SMALL_CONTRIBUTOR_THRESHOLD = 2500; + inline constexpr uint64_t SMALL_CONTRIBUTOR_UNLOCK_TIMER = 2880 * 30; + inline constexpr uint64_t SMALL_CONTRIBUTOR_THRESHOLD = 2500; #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) - constexpr size_t STATE_CHANGE_QUORUM_SIZE = 5; - constexpr size_t STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE = 1; - constexpr int MIN_TIME_IN_S_BEFORE_VOTING = 0; - constexpr size_t CHECKPOINT_QUORUM_SIZE = 5; - constexpr size_t CHECKPOINT_MIN_VOTES = 1; - constexpr int FLASH_SUBQUORUM_SIZE = 5; - constexpr int FLASH_MIN_VOTES = 1; + inline constexpr size_t STATE_CHANGE_QUORUM_SIZE = 5; + inline constexpr size_t STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE = 1; + inline constexpr int MIN_TIME_IN_S_BEFORE_VOTING = 0; + inline constexpr size_t CHECKPOINT_QUORUM_SIZE = 5; + inline constexpr size_t CHECKPOINT_MIN_VOTES = 1; + inline constexpr int FLASH_SUBQUORUM_SIZE = 5; + inline constexpr int FLASH_MIN_VOTES = 1; #else - constexpr size_t STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE = 7; - constexpr size_t STATE_CHANGE_QUORUM_SIZE = 10; - constexpr size_t CHECKPOINT_QUORUM_SIZE = 20; - constexpr size_t CHECKPOINT_MIN_VOTES = 13; - constexpr int FLASH_SUBQUORUM_SIZE = 10; - constexpr int FLASH_MIN_VOTES = 7; + inline constexpr size_t STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE = 7; + inline constexpr size_t STATE_CHANGE_QUORUM_SIZE = 10; + inline constexpr size_t CHECKPOINT_QUORUM_SIZE = 20; + inline constexpr size_t CHECKPOINT_MIN_VOTES = 13; + inline constexpr int FLASH_SUBQUORUM_SIZE = 10; + inline constexpr int FLASH_MIN_VOTES = 7; #endif static_assert(STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE <= STATE_CHANGE_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick."); @@ -188,65 +188,65 @@ namespace master_nodes { #endif // NOTE: We can reorg up to last 2 checkpoints + the number of extra blocks before the next checkpoint is set - constexpr uint64_t REORG_SAFETY_BUFFER_BLOCKS_POST_HF12 = (CHECKPOINT_INTERVAL * CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY) + (CHECKPOINT_INTERVAL - 1); - constexpr uint64_t REORG_SAFETY_BUFFER_BLOCKS_PRE_HF12 = 20; + inline constexpr uint64_t REORG_SAFETY_BUFFER_BLOCKS_POST_HF12 = (CHECKPOINT_INTERVAL * CHECKPOINT_NUM_CHECKPOINTS_FOR_CHAIN_FINALITY) + (CHECKPOINT_INTERVAL - 1); + inline constexpr uint64_t REORG_SAFETY_BUFFER_BLOCKS_PRE_HF12 = 20; // A single public IP address is restricted from hosting more than 3 masternodes. - constexpr int16_t MAX_ALLOWED_MASTERNODES_PER_IP = 3; + inline constexpr int16_t MAX_ALLOWED_MASTERNODES_PER_IP = 3; - constexpr auto IP_CHANGE_WINDOW = 24h; // How far back an obligations quorum looks for multiple IPs (unless the following buffer is more recent) - constexpr auto IP_CHANGE_BUFFER = 2h; // After we bump a MN for an IP change we don't bump again for changes within this time period + inline constexpr auto IP_CHANGE_WINDOW = 24h; // How far back an obligations quorum looks for multiple IPs (unless the following buffer is more recent) + inline constexpr auto IP_CHANGE_BUFFER = 2h; // After we bump a MN for an IP change we don't bump again for changes within this time period - constexpr size_t MAX_SWARM_SIZE = 10; + inline constexpr size_t MAX_SWARM_SIZE = 10; // We never create a new swarm unless there are SWARM_BUFFER extra nodes // available in the queue. - constexpr size_t SWARM_BUFFER = 5; + inline constexpr size_t SWARM_BUFFER = 5; // if a swarm has strictly less nodes than this, it is considered unhealthy // and nearby swarms will mirror it's data. It will disappear, and is already considered gone. - constexpr size_t MIN_SWARM_SIZE = 5; - constexpr size_t IDEAL_SWARM_MARGIN = 2; - constexpr size_t IDEAL_SWARM_SIZE = MIN_SWARM_SIZE + IDEAL_SWARM_MARGIN; - // constexpr size_t EXCESS_BASE = MIN_SWARM_SIZE; - constexpr size_t NEW_SWARM_SIZE = IDEAL_SWARM_SIZE; + inline constexpr size_t MIN_SWARM_SIZE = 5; + inline constexpr size_t IDEAL_SWARM_MARGIN = 2; + inline constexpr size_t IDEAL_SWARM_SIZE = MIN_SWARM_SIZE + IDEAL_SWARM_MARGIN; + // inline constexpr size_t EXCESS_BASE = MIN_SWARM_SIZE; + inline constexpr size_t NEW_SWARM_SIZE = IDEAL_SWARM_SIZE; // The lower swarm percentile that will be randomly filled with new master nodes - constexpr size_t FILL_SWARM_LOWER_PERCENTILE = 25; + inline constexpr size_t FILL_SWARM_LOWER_PERCENTILE = 25; // Redistribute mnodes from decommissioned swarms to the smallest swarms - constexpr size_t DECOMMISSIONED_REDISTRIBUTION_LOWER_PERCENTILE = 0; + inline constexpr size_t DECOMMISSIONED_REDISTRIBUTION_LOWER_PERCENTILE = 0; // The upper swarm percentile that will be randomly selected during stealing - constexpr size_t STEALING_SWARM_UPPER_PERCENTILE = 75; - constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0; + inline constexpr size_t STEALING_SWARM_UPPER_PERCENTILE = 75; + inline constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0; - constexpr uint64_t STATE_CHANGE_TX_LIFETIME_IN_BLOCKS = VOTE_LIFETIME; + inline constexpr uint64_t STATE_CHANGE_TX_LIFETIME_IN_BLOCKS = VOTE_LIFETIME; // If we get an incoming vote of state change tx that is outside the acceptable range by this many // blocks then ignore it but don't trigger a connection drop; the sending side could be a couple // blocks out of sync and sending something that it thinks is legit. - constexpr uint64_t VOTE_OR_TX_VERIFY_HEIGHT_BUFFER = 5; + inline constexpr uint64_t VOTE_OR_TX_VERIFY_HEIGHT_BUFFER = 5; - constexpr std::array MIN_STORAGE_SERVER_VERSION{{2, 3, 0}}; - constexpr std::array MIN_BELNET_VERSION{{0, 9, 7}}; + inline constexpr std::array MIN_STORAGE_SERVER_VERSION{{2, 3, 0}}; + inline constexpr std::array MIN_BELNET_VERSION{{0, 9, 7}}; // The minimum accepted version number, broadcasted by Master Nodes via uptime proofs for each hardfork struct proof_version { - std::pair hardfork_revision; + std::pair hardfork_revision; std::array beldexd; std::array belnet; std::array storage_server; }; - constexpr proof_version MIN_UPTIME_PROOF_VERSIONS[] = { - proof_version{{cryptonote::network_version_19, 0}, {6,0,0}, {0,9,7}, {2,3,0}}, - proof_version{{cryptonote::network_version_18_bns, 0}, {5,0,0}, {0,9,7}, {2,3,0}}, - proof_version{{cryptonote::network_version_17_POS, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, - proof_version{{cryptonote::network_version_16, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, - proof_version{{cryptonote::network_version_15_flash, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, - proof_version{{cryptonote::network_version_14_enforce_checkpoints, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, - proof_version{{cryptonote::network_version_13_checkpointing, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, + inline constexpr proof_version MIN_UPTIME_PROOF_VERSIONS[] = { + proof_version{{cryptonote::hf::hf19_enhance_bns, 0}, {6,0,0}, {0,9,7}, {2,3,0}}, + proof_version{{cryptonote::hf::hf18_bns, 0}, {5,0,0}, {0,9,7}, {2,3,0}}, + proof_version{{cryptonote::hf::hf17_POS, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, + proof_version{{cryptonote::hf::hf16, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, + proof_version{{cryptonote::hf::hf15_flash, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, + proof_version{{cryptonote::hf::hf14_enforce_checkpoints,0}, {4,0,0}, {0,9,5}, {2,2,0}}, + proof_version{{cryptonote::hf::hf13_checkpointing, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, }; - using swarm_id_t = uint64_t; - constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX; + using swarm_id_t = uint64_t; + inline constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX; constexpr size_t min_votes_for_quorum_type(quorum_type q) { return @@ -256,36 +256,36 @@ namespace master_nodes { std::numeric_limits::max(); }; - constexpr quorum_type max_quorum_type_for_hf(uint8_t hf_version) + constexpr quorum_type max_quorum_type_for_hf(cryptonote::hf hf_version) { return - hf_version <= cryptonote::network_version_13_checkpointing ? quorum_type::obligations : - hf_version < cryptonote::network_version_15_flash ? quorum_type::checkpointing : - hf_version < cryptonote::network_version_17_POS ? quorum_type::flash : + hf_version <= cryptonote::hf::hf13_checkpointing ? quorum_type::obligations : + hf_version < cryptonote::hf::hf15_flash ? quorum_type::checkpointing : + hf_version < cryptonote::hf::hf17_POS ? quorum_type::flash : quorum_type::POS; } - constexpr uint64_t staking_num_lock_blocks(cryptonote::network_type nettype,uint8_t hf_version) + constexpr uint64_t staking_num_lock_blocks(cryptonote::network_type nettype, cryptonote::hf hf_version) { - auto blocks_per_day = (hf_version>=cryptonote::network_version_17_POS) ? BLOCKS_PER_DAY : BLOCKS_PER_DAY_OLD ; + auto blocks_per_day = (hf_version >= cryptonote::hf::hf17_POS) ? cryptonote::BLOCKS_PER_DAY : cryptonote::old::BLOCKS_PER_DAY_12 ; switch (nettype) { - case cryptonote::FAKECHAIN: return 30; - case cryptonote::TESTNET: return 2 * blocks_per_day; + case cryptonote::network_type::FAKECHAIN: return 30; + case cryptonote::network_type::TESTNET: return 2 * blocks_per_day; default: return 30 * blocks_per_day; } } //If a nodes timestamp varies by this amount of seconds they will be considered out of sync - constexpr uint8_t THRESHOLD_SECONDS_OUT_OF_SYNC = 30; +inline constexpr uint8_t THRESHOLD_SECONDS_OUT_OF_SYNC = 30; //If the below percentage of service nodes are out of sync we will consider our clock out of sync - constexpr uint8_t MAXIMUM_EXTERNAL_OUT_OF_SYNC = 80; +inline constexpr uint8_t MAXIMUM_EXTERNAL_OUT_OF_SYNC = 80; -static_assert(STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution"); +static_assert(cryptonote::old::STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution"); // return: UINT64_MAX if (num_contributions > the max number of contributions), otherwise the amount in beldex atomic units -uint64_t get_min_node_contribution (uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions); -uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions); +uint64_t get_min_node_contribution (cryptonote::hf version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions); +uint64_t get_min_node_contribution_in_portions(cryptonote::hf version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions); // Gets the maximum allowed stake amount. This is used to prevent significant overstaking. The // wallet tries to avoid this when submitting a stake, but it can still happen when competing stakes @@ -294,7 +294,7 @@ uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking // of stake despite locking 8k. // Starting in HF16, we disallow a stake if it is more than MAXIMUM_ACCEPTABLE_STAKE ratio of the // available contribution room, which allows slight overstaking but disallows larger overstakes. -uint64_t get_max_node_contribution(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved); +uint64_t get_max_node_contribution(cryptonote::hf version, uint64_t staking_requirement, uint64_t total_reserved); uint64_t get_staking_requirement(uint64_t height); @@ -302,13 +302,13 @@ uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement); /// Check if portions are sufficiently large (provided the contributions /// are made in the specified order) and don't exceed the required amount -bool check_master_node_portions(uint8_t version, const std::vector& portions); +bool check_master_node_portions(cryptonote::hf version, const std::vector& portions); crypto::hash generate_request_stake_unlock_hash(uint32_t nonce); -uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t curr_height,uint8_t version); +uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t curr_height, cryptonote::hf version); // Returns lowest x such that (staking_requirement * x/STAKING_PORTIONS) >= amount -uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount, uint64_t max_portions = STAKING_PORTIONS); +uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount, uint64_t max_portions = cryptonote::old::STAKING_PORTIONS); bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions); } diff --git a/src/cryptonote_core/master_node_voting.cpp b/src/cryptonote_core/master_node_voting.cpp index a104d36a109..9b3993716f3 100755 --- a/src/cryptonote_core/master_node_voting.cpp +++ b/src/cryptonote_core/master_node_voting.cpp @@ -46,6 +46,7 @@ #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "master_nodes" +using cryptonote::hf; namespace master_nodes { static crypto::hash make_state_change_vote_hash(uint64_t block_height, uint32_t master_node_index, new_state state) @@ -132,10 +133,10 @@ namespace master_nodes uint64_t latest_height, cryptonote::tx_verification_context &tvc, const master_nodes::quorum &quorum, - const uint8_t hf_version) + const hf hf_version) { auto &vvc = tvc.m_vote_ctx; - if (state_change.state != new_state::deregister && hf_version < cryptonote::network_version_13_checkpointing) + if (state_change.state != new_state::deregister && hf_version < hf::hf13_checkpointing) { LOG_PRINT_L1("Received state change TX with Non-deregister state changes are invalid before v12"); return bad_tx(tvc); @@ -198,7 +199,7 @@ namespace master_nodes int validator_index_tracker = -1; for (const auto &vote : state_change.votes) { - if (hf_version >= cryptonote::network_version_14_enforce_checkpoints) // NOTE: After HF13, votes must be stored in ascending order + if (hf_version >= hf::hf14_enforce_checkpoints) // NOTE: After HF13, votes must be stored in ascending order { if (validator_index_tracker >= static_cast(vote.validator_index)) { @@ -238,7 +239,7 @@ namespace master_nodes return true; } - bool verify_quorum_signatures(master_nodes::quorum const &quorum, master_nodes::quorum_type type, uint8_t hf_version, uint64_t height, crypto::hash const &hash, std::vector const &signatures, const cryptonote::block* block) + bool verify_quorum_signatures(master_nodes::quorum const &quorum, master_nodes::quorum_type type, hf hf_version, uint64_t height, crypto::hash const &hash, std::vector const &signatures, const cryptonote::block* block) { bool enforce_vote_ordering = true; constexpr size_t MAX_QUORUM_SIZE = std::max(CHECKPOINT_QUORUM_SIZE, POS_QUORUM_NUM_VALIDATORS); @@ -266,7 +267,7 @@ namespace master_nodes return false; } - enforce_vote_ordering = hf_version >= cryptonote::network_version_14_enforce_checkpoints; + enforce_vote_ordering = hf_version >= hf::hf14_enforce_checkpoints; } break; @@ -362,7 +363,7 @@ namespace master_nodes return result; } - bool verify_checkpoint(uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, master_nodes::quorum const &quorum) + bool verify_checkpoint(hf hf_version, cryptonote::checkpoint_t const &checkpoint, master_nodes::quorum const &quorum) { if (checkpoint.type == cryptonote::checkpoint_type::master_node) { @@ -404,7 +405,7 @@ namespace master_nodes return result; } - quorum_vote_t make_checkpointing_vote(uint8_t hf_version, crypto::hash const &block_hash, uint64_t block_height, uint16_t index_in_quorum, const master_node_keys &keys) + quorum_vote_t make_checkpointing_vote(hf hf_version, crypto::hash const &block_hash, uint64_t block_height, uint16_t index_in_quorum, const master_node_keys &keys) { quorum_vote_t result = {}; result.type = quorum_type::checkpointing; @@ -425,7 +426,7 @@ namespace master_nodes return result; } - bool verify_vote_age(const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc,uint8_t hf_version) + bool verify_vote_age(const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc, hf hf_version) { bool result = true; bool height_in_buffer = false; @@ -454,7 +455,7 @@ namespace master_nodes return result; } - bool verify_vote_signature(uint8_t hf_version, const quorum_vote_t &vote, cryptonote::vote_verification_context &vvc, const master_nodes::quorum &quorum) { + bool verify_vote_signature(hf hf_version, const quorum_vote_t &vote, cryptonote::vote_verification_context &vvc, const master_nodes::quorum &quorum) { bool result = true; if (vote.type > tools::enum_top) { vvc.m_invalid_vote_type = true; @@ -597,7 +598,7 @@ namespace master_nodes result.push_back(vote_entry.vote); } - std::vector voting_pool::get_relayable_votes(uint64_t height, uint8_t hf_version, bool quorum_relay) const + std::vector voting_pool::get_relayable_votes(uint64_t height, hf hf_version, bool quorum_relay) const { std::unique_lock lock{m_lock}; @@ -612,13 +613,13 @@ namespace master_nodes std::vector result; - if (quorum_relay && hf_version < cryptonote::network_version_15_flash) + if (quorum_relay && hf_version < hf::hf15_flash) return result; // no quorum relaying before HF14 - if (hf_version < cryptonote::network_version_15_flash || quorum_relay) + if (hf_version < hf::hf15_flash || quorum_relay) append_relayable_votes(result, m_obligations_pool, max_last_sent, min_height); - if (hf_version < cryptonote::network_version_15_flash || !quorum_relay) + if (hf_version < hf::hf15_flash || !quorum_relay) append_relayable_votes(result, m_checkpoint_pool, max_last_sent, min_height); return result; @@ -653,7 +654,7 @@ namespace master_nodes return *votes; } - void voting_pool::remove_used_votes(std::vector const &txs, uint8_t hard_fork_version) + void voting_pool::remove_used_votes(std::vector const &txs, hf version) { // TODO(doyle): Cull checkpoint votes std::unique_lock lock{m_lock}; @@ -666,7 +667,7 @@ namespace master_nodes continue; cryptonote::tx_extra_master_node_state_change state_change; - if (!get_master_node_state_change_from_tx_extra(tx.extra, state_change, hard_fork_version)) + if (!get_master_node_state_change_from_tx_extra(tx.extra, state_change, version)) { LOG_ERROR("Could not get state change from tx, possibly corrupt tx"); continue; @@ -692,7 +693,7 @@ namespace master_nodes } } - void voting_pool::remove_expired_votes(uint64_t height,uint8_t hf_version) + void voting_pool::remove_expired_votes(uint64_t height) { std::unique_lock lock{m_lock}; uint64_t min_height = (height < VOTE_LIFETIME) ? 0 : height - VOTE_LIFETIME; diff --git a/src/cryptonote_core/master_node_voting.h b/src/cryptonote_core/master_node_voting.h index 9a56a04f400..e7b48e3a151 100755 --- a/src/cryptonote_core/master_node_voting.h +++ b/src/cryptonote_core/master_node_voting.h @@ -102,14 +102,14 @@ namespace master_nodes struct master_node_keys; quorum_vote_t make_state_change_vote(uint64_t block_height, uint16_t index_in_group, uint16_t worker_index, new_state state, uint16_t reason, const master_node_keys &keys); - quorum_vote_t make_checkpointing_vote(uint8_t hf_version, crypto::hash const &block_hash, uint64_t block_height, uint16_t index_in_quorum, const master_node_keys &keys); + quorum_vote_t make_checkpointing_vote(cryptonote::hf hf_version, crypto::hash const &block_hash, uint64_t block_height, uint16_t index_in_quorum, const master_node_keys &keys); cryptonote::checkpoint_t make_empty_master_node_checkpoint(crypto::hash const &block_hash, uint64_t height); - bool verify_checkpoint (uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, master_nodes::quorum const &quorum); - bool verify_tx_state_change (const cryptonote::tx_extra_master_node_state_change& state_change, uint64_t latest_height, cryptonote::tx_verification_context& vvc, const master_nodes::quorum &quorum, const uint8_t hf_version); - bool verify_vote_age (const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc,uint8_t hf_version); - bool verify_vote_signature (uint8_t hf_version, const quorum_vote_t& vote, cryptonote::vote_verification_context &vvc, const master_nodes::quorum &quorum); - bool verify_quorum_signatures (master_nodes::quorum const &quorum, master_nodes::quorum_type type, uint8_t hf_version, uint64_t height, crypto::hash const &hash, std::vector const &signatures, const cryptonote::block* block = nullptr); + bool verify_checkpoint (cryptonote::hf hf_version, cryptonote::checkpoint_t const &checkpoint, master_nodes::quorum const &quorum); + bool verify_tx_state_change (const cryptonote::tx_extra_master_node_state_change& state_change, uint64_t latest_height, cryptonote::tx_verification_context& vvc, const master_nodes::quorum &quorum, const cryptonote::hf hf_version); + bool verify_vote_age (const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc, cryptonote::hf hf_version); + bool verify_vote_signature (cryptonote::hf hf_version, const quorum_vote_t& vote, cryptonote::vote_verification_context &vvc, const master_nodes::quorum &quorum); + bool verify_quorum_signatures (master_nodes::quorum const &quorum, master_nodes::quorum_type type, cryptonote::hf hf_version, uint64_t height, crypto::hash const &hash, std::vector const &signatures, const cryptonote::block* block = nullptr); bool verify_POS_quorum_sizes (master_nodes::quorum const &quorum); crypto::signature make_signature_from_vote (quorum_vote_t const &vote, const master_node_keys &keys); crypto::signature make_signature_from_tx_state_change(cryptonote::tx_extra_master_node_state_change const &state_change, const master_node_keys &keys); @@ -128,13 +128,13 @@ namespace master_nodes // TODO(beldex): Review relay behaviour and all the cases when it should be triggered void set_relayed (const std::vector& votes); - void remove_expired_votes(uint64_t height,uint8_t hf_version); - void remove_used_votes (std::vector const &txs, uint8_t hard_fork_version); + void remove_expired_votes(uint64_t height); + void remove_used_votes (std::vector const &txs, cryptonote::hf version); /// Returns relayable votes for either p2p (quorum_relay=false) or quorumnet /// (quorum_relay=true). Before HF14 everything goes via p2p; starting in HF14 obligation votes /// go via quorumnet, checkpoints go via p2p. - std::vector get_relayable_votes (uint64_t height, uint8_t hf_version, bool quorum_relay) const; + std::vector get_relayable_votes (uint64_t height, cryptonote::hf hf_version, bool quorum_relay) const; bool received_checkpoint_vote(uint64_t height, size_t index_in_quorum) const; private: diff --git a/src/cryptonote_core/pos.cpp b/src/cryptonote_core/pos.cpp index 70be8fb2c99..3c8c6bf1292 100755 --- a/src/cryptonote_core/pos.cpp +++ b/src/cryptonote_core/pos.cpp @@ -723,9 +723,9 @@ void POS::handle_message(void *quorumnet_state, POS::message const &msg) } // TODO(doyle): Update POS::prepare_for_round with this function after the hard fork and sanity check it on testnet. -bool POS::convert_time_to_round(POS::time_point const &time, POS::time_point const &r0_timestamp, uint8_t *round) +bool POS::convert_time_to_round(POS::time_point const& time, POS::time_point const& r0_timestamp, uint8_t* round) { - auto const time_since_round_started = time <= r0_timestamp ? std::chrono::seconds(0) : (time - r0_timestamp); + const auto time_since_round_started = time <= r0_timestamp ? 0s : (time - r0_timestamp); size_t result_usize = time_since_round_started / master_nodes::POS_ROUND_TIME; if (round) *round = static_cast(result_usize); return result_usize <= master_nodes::POS_MAX_ROUNDS_BEFORE_NETWORK_STALLED; @@ -734,7 +734,7 @@ bool POS::convert_time_to_round(POS::time_point const &time, POS::time_point con bool POS::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t block_height, uint64_t prev_timestamp, POS::timings ×) { times = {}; - auto hf17 = hard_fork_begins(blockchain.nettype(), cryptonote::network_version_17_POS); + auto hf17 = hard_fork_begins(blockchain.nettype(), cryptonote::hf::hf17_POS); if (!hf17 || blockchain.get_current_blockchain_height() < *hf17) return false; @@ -745,7 +745,7 @@ bool POS::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t b uint64_t const delta_height = block_height - cryptonote::get_block_height(POS_genesis_block); times.genesis_timestamp = POS::time_point(std::chrono::seconds(POS_genesis_block.timestamp)); times.prev_timestamp = POS::time_point(std::chrono::seconds(prev_timestamp)); - times.ideal_timestamp = POS::time_point(times.genesis_timestamp + (TARGET_BLOCK_TIME * delta_height)); //only for POS + times.ideal_timestamp = POS::time_point(times.genesis_timestamp + (cryptonote::TARGET_BLOCK_TIME * delta_height)); //only for POS #if 1 @@ -1144,7 +1144,7 @@ round_state prepare_for_round(round_context &context, master_nodes::master_node_ return goto_wait_for_next_block_and_clear_round_data(context); } - uint8_t curr_round = static_cast(round_usize); + auto curr_round = static_cast(round_usize); if (curr_round > context.prepare_for_round.round) context.prepare_for_round.round = curr_round; } @@ -1162,7 +1162,7 @@ round_state prepare_for_round(round_context &context, master_nodes::master_node_ std::vector const entropy = master_nodes::get_POS_entropy_for_next_block(blockchain.get_db(), context.wait_for_next_block.top_hash, context.prepare_for_round.round); auto const active_node_list = blockchain.get_master_node_list().active_master_nodes_infos(); - uint8_t const hf_version = blockchain.get_network_version(); + auto hf_version = blockchain.get_network_version(); crypto::public_key const &block_leader = blockchain.get_master_node_list().get_block_leader().key; context.prepare_for_round.quorum = @@ -1682,7 +1682,7 @@ void POS::main(void *quorumnet_state, cryptonote::core &core) // // NOTE: Early exit if too early // - auto hf17 = hard_fork_begins(core.get_nettype(), cryptonote::network_version_17_POS); + auto hf17 = hard_fork_begins(core.get_nettype(), cryptonote::hf::hf17_POS); if (!hf17) { for (static bool once = true; once; once = !once) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 910de9a1e09..64234ac1ed1 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -33,6 +33,7 @@ #include #include +#include "common/util.h" #include "tx_pool.h" #include "cryptonote_tx_utils.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" @@ -79,29 +80,29 @@ namespace cryptonote return d; } - uint64_t get_transaction_weight_limit(uint8_t version) + uint64_t get_transaction_weight_limit(hf version) { // from v10, bulletproofs, limit a tx to 50% of the minimum block weight - if (version >= network_version_10_bulletproofs) - return get_min_block_weight(version) / 2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + if (version >= hf::hf10_bulletproofs) + return get_min_block_weight(version) / 2 - COINBASE_BLOB_RESERVED_SIZE; else - return get_min_block_weight(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + return get_min_block_weight(version) - COINBASE_BLOB_RESERVED_SIZE; } } //--------------------------------------------------------------------------------- // warning: bchs is passed here uninitialized, so don't do anything but store it - tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_txpool_max_weight(DEFAULT_TXPOOL_MAX_WEIGHT), m_txpool_weight(0), m_cookie(0) + tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_txpool_max_weight(DEFAULT_MEMPOOL_MAX_WEIGHT), m_txpool_weight(0), m_cookie(0) { } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_duplicated_non_standard_tx(transaction const &tx, uint8_t hard_fork_version) const + bool tx_memory_pool::have_duplicated_non_standard_tx(transaction const &tx, hf version) const { auto &master_node_list = m_blockchain.get_master_node_list(); if (tx.type == txtype::state_change) { tx_extra_master_node_state_change state_change; - if (!get_master_node_state_change_from_tx_extra(tx.extra, state_change, hard_fork_version)) + if (!get_master_node_state_change_from_tx_extra(tx.extra, state_change, version)) { MERROR("Could not get master node state change from tx: " << get_transaction_hash(tx) << ", possibly corrupt tx in your blockchain, rejecting malformed state change"); return false; @@ -126,13 +127,13 @@ namespace cryptonote continue; tx_extra_master_node_state_change pool_tx_state_change; - if (!get_master_node_state_change_from_tx_extra(pool_tx.extra, pool_tx_state_change, hard_fork_version)) + if (!get_master_node_state_change_from_tx_extra(pool_tx.extra, pool_tx_state_change, version)) { LOG_PRINT_L1("Could not get master node state change from tx: " << get_transaction_hash(pool_tx) << ", possibly corrupt tx in the pool"); continue; } - if (hard_fork_version >= cryptonote::network_version_13_checkpointing) + if (version >= hf::hf13_checkpointing) { crypto::public_key master_node_to_change_in_the_pool; bool same_master_node = false; @@ -235,7 +236,7 @@ namespace cryptonote // to set `do_not_relay` to false and starts relaying it (other quorum members do the same). //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, const tx_pool_options &opts, uint8_t hf_version, + bool tx_memory_pool::add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, const tx_pool_options &opts, hf hf_version, uint64_t *flash_rollback_height) { // this should already be called with that lock, but let's make it explicit for clarity @@ -262,7 +263,7 @@ namespace cryptonote if(tx.type == txtype::key_image_unlock) { - if(hf_version >= cryptonote::network_version_18_bns) + if(hf_version >= hf::hf18_bns) { crypto::public_key mnode_key; if (!cryptonote::get_master_node_pubkey_from_tx_extra(tx.extra, mnode_key)) @@ -284,7 +285,7 @@ namespace cryptonote }); if (cit != contributor.locked_contributions.end()) { - if (cit->amount < (master_nodes::SMALL_CONTRIBUTOR_THRESHOLD*COIN) && (block_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER) + if (cit->amount < (master_nodes::SMALL_CONTRIBUTOR_THRESHOLD * beldex::COIN) && (block_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER) { MWARNING("Unlock TX: small contributor trying to unlock node before " << std::to_string(master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER) <<" blocks from the registration height" @@ -307,7 +308,7 @@ namespace cryptonote uint64_t fee, burned; - if (!get_tx_miner_fee(tx, fee, hf_version >= HF_VERSION_FEE_BURNING, &burned)) + if (!get_tx_miner_fee(tx, fee, hf_version >= feature::FEE_BURNING, &burned)) { // This code is a bit convoluted: the above sets `fee`, and returns false for a pre-ringct tx // with a too-low fee, but for ringct (v2+) txes it just sets `fee` but doesn't check it and @@ -325,7 +326,7 @@ namespace cryptonote } size_t tx_weight_limit = get_transaction_weight_limit(hf_version); - if ((!opts.kept_by_block || hf_version >= HF_VERSION_PER_BYTE_FEE) && tx_weight > tx_weight_limit) + if ((!opts.kept_by_block || hf_version >= feature::PER_BYTE_FEE) && tx_weight > tx_weight_limit) { LOG_PRINT_L1("transaction is too heavy: " << tx_weight << " bytes, maximum weight: " << tx_weight_limit); tvc.m_verifivation_failed = true; @@ -508,7 +509,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, const tx_pool_options &opts, uint8_t version) + bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, const tx_pool_options &opts, hf version) { crypto::hash h = null_hash; size_t blob_size = 0; @@ -833,7 +834,7 @@ namespace cryptonote } }; - const auto unexpired = std::time(nullptr) - MEMPOOL_PRUNE_NON_STANDARD_TX_LIFETIME; + const auto unexpired = std::time(nullptr) - static_cast(tools::to_seconds(MEMPOOL_PRUNE_NON_STANDARD_TX_LIFETIME)); for (auto it = m_txs_by_fee_and_receive_time.begin(); it != m_txs_by_fee_and_receive_time.end(); ) { const bool is_standard_tx = !std::get<0>(it->first); @@ -995,8 +996,8 @@ namespace cryptonote m_blockchain.for_all_txpool_txes([this, &remove](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata*) { uint64_t tx_age = time(nullptr) - meta.receive_time; - if((tx_age > CRYPTONOTE_MEMPOOL_TX_LIVETIME && !meta.kept_by_block) || - (tx_age > CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME && meta.kept_by_block) ) + if((tx_age > tools::to_seconds(MEMPOOL_TX_LIVETIME) && !meta.kept_by_block) || + (tx_age > tools::to_seconds(MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME) && meta.kept_by_block) ) { LOG_PRINT_L1("Tx " << txid << " removed from tx pool due to outdated, age: " << tx_age ); auto sorted_it = find_tx_in_sorted_container(txid); @@ -1062,7 +1063,7 @@ namespace cryptonote // if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem // mentioned by smooth where nodes would flush txes at slightly different times, causing // flushed txes to be re-added when received from a node which was just about to flush it - uint64_t max_age = meta.kept_by_block ? CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME : CRYPTONOTE_MEMPOOL_TX_LIVETIME; + uint64_t max_age = tools::to_seconds(meta.kept_by_block ? MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME : MEMPOOL_TX_LIVETIME); if (now - meta.receive_time <= max_age / 2) { try @@ -1800,7 +1801,7 @@ namespace cryptonote } //--------------------------------------------------------------------------------- //TODO: investigate whether boolean return is appropriate - bool tx_memory_pool::fill_block_template(block &bl, size_t median_weight, uint64_t already_generated_coins, size_t &total_weight, uint64_t &raw_fee, uint64_t &expected_reward, uint8_t version, uint64_t height) + bool tx_memory_pool::fill_block_template(block &bl, size_t median_weight, uint64_t already_generated_coins, size_t &total_weight, uint64_t &raw_fee, uint64_t &expected_reward, hf version, uint64_t height) { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); @@ -1819,10 +1820,10 @@ namespace cryptonote return false; } - best_reward = version >= cryptonote::network_version_17_POS ? 0 /*Empty block, starts with 0 fee*/ : reward_parts.base_miner; + best_reward = version >= hf::hf17_POS ? 0 /*Empty block, starts with 0 fee*/ : reward_parts.base_miner; } - size_t const max_total_weight = 2 * median_weight - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + size_t const max_total_weight = 2 * median_weight - COINBASE_BLOB_RESERVED_SIZE; std::unordered_set k_images; LOG_PRINT_L2("Filling block template, median weight " << median_weight << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); @@ -1864,7 +1865,7 @@ namespace cryptonote // NOTE: Use the net fee for comparison (after penalty is applied). // After HF16, penalty is applied on the miner fee. Before, penalty is // applied on the base reward. - if (version >= cryptonote::network_version_17_POS) + if (version >= hf::hf17_POS) { next_reward = next_reward_parts.miner_fee; } @@ -1938,7 +1939,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - size_t tx_memory_pool::validate(uint8_t version) + size_t tx_memory_pool::validate(hf version) { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); @@ -2006,7 +2007,7 @@ namespace cryptonote { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); - m_txpool_max_weight = max_txpool_weight ? max_txpool_weight : DEFAULT_TXPOOL_MAX_WEIGHT; + m_txpool_max_weight = max_txpool_weight ? max_txpool_weight : DEFAULT_MEMPOOL_MAX_WEIGHT; m_txs_by_fee_and_receive_time.clear(); m_spent_key_images.clear(); m_txpool_weight = 0; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 66ef8bc702e..32d91a236d5 100755 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -84,13 +84,13 @@ namespace cryptonote static tx_pool_options from_block() { tx_pool_options o; o.kept_by_block = true; o.relayed = true; return o; } static tx_pool_options from_peer() { tx_pool_options o; o.relayed = true; return o; } static tx_pool_options new_tx(bool do_not_relay = false) { tx_pool_options o; o.do_not_relay = do_not_relay; return o; } - static tx_pool_options new_flash(bool approved, uint8_t hf_version) { + static tx_pool_options new_flash(bool approved, hf hf_version) { tx_pool_options o; o.do_not_relay = !approved; o.approved_flash = approved; - o.fee_percent = FLASH_MINER_TX_FEE_PERCENT; - o.burn_percent = FLASH_BURN_TX_FEE_PERCENT_OLD; - o.burn_fixed = FLASH_BURN_FIXED; + o.fee_percent = beldex::FLASH_MINER_TX_FEE_PERCENT; + o.burn_percent = beldex::FLASH_BURN_TX_FEE_PERCENT_OLD; + o.burn_fixed = beldex::FLASH_BURN_FIXED; return o; } }; @@ -124,7 +124,7 @@ namespace cryptonote tx_memory_pool &operator=(const tx_memory_pool &) = delete; /** - * @copydoc add_tx(transaction&, tx_verification_context&, const tx_pool_options &, uint8_t) + * @copydoc add_tx(transaction&, tx_verification_context&, const tx_pool_options &, hf) * * @param id the transaction's hash * @param tx_weight the transaction's weight @@ -132,7 +132,7 @@ namespace cryptonote * block tx then set this pointer to the required new height: that is, all blocks with height * `block_rollback_height` and above must be removed. */ - bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, const tx_pool_options &opts, uint8_t hf_version, uint64_t *flash_rollback_height = nullptr); + bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, const tx_pool_options &opts, hf hf_version, uint64_t *flash_rollback_height = nullptr); /** * @brief add a transaction to the transaction pool @@ -149,7 +149,7 @@ namespace cryptonote * * @return true if the transaction passes validations, otherwise false */ - bool add_tx(transaction &tx, tx_verification_context& tvc, const tx_pool_options &opts, uint8_t hf_version); + bool add_tx(transaction &tx, tx_verification_context& tvc, const tx_pool_options &opts, hf hf_version); /** * @brief attempts to add a flash transaction to the transaction pool. @@ -389,7 +389,7 @@ namespace cryptonote * * @return true */ - bool fill_block_template(block &bl, size_t median_weight, uint64_t already_generated_coins, size_t &total_weight, uint64_t &raw_fee, uint64_t &expected_reward, uint8_t version, uint64_t height); + bool fill_block_template(block &bl, size_t median_weight, uint64_t already_generated_coins, size_t &total_weight, uint64_t &raw_fee, uint64_t &expected_reward, hf version, uint64_t height); /** * @brief get a list of all transactions in the pool @@ -522,7 +522,7 @@ namespace cryptonote * * @return the number of transactions removed */ - size_t validate(uint8_t version); + size_t validate(hf version); /** * @brief return the cookie @@ -580,7 +580,7 @@ namespace cryptonote * @return true if it already exists * */ - bool have_duplicated_non_standard_tx(transaction const &tx, uint8_t hard_fork_version) const; + bool have_duplicated_non_standard_tx(transaction const &tx, hf version) const; /** * @brief check if any spent key image in a transaction is in the pool diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index b4ba0be8931..ca4e0639e48 100755 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -268,8 +268,8 @@ std::pair block_queue::reserve_span( // if the peer's pruned for the starting block and its unpruned stripe comes next, start downloading from there const uint32_t next_unpruned_height = tools::get_next_unpruned_block_height(span_start_height, blockchain_height, pruning_seed); MDEBUG("reserve_span: next_unpruned_height " << next_unpruned_height << " from " << span_start_height << " and seed " - << epee::string_tools::to_string_hex(pruning_seed) << ", limit " << span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE); - if (next_unpruned_height > span_start_height && next_unpruned_height < span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE) + << epee::string_tools::to_string_hex(pruning_seed) << ", limit " << span_start_height + PRUNING_STRIPE_SIZE); + if (next_unpruned_height > span_start_height && next_unpruned_height < span_start_height + PRUNING_STRIPE_SIZE) { MDEBUG("We can download from next span: ideal height " << span_start_height << ", next unpruned height " << next_unpruned_height << "(+" << next_unpruned_height - span_start_height << "), current seed " << pruning_seed); diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.cpp b/src/cryptonote_protocol/cryptonote_protocol_defs.cpp index 73739338424..32638bbed70 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.cpp @@ -76,7 +76,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(CORE_SYNC_DATA) KV_SERIALIZE(current_height) KV_SERIALIZE(cumulative_difficulty) KV_SERIALIZE_VAL_POD_AS_BLOB(top_id) - KV_SERIALIZE_OPT(top_version, (uint8_t)0) + KV_SERIALIZE_ENUM(top_version) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) KV_SERIALIZE(flash_blocks) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(flash_hash) diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 7765777484d..beddd3ad1ce 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -174,7 +174,7 @@ namespace cryptonote uint64_t current_height; uint64_t cumulative_difficulty; crypto::hash top_id; - uint8_t top_version; + hf top_version; uint32_t pruning_seed; std::vector flash_blocks; std::vector flash_hash; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index b599768f3ac..7bcae0661d1 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -347,7 +347,7 @@ namespace cryptonote if (hshd.current_height > 0) { auto nettype = m_core.get_nettype(); - const uint8_t version = get_network_version(nettype, hshd.current_height - 1); + const hf version = get_network_version(nettype, hshd.current_height - 1); if (version != hshd.top_version) { if (version < hshd.top_version && version == get_network_version(nettype, m_core.get_current_blockchain_height())) @@ -362,7 +362,7 @@ namespace cryptonote if (hshd.pruning_seed) { const uint32_t log_stripes = tools::get_pruning_log_stripes(hshd.pruning_seed); - if (log_stripes != CRYPTONOTE_PRUNING_LOG_STRIPES || tools::get_pruning_stripe(hshd.pruning_seed) > (1u << log_stripes)) + if (log_stripes != PRUNING_LOG_STRIPES || tools::get_pruning_stripe(hshd.pruning_seed) > (1u << log_stripes)) { MINFO("ERROR " << context << " peer claim unexpected pruning seed " << epee::string_tools::to_string_hex(hshd.pruning_seed) << ", disconnecting"); return false; @@ -371,11 +371,10 @@ namespace cryptonote context.m_remote_blockchain_height = hshd.current_height; context.m_pruning_seed = hshd.pruning_seed; -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - context.m_pruning_seed = tools::make_pruning_seed(1 + (context.m_remote_address.as().ip()) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES); - LOG_INFO_CC(context, "New connection posing as pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ", seed address " << &context.m_pruning_seed); -#endif - + if constexpr (PRUNING_DEBUG_SPOOF_SEED) { + context.m_pruning_seed = tools::make_pruning_seed(1 + (context.m_remote_address.as().ip()) % (1 << PRUNING_LOG_STRIPES), PRUNING_LOG_STRIPES); + LOG_INFO_CC(context, "New connection posing as pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ", seed address " << &context.m_pruning_seed); + } // No chain synchronization over hidden networks (tor, i2p, etc.) if(context.m_remote_address.get_zone() != epee::net_utils::zone::public_) { @@ -387,7 +386,7 @@ namespace cryptonote context.m_need_flash_sync = false; // Check for any flash txes being advertised that we don't know about - if (is_hard_fork_at_least(m_core.get_nettype(), HF_VERSION_FLASH, curr_height)) + if (is_hard_fork_at_least(m_core.get_nettype(), feature::FLASH, curr_height)) { if (hshd.flash_blocks.size() != hshd.flash_hash.size()) { @@ -468,19 +467,19 @@ namespace cryptonote auto nettype = m_core.get_nettype(); LOG_PRINT_CCONTEXT_L0("process_payload_sync_data hard_fork_begins"); - auto hf17 = hard_fork_begins(nettype, cryptonote::network_version_17_POS); + auto hf17 = hard_fork_begins(nettype, hf::hf17_POS); LOG_PRINT_CCONTEXT_L0("process_payload_sync_data hf17?"); if (hf17) { LOG_PRINT_CCONTEXT_L0("process_payload_sync_data hf17"); - std::chrono::seconds behindtime = 0 * TARGET_BLOCK_TIME_OLD; + std::chrono::seconds behindtime = 0 * old::TARGET_BLOCK_TIME_12; int64_t diff = static_cast(hshd.current_height) - static_cast(curr_height); uint64_t abs_diff = std::abs(diff); if (curr_height<*hf17){ LOG_PRINT_CCONTEXT_L0("process_payload_sync_data curr_height(*hf17) - static_cast(curr_height); - behindtime = old_diff * TARGET_BLOCK_TIME_OLD; + behindtime = old_diff * old::TARGET_BLOCK_TIME_12; uint64_t max_block_height = std::max(hshd.current_height, curr_height); behindtime = behindtime + ((max_block_height - *hf17) * TARGET_BLOCK_TIME); } else{ @@ -1664,8 +1663,8 @@ namespace cryptonote } progress_message += ")"; } - const uint32_t previous_stripe = tools::get_pruning_stripe(previous_height, target_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t current_stripe = tools::get_pruning_stripe(current_blockchain_height, target_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); + const uint32_t previous_stripe = tools::get_pruning_stripe(previous_height, target_blockchain_height, PRUNING_LOG_STRIPES); + const uint32_t current_stripe = tools::get_pruning_stripe(current_blockchain_height, target_blockchain_height, PRUNING_LOG_STRIPES); std::string timing_message = ""; if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info")) timing_message = std::string(" (") + std::to_string(dt.count()) + " sec, " @@ -1821,7 +1820,7 @@ skip: MTRACE(n_syncing << " syncing, " << n_synced << " synced"); // if we're at max out peers, and not enough are syncing - if (n_synced + n_syncing >= m_max_out_peers && n_syncing < P2P_DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid()) + if (n_synced + n_syncing >= m_max_out_peers && n_syncing < p2p::DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid()) { if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ MINFO(ctx << "dropping synced peer, " << n_syncing << " syncing, " << n_synced << " synced"); @@ -1999,7 +1998,7 @@ skip: ++n_peers_on_next_stripe; return true; }); - const uint32_t distance = (peer_stripe + (1<= m_max_out_peers && n_peers_on_next_stripe == 0) || (distance > 1 && n_peers_on_next_stripe <= 2) || distance > 2) { MDEBUG(context << "we want seed " << next_stripe << ", and either " << n_out_peers << " is at max out peers (" @@ -2053,7 +2052,7 @@ skip: size_t size = m_block_queue.get_data_size(); const uint64_t bc_height = m_core.get_current_blockchain_height(); const auto next_needed_pruning_stripe = get_next_needed_pruning_stripe(); - const uint32_t add_stripe = tools::get_pruning_stripe(bc_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); + const uint32_t add_stripe = tools::get_pruning_stripe(bc_height, context.m_remote_blockchain_height, PRUNING_LOG_STRIPES); const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed); const size_t block_queue_size_threshold = m_block_download_max_size ? m_block_download_max_size : BLOCK_QUEUE_SIZE_THRESHOLD; bool queue_proceed = nspans < BLOCK_QUEUE_NSPANS_THRESHOLD || size < block_queue_size_threshold; @@ -2068,7 +2067,7 @@ skip: bool stripe_proceed_main = (add_stripe == 0 || peer_stripe == 0 || add_stripe == peer_stripe) && (next_block_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS || next_needed_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS); bool stripe_proceed_secondary = tools::has_unpruned_block(next_block_height, context.m_remote_blockchain_height, context.m_pruning_seed); bool proceed = stripe_proceed_main || (queue_proceed && stripe_proceed_secondary); - if (!stripe_proceed_main && !stripe_proceed_secondary && should_drop_connection(context, tools::get_pruning_stripe(next_block_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES))) + if (!stripe_proceed_main && !stripe_proceed_secondary && should_drop_connection(context, tools::get_pruning_stripe(next_block_height, context.m_remote_blockchain_height, PRUNING_LOG_STRIPES))) { if (!context.m_is_income) m_p2p->add_used_stripe_peer(context); @@ -2198,7 +2197,7 @@ skip: MDEBUG(context << " span from " << first_block_height << ": " << span.first << "/" << span.second); if (span.second > 0) { - const uint32_t stripe = tools::get_pruning_stripe(span.first, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); + const uint32_t stripe = tools::get_pruning_stripe(span.first, context.m_remote_blockchain_height, PRUNING_LOG_STRIPES); if (context.m_pruning_seed && stripe != tools::get_pruning_stripe(context.m_pruning_seed)) { MDEBUG(context << " starting early on next seed (" << span.first << " with stripe " << stripe << @@ -2450,7 +2449,7 @@ skip: } MDEBUG(context << "first block hash " << arg.m_block_ids.front() << ", last " << arg.m_block_ids.back()); - if (arg.total_height >= CRYPTONOTE_MAX_BLOCK_NUMBER || arg.m_block_ids.size() >= CRYPTONOTE_MAX_BLOCK_NUMBER) + if (arg.total_height >= MAX_BLOCK_NUMBER || arg.m_block_ids.size() >= MAX_BLOCK_NUMBER) { LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_CHAIN_ENTRY, with total_height=" << arg.total_height << " and block_ids=" << arg.m_block_ids.size()); drop_connection(context, false, false); @@ -2623,7 +2622,7 @@ skip: // flash data that got sent to us (we may have additional flash info, or may have rejected some // of the incoming flash data). arg.flashes.clear(); - if (is_hard_fork_at_least(m_core.get_nettype(), HF_VERSION_FLASH, m_core.get_current_blockchain_height())) + if (is_hard_fork_at_least(m_core.get_nettype(), feature::FLASH, m_core.get_current_blockchain_height())) { auto &pool = m_core.get_pool(); auto lock = pool.flash_shared_lock(); @@ -2669,13 +2668,13 @@ skip: uint64_t blockchain_height = m_core.get_target_blockchain_height(); // if we don't know the remote chain size yet, assume infinitely large so we get the right stripe if we're not near the tip if (blockchain_height == 0) - blockchain_height = CRYPTONOTE_MAX_BLOCK_NUMBER; - const uint32_t next_pruning_stripe = tools::get_pruning_stripe(want_height, blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); + blockchain_height = MAX_BLOCK_NUMBER; + const uint32_t next_pruning_stripe = tools::get_pruning_stripe(want_height, blockchain_height, PRUNING_LOG_STRIPES); if (next_pruning_stripe == 0) return std::make_pair(0, 0); // if we already have a few peers on this stripe, but none on next one, try next one unsigned int n_next = 0, n_subsequent = 0, n_others = 0; - const uint32_t subsequent_pruning_stripe = 1 + next_pruning_stripe % (1<for_each_connection([&](const connection_context &context, nodetool::peerid_type peer_id, uint32_t support_flags) { if (context.m_state >= cryptonote_connection_context::state_synchronizing) { @@ -2694,7 +2693,7 @@ skip: want_height_from_blockchain << " from blockchain, " << want_height_from_block_queue << " from block queue), stripe " << next_pruning_stripe << " (" << n_next << "/" << m_max_out_peers << " on it and " << n_subsequent << " on " << subsequent_pruning_stripe << ", " << n_others << " others) -> " << ret_stripe << " (+" << - (ret_stripe - next_pruning_stripe + (1 << CRYPTONOTE_PRUNING_LOG_STRIPES)) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) << + (ret_stripe - next_pruning_stripe + (1 << PRUNING_LOG_STRIPES)) % (1 << PRUNING_LOG_STRIPES) << "), current peers " << po); return std::make_pair(next_pruning_stripe, ret_stripe); } diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index b0f455d8b45..634870afb09 100755 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -46,20 +46,12 @@ #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "net.p2p.tx" -namespace cryptonote -{ -namespace levin +namespace cryptonote::levin { namespace { constexpr std::size_t connection_id_reserve_size = 100; - constexpr const std::chrono::minutes noise_min_epoch{CRYPTONOTE_NOISE_MIN_EPOCH}; - constexpr const std::chrono::seconds noise_epoch_range{CRYPTONOTE_NOISE_EPOCH_RANGE}; - - constexpr const std::chrono::seconds noise_min_delay{CRYPTONOTE_NOISE_MIN_DELAY}; - constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE}; - /*! Select a randomized duration from 0 to `range`. The precision will be to the systems `steady_clock`. As an example, supplying 3 seconds to this function will select a duration from [0, 3] seconds, and the increments @@ -199,7 +191,7 @@ namespace levin connection_count(0), is_public(is_public) { - for (std::size_t count = 0; !noise.view.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count) + for (std::size_t count = 0; !noise.view.empty() && count < NOISE_CHANNELS; ++count) channels.emplace_back(io_service); } @@ -311,7 +303,7 @@ namespace levin noise_channel& channel = zone_->channels.at(channel_); assert(channel.strand.running_in_this_thread()); static_assert( - CRYPTONOTE_MAX_FRAGMENTS <= (noise_min_epoch / (noise_min_delay + noise_delay_range)), + MAX_FRAGMENTS <= static_cast(NOISE_MIN_EPOCH / (NOISE_MIN_DELAY + NOISE_DELAY_RANGE)), "Max fragments more than the max that can be sent in an epoch" ); @@ -403,7 +395,7 @@ namespace levin return; noise_channel& channel = zone->channels.at(index); - channel.next_noise.expires_at(start + noise_min_delay + random_duration(noise_delay_range)); + channel.next_noise.expires_at(start + NOISE_MIN_DELAY + random_duration(NOISE_DELAY_RANGE)); channel.next_noise.async_wait( channel.strand.wrap(send_noise{std::move(zone), index}) ); @@ -497,7 +489,7 @@ namespace levin if (!zone_->noise.view.empty()) { const auto now = std::chrono::steady_clock::now(); - start_epoch{zone_, noise_min_epoch, noise_epoch_range, CRYPTONOTE_NOISE_CHANNELS}(); + start_epoch{zone_, NOISE_MIN_EPOCH, NOISE_EPOCH_RANGE, NOISE_CHANNELS}(); for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel) send_noise::wait(now, zone_, channel); } @@ -511,12 +503,12 @@ namespace levin if (!zone_) return {false, false}; - return {!zone_->noise.view.empty(), CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count}; + return {!zone_->noise.view.empty(), NOISE_CHANNELS <= zone_->connection_count}; } void notify::new_out_connection() { - if (!zone_ || zone_->noise.view.empty() || CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count) + if (!zone_ || zone_->noise.view.empty() || NOISE_CHANNELS <= zone_->connection_count) return; zone_->strand.dispatch( @@ -557,7 +549,7 @@ namespace levin MINFO("send_txs covert send in \"noise\" channel"); // covert send in "noise" channel static_assert( - CRYPTONOTE_MAX_FRAGMENTS * CRYPTONOTE_NOISE_BYTES <= LEVIN_DEFAULT_MAX_PACKET_SIZE, "most nodes will reject this fragment setting" + MAX_FRAGMENTS * NOISE_BYTES <= LEVIN_DEFAULT_MAX_PACKET_SIZE, "most nodes will reject this fragment setting" ); // padding is not useful when using noise mode @@ -565,7 +557,7 @@ namespace levin epee::shared_sv message{epee::levin::make_fragmented_notify( zone_->noise.view, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan(payload) )}; - if (CRYPTONOTE_MAX_FRAGMENTS * zone_->noise.size() < message.size()) + if (MAX_FRAGMENTS * zone_->noise.size() < message.size()) { MERROR("notify::send_txs provided message exceeding covert fragment size"); return false; @@ -591,5 +583,4 @@ namespace levin return true; } -} // levin -} // net +} // cryptonote::levin diff --git a/src/cryptonote_protocol/quorumnet.cpp b/src/cryptonote_protocol/quorumnet.cpp index 7bcc4e8d113..da3956ac8ad 100755 --- a/src/cryptonote_protocol/quorumnet.cpp +++ b/src/cryptonote_protocol/quorumnet.cpp @@ -859,7 +859,7 @@ void handle_flash(Message& m, QnetState& qnet) { auto local_height = qnet.core.get_current_blockchain_height(); auto hf_version = get_network_version(qnet.core.get_nettype(), local_height); - if (hf_version < HF_VERSION_FLASH) { + if (hf_version < cryptonote::feature::FLASH) { MWARNING("Rejecting flash message: flash is not available for hardfork " << (int) hf_version); if (tag) m.send_back("bl.nostart", bt_serialize(bt_dict{{"!", tag}, {"e", "Invalid flash authorization height"sv}})); diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h index 7666a5a5521..ec76fe39870 100755 --- a/src/daemon/command_line_args.h +++ b/src/daemon/command_line_args.h @@ -40,12 +40,12 @@ namespace daemon_args const command_line::arg_descriptor arg_config_file = { "config-file", "Specify configuration file", - "/" CRYPTONOTE_NAME ".conf"}; + "/" + std::string{cryptonote::CONF_FILENAME}}; const command_line::arg_descriptor arg_log_file = { "log-file", "Specify log file", - "/" CRYPTONOTE_NAME ".log"}; + "/" + std::string{cryptonote::LOG_FILENAME}}; const command_line::arg_descriptor arg_max_log_file_size = { "max-log-file-size" , "Specify maximum log file size [B]" diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 6492e3a3fb9..8e51adc3add 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -490,12 +490,12 @@ bool command_parser_executor::start_mining(const std::vector& args) cryptonote::address_parse_info info; cryptonote::network_type nettype; - if (cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, args.front())) - nettype = cryptonote::MAINNET; - else if (cryptonote::get_account_address_from_str(info, cryptonote::TESTNET, args.front())) - nettype = cryptonote::TESTNET; - else if (cryptonote::get_account_address_from_str(info, cryptonote::DEVNET, args.front())) - nettype = cryptonote::DEVNET; + if (cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, args.front())) + nettype = cryptonote::network_type::MAINNET; + else if (cryptonote::get_account_address_from_str(info, cryptonote::network_type::TESTNET, args.front())) + nettype = cryptonote::network_type::TESTNET; + else if (cryptonote::get_account_address_from_str(info, cryptonote::network_type::DEVNET, args.front())) + nettype = cryptonote::network_type::DEVNET; else { std::cout << "target account address has wrong format" << std::endl; @@ -506,8 +506,8 @@ bool command_parser_executor::start_mining(const std::vector& args) tools::fail_msg_writer() << "subaddress for mining reward is not yet supported!"; return true; } - if(nettype != cryptonote::MAINNET) - std::cout << "Mining to a " << (nettype == cryptonote::TESTNET ? "testnet" : "devnet") << " address, make sure this is intentional!"; + if(nettype != cryptonote::network_type::MAINNET) + std::cout << "Mining to a " << (nettype == cryptonote::network_type::TESTNET ? "testnet" : "devnet") << " address, make sure this is intentional!"; std::string_view threads_val = tools::find_prefixed_value(args.begin() + 1, args.end(), "threads="sv); std::string_view num_blocks_val = tools::find_prefixed_value(args.begin() + 1, args.end(), "num_blocks="sv); @@ -667,7 +667,7 @@ bool command_parser_executor::ban(const std::vector& args) { if (args.size() != 1 && args.size() != 2) return false; std::string ip = args[0]; - time_t seconds = P2P_IP_BLOCKTIME; + time_t seconds = tools::to_seconds(cryptonote::p2p::IP_BLOCK_TIME); if (args.size() > 1) { try diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index d359a22bb47..6b4c31849ca 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -160,11 +160,11 @@ daemon::daemon(boost::program_options::variables_map vm_) : if (restricted && restricted_rpc_port != 0) std::swap(main_rpc_port, restricted_rpc_port); else if (command_line::get_arg(vm, cryptonote::arg_testnet_on)) - main_rpc_port = config::testnet::RPC_DEFAULT_PORT; + main_rpc_port = cryptonote::config::testnet::RPC_DEFAULT_PORT; else if (command_line::get_arg(vm, cryptonote::arg_devnet_on)) - main_rpc_port = config::devnet::RPC_DEFAULT_PORT; + main_rpc_port = cryptonote::config::devnet::RPC_DEFAULT_PORT; else - main_rpc_port = config::RPC_DEFAULT_PORT; + main_rpc_port = cryptonote::config::RPC_DEFAULT_PORT; } if (main_rpc_port && main_rpc_port == restricted_rpc_port) restricted = true; diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index a450740c2c0..20a5eeb31d1 100755 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -171,8 +171,8 @@ int main(int argc, char const * argv[]) // want to rename if we find one (the data-dir migration happens later): std::list> potential; if (std::error_code ec; fs::exists(data_dir, ec)) { - potential.emplace_back(data_dir / CRYPTONOTE_NAME ".conf", false); - potential.emplace_back(data_dir / "beldex.conf", true); + potential.emplace_back(data_dir / cryptonote::CONF_FILENAME, false); + potential.emplace_back(data_dir / cryptonote::CONF_FILENAME, true); } else if (command_line::is_arg_defaulted(vm, cryptonote::arg_data_dir)) { // If we weren't given an explict command-line data-dir then we also need to check the // legacy data directory. (We will rename it, later, but we have to check it *first* @@ -185,14 +185,14 @@ int main(int argc, char const * argv[]) else if (command_line::get_arg(vm, cryptonote::arg_devnet_on)) old_data_dir /= "devnet"; else if (command_line::get_arg(vm, cryptonote::arg_regtest_on)) old_data_dir /= "regtest"; - potential.emplace_back(old_data_dir / CRYPTONOTE_NAME ".conf", false); - potential.emplace_back(old_data_dir / "beldex.conf", true); + potential.emplace_back(old_data_dir / cryptonote::CONF_FILENAME, false); + potential.emplace_back(old_data_dir / cryptonote::CONF_FILENAME, true); } for (auto& [conf, rename] : potential) { if (std::error_code ec; fs::exists(conf, ec)) { if (rename) { fs::path renamed = conf; - renamed.replace_filename(CRYPTONOTE_NAME ".conf"); + renamed.replace_filename(cryptonote::CONF_FILENAME); assert(renamed != conf); if (fs::rename(conf, renamed, ec); ec) { std::cerr << RED << "Failed to migrate " << conf << " -> " << renamed << @@ -299,11 +299,11 @@ int main(int argc, char const * argv[]) po::notify(vm); // log_file_path - // default: /.log + // default: /beldex.log // if log-file argument given: // absolute path // relative path: relative to data_dir - auto log_file_path = data_dir / CRYPTONOTE_NAME ".log"; + auto log_file_path = data_dir / cryptonote::LOG_FILENAME; if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_file)) log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file); if (log_file_path.is_relative()) @@ -338,9 +338,9 @@ int main(int argc, char const * argv[]) auto rpc_port = command_line::get_arg(vm, cryptonote::rpc::http_server::arg_rpc_bind_port); if (rpc_port == 0) rpc_port = - command_line::get_arg(vm, cryptonote::arg_testnet_on) ? config::testnet::RPC_DEFAULT_PORT : - command_line::get_arg(vm, cryptonote::arg_devnet_on) ? config::devnet::RPC_DEFAULT_PORT : - config::RPC_DEFAULT_PORT; + command_line::get_arg(vm, cryptonote::arg_testnet_on) ? cryptonote::config::testnet::RPC_DEFAULT_PORT : + command_line::get_arg(vm, cryptonote::arg_devnet_on) ? cryptonote::config::devnet::RPC_DEFAULT_PORT : + cryptonote::config::RPC_DEFAULT_PORT; rpc_addr = rpc_config.bind_ip.value_or("127.0.0.1") + ":" + std::to_string(rpc_port); } else { rpc_addr = command_line::get_arg(vm, cryptonote::rpc::http_server::arg_rpc_admin)[0]; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 39fe1f7dbbb..6248fc7062e 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -37,6 +37,7 @@ #include "daemon/rpc_command_executor.h" #include "common/median.h" #include "epee/int-util.h" +#include "beldex_economy.h" #include "rpc/core_rpc_server_commands_defs.h" #include "cryptonote_core/master_node_rules.h" #include "cryptonote_basic/hardfork.h" @@ -58,6 +59,8 @@ using namespace cryptonote::rpc; +using cryptonote::hf; + namespace daemonize { namespace { @@ -360,8 +363,8 @@ bool rpc_command_executor::print_peer_list_stats() { return false; tools::msg_writer() - << "White list size: " << res.white_list.size() << "/" << P2P_LOCAL_WHITE_PEERLIST_LIMIT << " (" << res.white_list.size() * 100.0 / P2P_LOCAL_WHITE_PEERLIST_LIMIT << "%)" << std::endl - << "Gray list size: " << res.gray_list.size() << "/" << P2P_LOCAL_GRAY_PEERLIST_LIMIT << " (" << res.gray_list.size() * 100.0 / P2P_LOCAL_GRAY_PEERLIST_LIMIT << "%)"; + << "White list size: " << res.white_list.size() << "/" << cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT << " (" << res.white_list.size() * 100.0 / cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT << "%)" << std::endl + << "Gray list size: " << res.gray_list.size() << "/" << cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT << " (" << res.gray_list.size() * 100.0 / cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT << "%)"; return true; } @@ -534,16 +537,16 @@ bool rpc_command_executor::show_status() { str << " was used"; } - if (hfres.version < HF_VERSION_POS && !has_mining_info) + if (hfres.version < cryptonote::feature::POS && !has_mining_info) str << ", mining info unavailable"; if (has_mining_info && !mining_busy && mres.active) str << ", mining at " << get_mining_speed(mres.speed); - if (hfres.version < HF_VERSION_POS) + if (hfres.version < cryptonote::feature::POS) str << ", net hash " << get_mining_speed(ires.difficulty / ires.target); str << ", v" << (ires.version.empty() ? "?.?.?" : ires.version); - str << "(net v" << +hfres.version << ')'; + str << "(net v" << static_cast(hfres.version) << ')'; if (hfres.earliest_height) print_fork_extra_info(str, *hfres.earliest_height, net_height, ires.target); @@ -1055,7 +1058,7 @@ bool rpc_command_executor::print_transaction_pool_stats() { else { uint64_t backlog = (res.pool_stats.bytes_total + full_reward_zone - 1) / full_reward_zone; - backlog_message = fmt::format("estimated {} block ({} minutes ) backlog", backlog, backlog * TARGET_BLOCK_TIME / 1min); + backlog_message = fmt::format("estimated {} block ({} minutes ) backlog", backlog, backlog * cryptonote::TARGET_BLOCK_TIME / 1min); } tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ", median " << res.pool_stats.bytes_med << ")" << std::endl @@ -1399,7 +1402,7 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, tools::msg_writer() << "Time span: " << tools::get_human_readable_timespan(std::chrono::seconds(dt)); cryptonote::difficulty_type start_difficulty = bhres.block_headers.back().difficulty; if (start_difficulty > 0) - tools::msg_writer() << "Approximated " << 100.f * tools::to_seconds(TARGET_BLOCK_TIME_OLD) * chain.length / dt << "% of network hash rate"; //old block time + tools::msg_writer() << "Approximated " << 100.f * tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12) * chain.length / dt << "% of network hash rate"; //old block time else tools::fail_msg_writer() << "Bad cumulative difficulty reported by dameon"; } @@ -1418,7 +1421,7 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) if (!invoke({}, ires, "Failed to retrieve node info") || !invoke({}, feres, "Failed to retrieve current fee info") || - !invoke({HF_VERSION_PER_BYTE_FEE}, hfres, "Failed to retrieve hard fork info")) + !invoke({static_cast(cryptonote::feature::PER_BYTE_FEE)}, hfres, "Failed to retrieve hard fork info")) return false; tools::msg_writer() << "Height: " << ires.height << ", diff " << ires.difficulty << ", cum. diff " << ires.cumulative_difficulty @@ -1525,7 +1528,7 @@ bool rpc_command_executor::sync_info() for (const auto &s: res.spans) { std::string address = epee::string_tools::pad_string(s.remote_address, 24); - std::string pruning_seed = epee::string_tools::to_string_hex(tools::get_pruning_seed(s.start_block_height, std::numeric_limits::max(), CRYPTONOTE_PRUNING_LOG_STRIPES)); + std::string pruning_seed = epee::string_tools::to_string_hex(tools::get_pruning_seed(s.start_block_height, std::numeric_limits::max(), cryptonote::PRUNING_LOG_STRIPES)); if (s.size == 0) { tools::success_msg_writer() << address << " " << s.nblocks << "/" << pruning_seed << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ") -"; @@ -1582,7 +1585,7 @@ static void print_participation_history(std::ostringstream &stream, std::vector< } } -static void append_printable_master_node_list_entry(cryptonote::network_type nettype, bool detailed_view, uint64_t blockchain_height, uint64_t entry_index, GET_MASTER_NODES::response::entry const &entry, std::string &buffer,uint8_t hf_version) +static void append_printable_master_node_list_entry(cryptonote::network_type nettype, bool detailed_view, uint64_t blockchain_height, uint64_t entry_index, GET_MASTER_NODES::response::entry const &entry, std::string &buffer) { const char indent1[] = " "; const char indent2[] = " "; @@ -1607,21 +1610,22 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net uint64_t const now = time(nullptr); { uint64_t expiry_height = 0; - if (entry.registration_hf_version >= cryptonote::network_version_11_infinite_staking) + auto reg_hf = static_cast(entry.registration_hf_version); + if (reg_hf >= hf::hf11_infinite_staking) { expiry_height = entry.requested_unlock_height; } - else if (entry.registration_hf_version >= cryptonote::network_version_10_bulletproofs) + else if (reg_hf >= hf::hf10_bulletproofs) { expiry_height = entry.registration_height + master_nodes::staking_num_lock_blocks(nettype,entry.registration_hf_version); - expiry_height += STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS; + expiry_height += cryptonote::old::STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS; } else { expiry_height = entry.registration_height + master_nodes::staking_num_lock_blocks(nettype,entry.registration_hf_version); } - stream << indent2 << "Registration: Hardfork Version: " << entry.registration_hf_version << "; Height: " << entry.registration_height << "; Expiry: "; + stream << indent2 << "Registration: Hardfork Version: " << static_cast(entry.registration_hf_version) << "; Height: " << entry.registration_height << "; Expiry: "; if (expiry_height == master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) { stream << "Staking Infinitely (stake unlock not requested)\n"; @@ -1629,7 +1633,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net else { uint64_t delta_height = (blockchain_height >= expiry_height) ? 0 : expiry_height - blockchain_height; - uint64_t expiry_epoch_time = now + (delta_height * tools::to_seconds(TARGET_BLOCK_TIME)); + uint64_t expiry_epoch_time = now + (delta_height * tools::to_seconds(cryptonote::TARGET_BLOCK_TIME)); stream << expiry_height << " (in " << delta_height << ") blocks\n"; stream << indent2 << "Expiry Date (estimated): " << get_date_time(expiry_epoch_time) << " (" << get_human_time_ago(expiry_epoch_time, now) << ")\n"; } @@ -1642,7 +1646,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net if (detailed_view) // Print operator information { - stream << indent2 << "Operator Cut (\% Of Reward): " << to_string_rounded((entry.portions_for_operator / (double)STAKING_PORTIONS) * 100.0, 2) << "%\n"; + stream << indent2 << "Operator Cut (\% Of Reward): " << to_string_rounded((entry.portions_for_operator / (double)cryptonote::old::STAKING_PORTIONS) * 100.0, 2) << "%\n"; stream << indent2 << "Operator Address: " << entry.operator_address << "\n"; } @@ -1750,7 +1754,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net if (entry.active) { stream << indent2 << "Current Status: ACTIVE\n"; stream << indent2 << "Downtime Credits: " << entry.earned_downtime_blocks << " blocks" - << " (about " << to_string_rounded(entry.earned_downtime_blocks / (double) BLOCKS_PER_HOUR, 2) << " hours)"; + << " (about " << to_string_rounded(entry.earned_downtime_blocks / (double) cryptonote::BLOCKS_PER_HOUR, 2) << " hours)"; if (entry.earned_downtime_blocks < master_nodes::DECOMMISSION_MINIMUM) stream << " (Note: " << master_nodes::DECOMMISSION_MINIMUM << " blocks required to enable deregistration delay)"; @@ -1799,10 +1803,10 @@ bool rpc_command_executor::print_mn(const std::vector &args) return false; cryptonote::network_type nettype = - get_info_res.mainnet ? cryptonote::MAINNET : - get_info_res.devnet ? cryptonote::DEVNET : - get_info_res.testnet ? cryptonote::TESTNET : - cryptonote::UNDEFINED; + get_info_res.mainnet ? cryptonote::network_type::MAINNET : + get_info_res.devnet ? cryptonote::network_type::DEVNET : + get_info_res.testnet ? cryptonote::network_type::TESTNET : + cryptonote::network_type::UNDEFINED; uint64_t curr_height = get_info_res.height; std::vector unregistered; @@ -1865,17 +1869,16 @@ bool rpc_command_executor::print_mn(const std::vector &args) std::string unregistered_print_data; std::string registered_print_data; - const uint8_t hf_version = cryptonote::get_network_version(nettype, curr_height); for (size_t i = 0; i < unregistered.size(); i++) { if (i) unregistered_print_data.append("\n"); - append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, *unregistered[i], unregistered_print_data, hf_version); + append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, *unregistered[i], unregistered_print_data); } for (size_t i = 0; i < registered.size(); i++) { if (i) registered_print_data.append("\n"); - append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, *registered[i], registered_print_data,hf_version); + append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, *registered[i], registered_print_data); } if (unregistered.size() > 0) @@ -1954,10 +1957,10 @@ static uint64_t get_amount_to_make_portions(uint64_t amount, uint64_t portions) { uint64_t lo, hi, resulthi, resultlo; lo = mul128(amount, portions, &hi); - if (lo > UINT64_MAX - (STAKING_PORTIONS - 1)) + if (lo > UINT64_MAX - (cryptonote::old::STAKING_PORTIONS - 1)) hi++; - lo += STAKING_PORTIONS-1; - div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo); + lo += cryptonote::old::STAKING_PORTIONS-1; + div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); return resultlo; } @@ -1965,7 +1968,7 @@ static uint64_t get_actual_amount(uint64_t amount, uint64_t portions) { uint64_t lo, hi, resulthi, resultlo; lo = mul128(amount, portions, &hi); - div128_64(hi, lo, STAKING_PORTIONS, &resulthi, &resultlo); + div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); return resultlo; } @@ -2009,16 +2012,16 @@ bool rpc_command_executor::prepare_registration(bool force_registration) } uint64_t block_height = std::max(res.height, res.target_height); - uint8_t hf_version = hf_res.version; + auto hf_version = hf_res.version; #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) cryptonote::network_type const nettype = cryptonote::FAKECHAIN; #else cryptonote::network_type const nettype = - res.mainnet ? cryptonote::MAINNET : - res.devnet ? cryptonote::DEVNET : - res.testnet ? cryptonote::TESTNET : - res.nettype == "fakechain" ? cryptonote::FAKECHAIN : - cryptonote::UNDEFINED; + res.mainnet ? cryptonote::network_type::MAINNET : + res.devnet ? cryptonote::network_type::DEVNET : + res.testnet ? cryptonote::network_type::TESTNET : + res.nettype == "fakechain" ? cryptonote::network_type::FAKECHAIN : + cryptonote::network_type::UNDEFINED; #endif // Query the latest block we've synced and check that the timestamp is sensible, issue a warning if not @@ -2058,7 +2061,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) master_nodes::get_staking_requirement(block_height + 30 * 24)); // allow 1 day // anything less than DUST will be added to operator stake - const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS; + const uint64_t DUST = beldex::MAX_NUMBER_OF_CONTRIBUTORS; std::cout << "\n BELDEX MASTER NODE REGISTRATION "<< std::endl; std::cout << "Current staking requirement: " << cryptonote::print_money(staking_requirement) << " " << cryptonote::get_unit() << std::endl; @@ -2082,8 +2085,8 @@ bool rpc_command_executor::prepare_registration(bool force_registration) register_step prev_step = register_step::is_open_stake__operator_address_to_reserve; bool is_solo_stake; size_t num_participants = 1; - uint64_t operator_fee_portions = STAKING_PORTIONS; - uint64_t portions_remaining = STAKING_PORTIONS; + uint64_t operator_fee_portions = cryptonote::old::STAKING_PORTIONS; + uint64_t portions_remaining = cryptonote::old::STAKING_PORTIONS; uint64_t total_reserved_contributions = 0; std::vector addresses; std::vector contributions; @@ -2120,7 +2123,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) { std::string address_str; state.addresses.push_back(address_str); - state.contributions.push_back(STAKING_PORTIONS); + state.contributions.push_back(cryptonote::old::STAKING_PORTIONS); state.portions_remaining = 0; state.total_reserved_contributions += staking_requirement; state.prev_step = step; @@ -2189,7 +2192,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) case register_step::is_open_stake__how_many_more_contributors: { - std::string prompt = "Number of additional contributors [1-" + std::to_string(MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]"; + std::string prompt = "Number of additional contributors [1-" + std::to_string(beldex::MAX_NUMBER_OF_CONTRIBUTORS - 1) + "]"; std::string input; last_input_result = input_line_back_cancel_get_input(prompt.c_str(), input); @@ -2203,9 +2206,9 @@ bool rpc_command_executor::prepare_registration(bool force_registration) } long additional_contributors = strtol(input.c_str(), NULL, 10 /*base 10*/); - if (additional_contributors < 1 || additional_contributors > (MAX_NUMBER_OF_CONTRIBUTORS - 1)) + if (additional_contributors < 1 || additional_contributors > (beldex::MAX_NUMBER_OF_CONTRIBUTORS - 1)) { - std::cout << "Invalid value. Should be between [1-" << (MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl; + std::cout << "Invalid value. Should be between [1-" << (beldex::MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl; continue; } @@ -2412,8 +2415,8 @@ bool rpc_command_executor::prepare_registration(bool force_registration) assert(state.addresses.size() == state.contributions.size()); const uint64_t amount_left = staking_requirement - state.total_reserved_contributions; - std::cout << "Summary:" << std::endl; - std::cout << "Operating costs as % of reward: " << (state.operator_fee_portions * 100.0 / static_cast(STAKING_PORTIONS)) << "%" << std::endl; + std::cout << "nRegistration Summary:" << std::endl; + std::cout << "Operating costs as % of reward: " << (state.operator_fee_portions * 100.0 / static_cast(cryptonote::old::STAKING_PORTIONS)) << "%" << std::endl; printf("%-16s%-9s%-19s%-s\n", "Contributor", "Address", "Contribution", "Contribution(%)"); printf("%-16s%-9s%-19s%-s\n", "___________", "_______", "____________", "_______________"); @@ -2423,7 +2426,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) uint64_t amount = get_actual_amount(staking_requirement, state.contributions[i]); if (amount_left <= DUST && i == 0) amount += amount_left; // add dust to the operator. - printf("%-16s%-9s%-19s%-.9f\n", participant_name.c_str(), state.addresses[i].substr(0, 6).c_str(), cryptonote::print_money(amount).c_str(), (double)state.contributions[i] * 100 / (double)STAKING_PORTIONS); + printf("%-16s%-9s%-19s%-.9f\n", participant_name.c_str(), state.addresses[i].substr(0, 6).c_str(), cryptonote::print_money(amount).c_str(), (double)state.contributions[i] * 100 / (double)cryptonote::old::STAKING_PORTIONS); } if (amount_left > DUST) diff --git a/src/daemonizer/windows_daemonizer.inl b/src/daemonizer/windows_daemonizer.inl index a40dda30aba..18de6e81443 100755 --- a/src/daemonizer/windows_daemonizer.inl +++ b/src/daemonizer/windows_daemonizer.inl @@ -100,13 +100,13 @@ namespace daemonizer if (admin) { return fs::absolute( - tools::get_special_folder_path(CSIDL_COMMON_APPDATA, true) / CRYPTONOTE_NAME + tools::get_special_folder_path(CSIDL_COMMON_APPDATA, true) / cryptonote::DATA_DIRNAME ); } else { return fs::absolute( - tools::get_special_folder_path(CSIDL_APPDATA, true) / CRYPTONOTE_NAME + tools::get_special_folder_path(CSIDL_APPDATA, true) / cryptonote::DATA_DIRNAME ); } } diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp index 5f4f57bd4ac..0aa97738931 100755 --- a/src/debug_utilities/cn_deserialize.cpp +++ b/src/debug_utilities/cn_deserialize.cpp @@ -115,6 +115,19 @@ static void print_extra_fields(const std::vector &fi } } +constexpr static std::string_view network_type_str(network_type nettype) +{ + switch(nettype) + { + case network_type::MAINNET: return "Mainnet"sv; + case network_type::TESTNET: return "Testnet"sv; + case network_type::DEVNET: return "Devnet"sv; + case network_type::FAKECHAIN: return "Fakenet"sv; + case network_type::UNDEFINED: return "Undefined Net"sv; + } + return "Unhandled Net"sv; +} + int main(int argc, char* argv[]) { uint32_t log_level = 0; @@ -214,14 +227,14 @@ int main(int argc, char* argv[]) else { bool addr_decoded = false; - for (uint8_t nettype = MAINNET; nettype < DEVNET + 1; nettype++) + for (auto nettype : {network_type::MAINNET, network_type::TESTNET, network_type::DEVNET}) { cryptonote::address_parse_info addr_info = {}; if (cryptonote::get_account_address_from_str(addr_info, static_cast(nettype), input)) { addr_decoded = true; cryptonote::account_public_address const &address = addr_info.address; - std::cout << "Network Type: " << cryptonote::network_type_str(static_cast(nettype)) << "\n"; + std::cout << "Network Type: " << network_type_str(static_cast(nettype)) << "\n"; std::cout << "Address: " << input << "\n"; std::cout << "Subaddress: " << (addr_info.is_subaddress ? "Yes" : "No") << "\n"; std::cout << "Payment ID: " << (addr_info.has_payment_id ? tools::type_to_hex(addr_info.payment_id) : "(none)") << "\n"; diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index ee157bbc612..9e262a24fe8 100755 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -100,7 +100,7 @@ namespace hw::core { epee::mlocked> data; memcpy(data.data(), &view_key, sizeof(view_key)); memcpy(data.data() + sizeof(view_key), &spend_key, sizeof(spend_key)); - data[sizeof(data) - 1] = config::HASH_KEY_WALLET; + data[sizeof(data) - 1] = cryptonote::hashkey::WALLET; crypto::generate_chacha_key(data.data(), sizeof(data), key, kdf_rounds); return true; } @@ -190,13 +190,13 @@ namespace hw::core { } crypto::secret_key device_default::get_subaddress_secret_key(const crypto::secret_key &a, const cryptonote::subaddress_index &index) { - char data[config::HASH_KEY_SUBADDRESS.size() + sizeof(crypto::secret_key) + 2 * sizeof(uint32_t)]; - memcpy(data, config::HASH_KEY_SUBADDRESS.data(), config::HASH_KEY_SUBADDRESS.size()); - memcpy(data + config::HASH_KEY_SUBADDRESS.size(), &a, sizeof(crypto::secret_key)); + char data[cryptonote::hashkey::SUBADDRESS.size() + sizeof(crypto::secret_key) + 2 * sizeof(uint32_t)]; + memcpy(data, cryptonote::hashkey::SUBADDRESS.data(), cryptonote::hashkey::SUBADDRESS.size()); + memcpy(data + cryptonote::hashkey::SUBADDRESS.size(), &a, sizeof(crypto::secret_key)); uint32_t idx = SWAP32LE(index.major); - memcpy(data + config::HASH_KEY_SUBADDRESS.size() + sizeof(crypto::secret_key), &idx, sizeof(uint32_t)); + memcpy(data + cryptonote::hashkey::SUBADDRESS.size() + sizeof(crypto::secret_key), &idx, sizeof(uint32_t)); idx = SWAP32LE(index.minor); - memcpy(data + config::HASH_KEY_SUBADDRESS.size() + sizeof(crypto::secret_key) + sizeof(uint32_t), &idx, sizeof(uint32_t)); + memcpy(data + cryptonote::hashkey::SUBADDRESS.size() + sizeof(crypto::secret_key) + sizeof(uint32_t), &idx, sizeof(uint32_t)); crypto::secret_key m; crypto::hash_to_scalar(data, sizeof(data), m); return m; @@ -375,7 +375,7 @@ namespace hw::core { return false; memcpy(data, &derivation, 32); - data[32] = config::HASH_KEY_ENCRYPTED_PAYMENT_ID; + data[32] = cryptonote::hashkey::ENCRYPTED_PAYMENT_ID; cn_fast_hash(data, 33, hash); for (size_t b = 0; b < 8; ++b) diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt index 4fea83eab27..f6cf006ecc9 100755 --- a/src/device_trezor/CMakeLists.txt +++ b/src/device_trezor/CMakeLists.txt @@ -63,6 +63,4 @@ if(DEVICE_TREZOR_READY) else() message(STATUS "Trezor support disabled") - add_library(device_trezor device_trezor.cpp) - target_link_libraries(device_trezor PRIVATE cncrypto common) endif() diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index 3f1ca3e2572..0d968fa469a 100755 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -434,7 +434,7 @@ namespace tx { static rct::RangeProofType get_rsig_type(const rct::RCTConfig &rct_config, size_t num_outputs){ if (rct_config.range_proof_type == rct::RangeProofType::Borromean){ return rct::RangeProofType::Borromean; - } else if (num_outputs > BULLETPROOF_MAX_OUTPUTS){ + } else if (num_outputs > TX_BULLETPROOF_MAX_OUTPUTS){ return rct::RangeProofType::MultiOutputBulletproof; } else { return rct::RangeProofType::PaddedBulletproof; @@ -450,15 +450,15 @@ namespace tx { amount_batched += 1; } else if (rsig_type == rct::RangeProofType::PaddedBulletproof){ - if (num_outputs > BULLETPROOF_MAX_OUTPUTS){ - throw std::invalid_argument("BP padded can support only BULLETPROOF_MAX_OUTPUTS statements"); + if (num_outputs > TX_BULLETPROOF_MAX_OUTPUTS){ + throw std::invalid_argument("BP padded can support only TX_BULLETPROOF_MAX_OUTPUTS statements"); } batches.push_back(num_outputs); amount_batched += num_outputs; } else if (rsig_type == rct::RangeProofType::MultiOutputBulletproof){ size_t batch_size = 1; - while (batch_size * 2 + amount_batched <= num_outputs && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS){ + while (batch_size * 2 + amount_batched <= num_outputs && batch_size * 2 <= TX_BULLETPROOF_MAX_OUTPUTS){ batch_size *= 2; } batch_size = std::min(batch_size, num_outputs - amount_batched); diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 4d20c7c94d3..10327a0bb4d 100755 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -242,7 +242,7 @@ int main(int argc, char* argv[]) } bool create_address_file = command_line::get_arg(*vm, arg_create_address_file); - if (!generate_multisig(threshold, total, basename, testnet ? TESTNET : devnet ? DEVNET : MAINNET, create_address_file)) + if (!generate_multisig(threshold, total, basename, testnet ? network_type::TESTNET : devnet ? network_type::DEVNET : network_type::MAINNET, create_address_file)) return 1; return 0; diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index c432d43e5ef..98a837e3cac 100755 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -46,8 +46,8 @@ namespace cryptonote crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) { rct::key multisig_salt; - static_assert(sizeof(rct::key) == config::HASH_KEY_MULTISIG.size(), "Hash domain separator is an unexpected size"); - memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG.data(), sizeof(rct::key)); + static_assert(sizeof(rct::key) == cryptonote::hashkey::MULTISIG.size(), "Hash domain separator is an unexpected size"); + memcpy(multisig_salt.bytes, cryptonote::hashkey::MULTISIG.data(), sizeof(rct::key)); rct::keyV data; data.reserve(2); diff --git a/src/net/dandelionpp.cpp b/src/net/dandelionpp.cpp index 4d2f7542893..f5bd3f932e7 100755 --- a/src/net/dandelionpp.cpp +++ b/src/net/dandelionpp.cpp @@ -42,7 +42,7 @@ namespace dandelionpp { namespace { - constexpr const std::size_t expected_max_channels = CRYPTONOTE_NOISE_CHANNELS; + constexpr const std::size_t expected_max_channels = cryptonote::NOISE_CHANNELS; // could be in util somewhere struct key_less diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index eb517452a26..cb088ef696e 100755 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -51,7 +51,7 @@ namespace { constexpr const std::chrono::milliseconds future_poll_interval = 500ms; - constexpr const std::chrono::seconds socks_connect_timeout{P2P_DEFAULT_SOCKS_CONNECT_TIMEOUT}; + constexpr const std::chrono::seconds socks_connect_timeout{cryptonote::p2p::DEFAULT_SOCKS_CONNECT_TIMEOUT}; std::int64_t get_max_connections(const std::string_view value) noexcept @@ -112,26 +112,26 @@ namespace nodetool const command_line::arg_descriptor arg_p2p_bind_port = { "p2p-bind-port" , "Port for p2p network protocol (IPv4)" - , std::to_string(config::P2P_DEFAULT_PORT) + , std::to_string(cryptonote::config::P2P_DEFAULT_PORT) , {{ &cryptonote::arg_testnet_on, &cryptonote::arg_devnet_on }} , [](std::array testnet_devnet, bool defaulted, std::string val)->std::string { if (testnet_devnet[0] && defaulted) - return std::to_string(config::testnet::P2P_DEFAULT_PORT); + return std::to_string(cryptonote::config::testnet::P2P_DEFAULT_PORT); else if (testnet_devnet[1] && defaulted) - return std::to_string(config::devnet::P2P_DEFAULT_PORT); + return std::to_string(cryptonote::config::devnet::P2P_DEFAULT_PORT); return val; } }; const command_line::arg_descriptor arg_p2p_bind_port_ipv6 = { "p2p-bind-port-ipv6" , "Port for p2p network protocol (IPv6)" - , std::to_string(config::P2P_DEFAULT_PORT) + , std::to_string(cryptonote::config::P2P_DEFAULT_PORT) , {{ &cryptonote::arg_testnet_on, &cryptonote::arg_devnet_on }} , [](std::array testnet_devnet, bool defaulted, std::string val)->std::string { if (testnet_devnet[0] && defaulted) - return std::to_string(config::testnet::P2P_DEFAULT_PORT); + return std::to_string(cryptonote::config::testnet::P2P_DEFAULT_PORT); else if (testnet_devnet[1] && defaulted) - return std::to_string(config::devnet::P2P_DEFAULT_PORT); + return std::to_string(cryptonote::config::devnet::P2P_DEFAULT_PORT); return val; } }; @@ -156,8 +156,8 @@ namespace nodetool const command_line::arg_descriptor arg_in_peers = {"in-peers", "set max number of in peers", -1}; const command_line::arg_descriptor arg_tos_flag = {"tos-flag", "set TOS flag", -1}; - const command_line::arg_descriptor arg_limit_rate_up = {"limit-rate-up", "set limit-rate-up [kB/s]", P2P_DEFAULT_LIMIT_RATE_UP}; - const command_line::arg_descriptor arg_limit_rate_down = {"limit-rate-down", "set limit-rate-down [kB/s]", P2P_DEFAULT_LIMIT_RATE_DOWN}; + const command_line::arg_descriptor arg_limit_rate_up = {"limit-rate-up", "set limit-rate-up [kB/s]", cryptonote::p2p::DEFAULT_LIMIT_RATE_UP}; + const command_line::arg_descriptor arg_limit_rate_down = {"limit-rate-down", "set limit-rate-down [kB/s]", cryptonote::p2p::DEFAULT_LIMIT_RATE_DOWN}; const command_line::arg_descriptor arg_limit_rate = {"limit-rate", "set limit-rate [kB/s]", -1}; std::optional> get_proxies(boost::program_options::variables_map const& vm) diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index fa29ae9b077..04fa6a0d4a7 100755 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -56,6 +56,7 @@ #include "common/command_line.h" #include "common/periodic_task.h" #include "common/fs.h" +#include "common/util.h" PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) @@ -201,12 +202,12 @@ namespace nodetool void set_config_defaults() noexcept { // at this moment we have a hardcoded config - m_config.m_net_config.handshake_interval = P2P_DEFAULT_HANDSHAKE_INTERVAL; - m_config.m_net_config.packet_max_size = P2P_DEFAULT_PACKET_MAX_SIZE; + m_config.m_net_config.handshake_interval = tools::to_seconds(cryptonote::p2p::DEFAULT_HANDSHAKE_INTERVAL); + m_config.m_net_config.packet_max_size = cryptonote::p2p::DEFAULT_PACKET_MAX_SIZE; m_config.m_net_config.config_id = 0; - m_config.m_net_config.connection_timeout = P2P_DEFAULT_CONNECTION_TIMEOUT; - m_config.m_net_config.ping_connection_timeout = P2P_DEFAULT_PING_CONNECTION_TIMEOUT; - m_config.m_net_config.send_peerlist_sz = P2P_DEFAULT_PEERS_IN_HANDSHAKE; + m_config.m_net_config.connection_timeout = cryptonote::p2p::DEFAULT_CONNECTION_TIMEOUT; + m_config.m_net_config.ping_connection_timeout = cryptonote::p2p::DEFAULT_PING_CONNECTION_TIMEOUT; + m_config.m_net_config.send_peerlist_sz = cryptonote::p2p::DEFAULT_PEERS_IN_HANDSHAKE; m_config.m_support_flags = 0; // only set in public zone } }; @@ -253,9 +254,9 @@ namespace nodetool uint32_t get_max_out_public_peers() const; void change_max_in_public_peers(size_t count); uint32_t get_max_in_public_peers() const; - virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME); + virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = tools::to_seconds(cryptonote::p2p::IP_BLOCK_TIME)); virtual bool unblock_host(const epee::net_utils::network_address &address); - virtual bool block_subnet(const epee::net_utils::ipv4_network_subnet &subnet, time_t seconds = P2P_IP_BLOCKTIME); + virtual bool block_subnet(const epee::net_utils::ipv4_network_subnet &subnet, time_t seconds = tools::to_seconds(cryptonote::p2p::IP_BLOCK_TIME)); virtual bool unblock_subnet(const epee::net_utils::ipv4_network_subnet &subnet); virtual bool is_host_blocked(const epee::net_utils::network_address &address, time_t *seconds) { return !is_remote_host_allowed(address, seconds); } virtual std::map get_blocked_hosts() { std::shared_lock lock{m_blocked_hosts_lock}; return m_blocked_hosts; } @@ -416,7 +417,7 @@ namespace nodetool t_payload_net_handler& m_payload_handler; peerlist_storage m_peerlist_storage; - tools::periodic_task m_peer_handshake_idle_maker_interval{std::chrono::seconds{P2P_DEFAULT_HANDSHAKE_INTERVAL}}; + tools::periodic_task m_peer_handshake_idle_maker_interval{std::chrono::seconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INTERVAL}}; tools::periodic_task m_connections_maker_interval{1s}; tools::periodic_task m_peerlist_store_interval{30min}; tools::periodic_task m_gray_peerlist_housekeeping_interval{1min}; @@ -456,14 +457,14 @@ namespace nodetool std::map m_host_fails_score; std::mutex m_used_stripe_peers_mutex; - std::array, 1 << CRYPTONOTE_PRUNING_LOG_STRIPES> m_used_stripe_peers; + std::array, 1 << cryptonote::PRUNING_LOG_STRIPES> m_used_stripe_peers; boost::uuids::uuid m_network_id; cryptonote::network_type m_nettype; }; - const int64_t default_limit_up = P2P_DEFAULT_LIMIT_RATE_UP; // kB/s - const int64_t default_limit_down = P2P_DEFAULT_LIMIT_RATE_DOWN; // kB/s + const int64_t default_limit_up = cryptonote::p2p::DEFAULT_LIMIT_RATE_UP; // kB/s + const int64_t default_limit_down = cryptonote::p2p::DEFAULT_LIMIT_RATE_DOWN; // kB/s extern const command_line::arg_descriptor arg_p2p_bind_ip; extern const command_line::arg_descriptor arg_p2p_bind_ipv6_address; extern const command_line::arg_descriptor arg_p2p_bind_port; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 183c2e3b6d5..8f592eee5a5 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -115,11 +115,11 @@ namespace nodetool bool node_server::init_config() { TRY_ENTRY(); - auto storage = peerlist_storage::open(m_config_folder / P2P_NET_DATA_FILENAME); + auto storage = peerlist_storage::open(m_config_folder / cryptonote::P2P_NET_DATA_FILENAME); if (storage) m_peerlist_storage = std::move(*storage); - m_network_zones[epee::net_utils::zone::public_].m_config.m_support_flags = P2P_SUPPORT_FLAGS; + m_network_zones[epee::net_utils::zone::public_].m_config.m_support_flags = cryptonote::p2p::SUPPORT_FLAGS; m_first_connection_maker_call = true; CATCH_ENTRY_L0("node_server::init_config", false); @@ -316,11 +316,11 @@ namespace nodetool std::lock_guard lock{m_host_fails_score_lock}; uint64_t fails = ++m_host_fails_score[address.host_str()]; MDEBUG("Host " << address.host_str() << " fail score=" << fails); - if(fails > P2P_IP_FAILS_BEFORE_BLOCK) + if(fails > cryptonote::p2p::IP_FAILS_BEFORE_BLOCK) { auto it = m_host_fails_score.find(address.host_str()); CHECK_AND_ASSERT_MES(it != m_host_fails_score.end(), false, "internal error"); - it->second = P2P_IP_FAILS_BEFORE_BLOCK/2; + it->second = cryptonote::p2p::IP_FAILS_BEFORE_BLOCK/2; block_host(address); } return true; @@ -335,10 +335,10 @@ namespace nodetool bool devnet = command_line::get_arg(vm, cryptonote::arg_devnet_on); bool fakenet = command_line::get_arg(vm, cryptonote::arg_regtest_on); m_nettype = - testnet ? cryptonote::TESTNET : - devnet ? cryptonote::DEVNET : - fakenet ? cryptonote::FAKECHAIN : - cryptonote::MAINNET; + testnet ? cryptonote::network_type::TESTNET : + devnet ? cryptonote::network_type::DEVNET : + fakenet ? cryptonote::network_type::FAKECHAIN : + cryptonote::network_type::MAINNET; network_zone& public_zone = m_network_zones[epee::net_utils::zone::public_]; public_zone.m_connect = &public_connect; @@ -458,9 +458,9 @@ namespace nodetool epee::shared_sv this_noise; if (proxy.noise) { - static_assert(sizeof(epee::levin::bucket_head2) < CRYPTONOTE_NOISE_BYTES, "noise bytes too small"); + static_assert(sizeof(epee::levin::bucket_head2) < cryptonote::NOISE_BYTES, "noise bytes too small"); if (noise.view.empty()) - noise = epee::shared_sv{epee::levin::make_noise_notify(CRYPTONOTE_NOISE_BYTES)}; + noise = epee::shared_sv{epee::levin::make_noise_notify(cryptonote::NOISE_BYTES)}; this_noise = noise; } @@ -573,18 +573,18 @@ namespace nodetool std::set node_server::get_seed_nodes(cryptonote::network_type nettype) const { std::set full_addrs; - if (nettype == cryptonote::TESTNET) + if (nettype == cryptonote::network_type::TESTNET) { full_addrs.insert("test1.rpcnode.stream:29090"); full_addrs.insert("test2.rpcnode.stream:29090"); } - else if (nettype == cryptonote::DEVNET) + else if (nettype == cryptonote::network_type::DEVNET) { full_addrs.insert("35.237.218.150:39090"); full_addrs.insert("35.243.157.236:39090"); full_addrs.insert("35.237.118.17:39090"); } - else if (nettype == cryptonote::FAKECHAIN) + else if (nettype == cryptonote::network_type::FAKECHAIN) { } else @@ -605,16 +605,16 @@ namespace nodetool { return {}; } - if (m_nettype == cryptonote::TESTNET) + if (m_nettype == cryptonote::network_type::TESTNET) { - return get_seed_nodes(cryptonote::TESTNET); + return get_seed_nodes(cryptonote::network_type::TESTNET); } - if (m_nettype == cryptonote::DEVNET) + if (m_nettype == cryptonote::network_type::DEVNET) { - return get_seed_nodes(cryptonote::DEVNET); + return get_seed_nodes(cryptonote::network_type::DEVNET); } - return get_seed_nodes(cryptonote::MAINNET); + return get_seed_nodes(cryptonote::network_type::MAINNET); } //----------------------------------------------------------------------------------- template @@ -634,18 +634,11 @@ namespace nodetool bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); - if (m_nettype == cryptonote::TESTNET) - { - memcpy(&m_network_id, &::config::testnet::NETWORK_ID, 16); - } - else if (m_nettype == cryptonote::DEVNET) - { - memcpy(&m_network_id, &::config::devnet::NETWORK_ID, 16); - } - else - { - memcpy(&m_network_id, &::config::NETWORK_ID, 16); - } + memcpy(&m_network_id, + m_nettype == cryptonote::network_type::TESTNET ? &cryptonote::config::testnet::NETWORK_ID : + m_nettype == cryptonote::network_type::DEVNET ? &cryptonote::config::devnet::NETWORK_ID : + &cryptonote::config::NETWORK_ID, + 16); m_config_folder = fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)); network_zone& public_zone = m_network_zones.at(epee::net_utils::zone::public_); @@ -665,32 +658,31 @@ namespace nodetool for(const auto& p: m_command_line_peers) m_network_zones.at(p.adr.get_zone()).m_peerlist.append_with_peer_white(p); -// all peers are now setup -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - for (auto& zone : m_network_zones) - { - std::list plw; - while (zone.second.m_peerlist.get_white_peers_count()) + // all peers are now setup + if constexpr (cryptonote::PRUNING_DEBUG_SPOOF_SEED) { + for (auto& zone : m_network_zones) { - plw.push_back(peerlist_entry()); - zone.second.m_peerlist.get_white_peer_by_index(plw.back(), 0); - zone.second.m_peerlist.remove_from_peer_white(plw.back()); - } - for (auto &e:plw) - zone.second.m_peerlist.append_with_peer_white(e); + std::list plw; + while (zone.second.m_peerlist.get_white_peers_count()) + { + plw.push_back(peerlist_entry()); + zone.second.m_peerlist.get_white_peer_by_index(plw.back(), 0); + zone.second.m_peerlist.remove_from_peer_white(plw.back()); + } + for (auto &e:plw) + zone.second.m_peerlist.append_with_peer_white(e); - std::list plg; - while (zone.second.m_peerlist.get_gray_peers_count()) - { - plg.push_back(peerlist_entry()); - zone.second.m_peerlist.get_gray_peer_by_index(plg.back(), 0); - zone.second.m_peerlist.remove_from_peer_gray(plg.back()); + std::list plg; + while (zone.second.m_peerlist.get_gray_peers_count()) + { + plg.push_back(peerlist_entry()); + zone.second.m_peerlist.get_gray_peer_by_index(plg.back(), 0); + zone.second.m_peerlist.remove_from_peer_gray(plg.back()); + } + for (auto &e:plg) + zone.second.m_peerlist.append_with_peer_gray(e); } - for (auto &e:plg) - zone.second.m_peerlist.append_with_peer_gray(e); } -#endif - //only in case if we really sure that we have external visible ip m_have_address = true; @@ -706,7 +698,7 @@ namespace nodetool for (auto& zone : m_network_zones) { zone.second.m_net_server.get_config_object().set_handler(this); - zone.second.m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT; + zone.second.m_net_server.get_config_object().m_invoke_timeout = std::chrono::milliseconds{cryptonote::p2p::DEFAULT_INVOKE_TIMEOUT}.count(); if (!zone.second.m_bind_ip.empty()) { @@ -837,7 +829,7 @@ namespace nodetool for (auto& zone : m_network_zones) zone.second.m_peerlist.get_peerlist(active); - const auto state_file_path = m_config_folder / P2P_NET_DATA_FILENAME; + const auto state_file_path = m_config_folder / cryptonote::P2P_NET_DATA_FILENAME; if (!m_peerlist_storage.store(state_file_path, active)) { MWARNING("Failed to save config to file " << state_file_path); @@ -875,8 +867,11 @@ namespace nodetool LOG_PRINT_CC_L0(context_,"do_handshake_with_peer"); network_zone& zone = m_network_zones.at(context_.m_remote_address.get_zone()); - typename COMMAND_HANDSHAKE::request arg{}; - typename COMMAND_HANDSHAKE::response rsp{}; + using request_t = typename COMMAND_HANDSHAKE::request; + using response_t = typename COMMAND_HANDSHAKE::response; + + request_t arg{}; + response_t rsp{}; get_local_node_data(arg.node_data, zone); m_payload_handler.get_payload_sync_data(arg.payload_data); @@ -884,8 +879,8 @@ namespace nodetool std::atomic hsh_result(false); bool timeout = false; LOG_PRINT_CC_L0(context_,"do_handshake_with_peer COMMAND_HANDSHAKE"); - bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, zone.m_net_server.get_config_object(), - [this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_, &timeout](int code, typename COMMAND_HANDSHAKE::response&& rsp, p2p_connection_context& context) + bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, zone.m_net_server.get_config_object(), + [this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_, &timeout](int code, response_t&& rsp, p2p_connection_context& context) { BELDEX_DEFER { ev.set_value(); }; if(code < 0) @@ -937,7 +932,7 @@ namespace nodetool LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); } context_ = context; - }, P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT); + }, std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT}.count()); if(r) { @@ -965,13 +960,16 @@ namespace nodetool template bool node_server::do_peer_timed_sync(const epee::net_utils::connection_context_base& context_, peerid_type peer_id) { - typename COMMAND_TIMED_SYNC::request arg{}; + using request_t = typename COMMAND_TIMED_SYNC::request; + using response_t = typename COMMAND_TIMED_SYNC::response; + request_t arg{}; + LOG_PRINT_CC_L0(context_,"do_peer_timed_sync"); m_payload_handler.get_payload_sync_data(arg.payload_data); network_zone& zone = m_network_zones.at(context_.m_remote_address.get_zone()); - bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_TIMED_SYNC::ID, arg, zone.m_net_server.get_config_object(), - [this](int code, typename COMMAND_TIMED_SYNC::response&& rsp, p2p_connection_context& context) + bool r = epee::net_utils::async_invoke_remote_command2(context_.m_connection_id, COMMAND_TIMED_SYNC::ID, arg, zone.m_net_server.get_config_object(), + [this](int code, response_t&& rsp, p2p_connection_context& context) { context.m_in_timedsync = false; if(code < 0) @@ -1232,10 +1230,8 @@ namespace nodetool if(it == m_conn_fails_cache.end()) return false; - if(time(NULL) - it->second > P2P_FAILED_ADDR_FORGET_SECONDS) - return false; - else - return true; + auto ago = std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(it->second); + return ago <= cryptonote::p2p::FAILED_ADDR_FORGET; } //----------------------------------------------------------------------------------- template @@ -1308,7 +1304,7 @@ namespace nodetool const size_t limit = use_white_list ? 20 : std::numeric_limits::max(); for (int step = 0; step < 2; ++step) { - bool skip_duplicate_class_B = step == 0 && m_nettype == cryptonote::MAINNET; + bool skip_duplicate_class_B = step == 0 && m_nettype == cryptonote::network_type::MAINNET; size_t idx = 0, skipped = 0; zone.m_peerlist.foreach (use_white_list, [&classB, &filtered, &idx, &skipped, skip_duplicate_class_B, limit, next_needed_pruning_stripe](const peerlist_entry &pe){ if (filtered.size() >= limit) @@ -1344,7 +1340,7 @@ namespace nodetool // if using the white list, we first pick in the set of peers we've already been using earlier random_index = get_random_index_with_fixed_probability(std::min(filtered.size() - 1, 20)); std::lock_guard lock{m_used_stripe_peers_mutex}; - if (next_needed_pruning_stripe > 0 && next_needed_pruning_stripe <= (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES) && !m_used_stripe_peers[next_needed_pruning_stripe-1].empty()) + if (next_needed_pruning_stripe > 0 && next_needed_pruning_stripe <= (1ul << cryptonote::PRUNING_LOG_STRIPES) && !m_used_stripe_peers[next_needed_pruning_stripe-1].empty()) { const epee::net_utils::network_address na = m_used_stripe_peers[next_needed_pruning_stripe-1].front(); m_used_stripe_peers[next_needed_pruning_stripe-1].pop_front(); @@ -1506,7 +1502,7 @@ namespace nodetool for(auto& zone : m_network_zones) { - size_t base_expected_white_connections = (zone.second.m_config.m_net_config.max_out_connection_count*P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100; + size_t base_expected_white_connections = (zone.second.m_config.m_net_config.max_out_connection_count * cryptonote::p2p::DEFAULT_WHITELIST_CONNECTIONS_PERCENT)/100; size_t conn_count = get_outgoing_connections_count(zone.second); while(conn_count < zone.second.m_config.m_net_config.max_out_connection_count) @@ -1515,8 +1511,8 @@ namespace nodetool if(conn_count < expected_white_connections) { //start from anchor list - while (get_outgoing_connections_count(zone.second) < P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT - && make_expected_connections_count(zone.second, anchor, P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT)); + while (get_outgoing_connections_count(zone.second) < cryptonote::p2p::DEFAULT_ANCHOR_CONNECTIONS_COUNT + && make_expected_connections_count(zone.second, anchor, cryptonote::p2p::DEFAULT_ANCHOR_CONNECTIONS_COUNT)); //then do white list while (get_outgoing_connections_count(zone.second) < expected_white_connections && make_expected_connections_count(zone.second, white, expected_white_connections)); @@ -1744,7 +1740,7 @@ namespace nodetool }); } - std::for_each(cncts.begin(), cncts.end(), [&](const typename local_connects_type::value_type& vl){do_peer_timed_sync(vl.first, vl.second);}); + std::for_each(cncts.begin(), cncts.end(), [&](const auto& vl){do_peer_timed_sync(vl.first, vl.second);}); MDEBUG("FINISHED PEERLIST IDLE HANDSHAKE"); return true; @@ -1770,7 +1766,7 @@ namespace nodetool else if (ipv4.port() == be.rpc_port) ignore = true; } - if (be.pruning_seed && (be.pruning_seed < tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES) || be.pruning_seed > tools::make_pruning_seed(1ul << CRYPTONOTE_PRUNING_LOG_STRIPES, CRYPTONOTE_PRUNING_LOG_STRIPES))) + if (be.pruning_seed && (be.pruning_seed < tools::make_pruning_seed(1, cryptonote::PRUNING_LOG_STRIPES) || be.pruning_seed > tools::make_pruning_seed(1ul << cryptonote::PRUNING_LOG_STRIPES, cryptonote::PRUNING_LOG_STRIPES))) ignore = true; if (ignore) { @@ -1782,9 +1778,9 @@ namespace nodetool } local_peerlist[i].last_seen = 0; -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - be.pruning_seed = tools::make_pruning_seed(1 + (be.adr.as().ip()) % (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES); -#endif + if constexpr (cryptonote::PRUNING_DEBUG_SPOOF_SEED) + be.pruning_seed = tools::make_pruning_seed(1 + (be.adr.as().ip()) % (1ul << cryptonote::PRUNING_LOG_STRIPES), cryptonote::PRUNING_LOG_STRIPES); + } return true; } @@ -1994,8 +1990,8 @@ namespace nodetool address = epee::net_utils::network_address{epee::net_utils::ipv6_network_address(ipv6_addr, node_data.my_port)}; } peerid_type pr = node_data.peer_id; - LOG_PRINT_CC_L0(context,"try_ping start ip:" << ip << " port:" << port << " timeout:" << zone.m_config.m_net_config.ping_connection_timeout ); - bool r = zone.m_net_server.connect_async(ip, port, zone.m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( + LOG_PRINT_CC_L0(context,"try_ping start ip:" << ip << " port:" << port << " timeout:" << zone.m_config.m_net_config.ping_connection_timeout.count()); + bool r = zone.m_net_server.connect_async(ip, port, zone.m_config.m_net_config.ping_connection_timeout.count(), [cb, /*context,*/ address, pr, this]( const typename net_server::t_connection_context& ping_context, const boost::system::error_code& ec)->bool { @@ -2061,14 +2057,17 @@ namespace nodetool if(context.m_remote_address.get_zone() != epee::net_utils::zone::public_) return false; - COMMAND_REQUEST_SUPPORT_FLAGS::request support_flags_request{}; - bool r = epee::net_utils::async_invoke_remote_command2 + + using request_t = typename COMMAND_REQUEST_SUPPORT_FLAGS::request; + using response_t = typename COMMAND_REQUEST_SUPPORT_FLAGS::response; + request_t support_flags_request{}; + bool r = epee::net_utils::async_invoke_remote_command2 ( context.m_connection_id, COMMAND_REQUEST_SUPPORT_FLAGS::ID, support_flags_request, m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object(), - [=](int code, const typename COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context_) + [=](int code, const response_t& rsp, p2p_connection_context& context_) { if(code < 0) { @@ -2078,7 +2077,7 @@ namespace nodetool LOG_PRINT_CC_L0(context_,"try_get_support_flags response"); f(context_, rsp.support_flags); }, - P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT + std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT}.count() ); return r; @@ -2099,7 +2098,7 @@ namespace nodetool network_zone& zone = m_network_zones.at(zone_type); std::vector local_peerlist_new; - zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, P2P_DEFAULT_PEERS_IN_HANDSHAKE); + zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, cryptonote::p2p::DEFAULT_PEERS_IN_HANDSHAKE); //only include out peers we did not already send rsp.local_peerlist_new.reserve(local_peerlist_new.size()); @@ -2369,7 +2368,7 @@ namespace nodetool bool node_server::set_max_out_peers(network_zone& zone, int64_t max) { if (max == -1) - max = P2P_DEFAULT_CONNECTIONS_COUNT_OUT; + max = cryptonote::p2p::DEFAULT_CONNECTIONS_COUNT_OUT; LOG_PRINT_L0("set_max_out_peers " << max); zone.m_config.m_net_config.max_out_connection_count = max; return true; @@ -2379,7 +2378,7 @@ namespace nodetool bool node_server::set_max_in_peers(network_zone& zone, int64_t max) { if (max == -1) - max = P2P_DEFAULT_CONNECTIONS_COUNT_IN; + max = cryptonote::p2p::DEFAULT_CONNECTIONS_COUNT_IN; LOG_PRINT_L0("set_max_in_peers " << max); zone.m_config.m_net_config.max_in_connection_count = max; return true; @@ -2501,7 +2500,7 @@ namespace nodetool if (address.get_zone() != epee::net_utils::zone::public_) return false; // Unable to determine how many connections from host - const size_t max_connections = m_nettype == cryptonote::MAINNET ? 1 : 20; + const size_t max_connections = m_nettype == cryptonote::network_type::MAINNET ? 1 : 20; size_t count = 0; m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) @@ -2555,7 +2554,7 @@ namespace nodetool void node_server::add_used_stripe_peer(const typename t_payload_net_handler::connection_context &context) { const uint32_t stripe = tools::get_pruning_stripe(context.m_pruning_seed); - if (stripe == 0 || stripe > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES)) + if (stripe == 0 || stripe > (1ul << cryptonote::PRUNING_LOG_STRIPES)) return; const uint32_t index = stripe - 1; std::lock_guard lock{m_used_stripe_peers_mutex}; @@ -2569,7 +2568,7 @@ namespace nodetool void node_server::remove_used_stripe_peer(const typename t_payload_net_handler::connection_context &context) { const uint32_t stripe = tools::get_pruning_stripe(context.m_pruning_seed); - if (stripe == 0 || stripe > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES)) + if (stripe == 0 || stripe > (1ul << cryptonote::PRUNING_LOG_STRIPES)) return; const uint32_t index = stripe - 1; std::lock_guard lock{m_used_stripe_peers_mutex}; @@ -2628,7 +2627,7 @@ namespace nodetool typename net_server::t_connection_context con{}; const bool res = zone.m_net_server.connect(address, port, - zone.m_config.m_net_config.connection_timeout, + zone.m_config.m_net_config.connection_timeout.count(), con, zone.m_bind_ip); if (res) diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 94e1c304d48..54f311e8b52 100755 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -104,7 +104,7 @@ namespace nodetool size_t get_white_peers_count(){std::lock_guard lock{m_peerlist_lock}; return m_peers_white.size();} size_t get_gray_peers_count(){std::lock_guard lock{m_peerlist_lock}; return m_peers_gray.size();} bool merge_peerlist(const std::vector& outer_bs, const std::function &f = NULL); - bool get_peerlist_head(std::vector& bs_head, bool anonymize, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); + bool get_peerlist_head(std::vector& bs_head, bool anonymize, uint32_t depth = cryptonote::p2p::DEFAULT_PEERS_IN_HANDSHAKE); void get_peerlist(std::vector& pl_gray, std::vector& pl_white); void get_peerlist(peerlist_types& peers); bool get_white_peer_by_index(peerlist_entry& p, size_t i); @@ -197,7 +197,7 @@ namespace nodetool //-------------------------------------------------------------------------------------------------- inline void peerlist_manager::trim_gray_peerlist() { - while(m_peers_gray.size() > P2P_LOCAL_GRAY_PEERLIST_LIMIT) + while(m_peers_gray.size() > cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT) { peers_indexed::index::type& sorted_index=m_peers_gray.get(); sorted_index.erase(sorted_index.begin()); @@ -206,7 +206,7 @@ namespace nodetool //-------------------------------------------------------------------------------------------------- inline void peerlist_manager::trim_white_peerlist() { - while(m_peers_white.size() > P2P_LOCAL_WHITE_PEERLIST_LIMIT) + while(m_peers_white.size() > cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT) { peers_indexed::index::type& sorted_index=m_peers_white.get(); sorted_index.erase(sorted_index.begin()); diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index f88030dd9c3..02f742961fc 100755 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -37,10 +37,7 @@ #include "net/tor_address.h" #include "net/i2p_address.h" #include "p2p/p2p_protocol_defs.h" - -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED #include "common/pruning.h" -#endif BOOST_CLASS_VERSION(nodetool::peerlist_entry, 2) @@ -228,12 +225,10 @@ namespace boost return; } a & pl.pruning_seed; -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - if (!typename Archive::is_saving()) + if constexpr (cryptonote::PRUNING_DEBUG_SPOOF_SEED && !typename Archive::is_saving()) { - pl.pruning_seed = tools::make_pruning_seed(1+pl.adr.as().ip() % (1<().ip() % (1 << cryptonote::PRUNING_LOG_STRIPES), cryptonote::PRUNING_LOG_STRIPES); } -#endif if (ver < 2) { if (!typename Archive::is_saving()) diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 10334e97273..9ccff1d4aea 100755 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -148,8 +148,8 @@ namespace nodetool uint32_t max_out_connection_count; uint32_t max_in_connection_count; - uint32_t connection_timeout; - uint32_t ping_connection_timeout; + std::chrono::milliseconds connection_timeout; + std::chrono::milliseconds ping_connection_timeout; uint32_t handshake_interval; uint32_t packet_max_size; uint32_t config_id; @@ -172,7 +172,7 @@ namespace nodetool }; -#define P2P_COMMANDS_POOL_BASE 1000 + inline constexpr int P2P_COMMANDS_POOL_BASE = 1000; /************************************************************************/ /* */ diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index 85eb4d48b05..e90955b2d29 100755 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -68,7 +68,7 @@ static rct::keyV vector_dup(const rct::key &x, size_t n); static rct::key inner_product(const rct::keyV &a, const rct::keyV &b); static constexpr size_t maxN = 64; -static constexpr size_t maxM = BULLETPROOF_MAX_OUTPUTS; +static constexpr size_t maxM = cryptonote::TX_BULLETPROOF_MAX_OUTPUTS;; static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static std::shared_ptr straus_HiGi_cache; @@ -99,7 +99,7 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_EXPONENT); + static const std::string domain_separator(cryptonote::hashkey::BULLETPROOF_EXPONENT); std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index e57115b6cda..5e177ae0a10 100755 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -213,9 +213,9 @@ namespace rct { keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset sc_0(mu_P_to_hash[0].bytes); - memcpy(mu_P_to_hash[0].bytes, config::HASH_KEY_CLSAG_AGG_0.data(), config::HASH_KEY_CLSAG_AGG_0.size()); + memcpy(mu_P_to_hash[0].bytes, cryptonote::hashkey::CLSAG_AGG_0.data(),cryptonote::hashkey::CLSAG_AGG_0.size()); sc_0(mu_C_to_hash[0].bytes); - memcpy(mu_C_to_hash[0].bytes, config::HASH_KEY_CLSAG_AGG_1.data(), config::HASH_KEY_CLSAG_AGG_1.size()); + memcpy(mu_C_to_hash[0].bytes, cryptonote::hashkey::CLSAG_AGG_1.data(), cryptonote::hashkey::CLSAG_AGG_1.size()); for (size_t i = 1; i < n+1; ++i) { mu_P_to_hash[i] = P[i-1]; mu_C_to_hash[i] = P[i-1]; @@ -238,7 +238,7 @@ namespace rct { keyV c_to_hash(2*n+5); // domain, P, C, C_offset, message, aG, aH key c; sc_0(c_to_hash[0].bytes); - memcpy(c_to_hash[0].bytes, config::HASH_KEY_CLSAG_ROUND.data(), config::HASH_KEY_CLSAG_ROUND.size()); + memcpy(c_to_hash[0].bytes, cryptonote::hashkey::CLSAG_ROUND.data(), cryptonote::hashkey::CLSAG_ROUND.size()); for (size_t i = 1; i < n+1; ++i) { c_to_hash[i] = P[i-1]; @@ -918,9 +918,9 @@ namespace rct { keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset sc_0(mu_P_to_hash[0].bytes); - memcpy(mu_P_to_hash[0].bytes, config::HASH_KEY_CLSAG_AGG_0.data(), config::HASH_KEY_CLSAG_AGG_0.size()); + memcpy(mu_P_to_hash[0].bytes, cryptonote::hashkey::CLSAG_AGG_0.data(), cryptonote::hashkey::CLSAG_AGG_0.size()); sc_0(mu_C_to_hash[0].bytes); - memcpy(mu_C_to_hash[0].bytes, config::HASH_KEY_CLSAG_AGG_1.data(), config::HASH_KEY_CLSAG_AGG_1.size()); + memcpy(mu_C_to_hash[0].bytes, cryptonote::hashkey::CLSAG_AGG_1.data(), cryptonote::hashkey::CLSAG_AGG_1.size()); for (size_t i = 1; i < n+1; ++i) { mu_P_to_hash[i] = pubs[i-1].dest; mu_C_to_hash[i] = pubs[i-1].dest; @@ -942,7 +942,7 @@ namespace rct { // Set up round hash keyV c_to_hash(2*n+5); // domain, P, C, C_offset, message, L, R sc_0(c_to_hash[0].bytes); - memcpy(c_to_hash[0].bytes, config::HASH_KEY_CLSAG_ROUND.data(), config::HASH_KEY_CLSAG_ROUND.size()); + memcpy(c_to_hash[0].bytes, cryptonote::hashkey::CLSAG_ROUND.data(), cryptonote::hashkey::CLSAG_ROUND.size()); for (size_t i = 1; i < n+1; ++i) { c_to_hash[i] = pubs[i-1].dest; @@ -1213,7 +1213,7 @@ namespace rct { { size_t batch_size = 1; if (rct_config.range_proof_type == RangeProofType::MultiOutputBulletproof) - while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS) + while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= cryptonote::TX_BULLETPROOF_MAX_OUTPUTS) batch_size *= 2; rct::keyV C, masks; std::vector batch_amounts(batch_size); diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index e23a7c8df10..71fa69f7313 100755 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -193,7 +193,7 @@ namespace rct { CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); + static_assert((1 << extra_bits) == cryptonote::TX_BULLETPROOF_MAX_OUTPUTS, "log2(TX_BULLETPROOF_MAX_OUTPUTS) is out of date"); CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); CHECK_AND_ASSERT_MES(proof.V.size() <= (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); CHECK_AND_ASSERT_MES(proof.V.size() * 2 > (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); @@ -220,7 +220,7 @@ namespace rct { CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == BULLETPROOF_MAX_OUTPUTS, "log2(BULLETPROOF_MAX_OUTPUTS) is out of date"); + static_assert((1 << extra_bits) == cryptonote::TX_BULLETPROOF_MAX_OUTPUTS, "log2(TX_BULLETPROOF_MAX_OUTPUTS) is out of date"); CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); return 1 << (proof.L.size() - 6); } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 10ceec0474b..df17f8dc43b 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -42,6 +42,7 @@ #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/tx_extra.h" +#include "cryptonote_config.h" #include "cryptonote_core/beldex_name_system.h" #include "cryptonote_core/pos.h" #include "beldex_economy.h" @@ -393,7 +394,7 @@ namespace cryptonote { namespace rpc { } res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(next_block_is_POS); - res.target = tools::to_seconds((next_block_is_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)); + res.target = tools::to_seconds((next_block_is_POS ? TARGET_BLOCK_TIME : old::TARGET_BLOCK_TIME_12)); res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase res.tx_pool_size = m_core.get_pool().get_transactions_count(); if (context.admin) @@ -407,10 +408,10 @@ namespace cryptonote { namespace rpc { } cryptonote::network_type nettype = m_core.get_nettype(); - res.mainnet = nettype == MAINNET; - res.testnet = nettype == TESTNET; - res.devnet = nettype == DEVNET; - res.nettype = nettype == MAINNET ? "mainnet" : nettype == TESTNET ? "testnet" : nettype == DEVNET ? "devnet" : "fakechain"; + res.mainnet = nettype == network_type::MAINNET; + res.testnet = nettype == network_type::TESTNET; + res.devnet = nettype == network_type::DEVNET; + res.nettype = nettype == network_type::MAINNET ? "mainnet" : nettype == network_type::TESTNET ? "testnet" : nettype == network_type::DEVNET ? "devnet" : "fakechain"; try { @@ -719,7 +720,7 @@ namespace cryptonote { namespace rpc { struct extra_extractor { GET_TRANSACTIONS::extra_entry& entry; const network_type nettype; - const uint8_t hf_version; + const cryptonote::hf hf_version; void operator()(const tx_extra_pub_key& x) { entry.pubkey = tools::type_to_hex(x.pub_key); } void operator()(const tx_extra_nonce& x) { @@ -840,7 +841,7 @@ namespace cryptonote { namespace rpc { }; - bool load_tx_extra_data(GET_TRANSACTIONS::extra_entry& e, const transaction& tx, network_type nettype,uint8_t hf_version) + bool load_tx_extra_data(GET_TRANSACTIONS::extra_entry& e, const transaction& tx, network_type nettype, cryptonote::hf hf_version) { std::vector extras; if (!parse_tx_extra(tx.extra, extras)) @@ -1303,7 +1304,7 @@ namespace cryptonote { namespace rpc { const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); - res.block_target = tools::to_seconds(TARGET_BLOCK_TIME_OLD); // old_block_time + res.block_target = tools::to_seconds(old::TARGET_BLOCK_TIME_12); // old_block_time res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(false /*POS*/); if ( lMiner.is_mining() ) { res.speed = lMiner.get_speed(); @@ -1313,11 +1314,11 @@ namespace cryptonote { namespace rpc { const account_public_address& lMiningAdr = lMiner.get_mining_address(); if (lMiner.is_mining()) res.address = get_account_address_as_str(nettype(), false, lMiningAdr); - const uint8_t major_version = m_core.get_blockchain_storage().get_network_version(); + const auto major_version = m_core.get_blockchain_storage().get_network_version(); res.pow_algorithm = - major_version >= network_version_13_checkpointing ? "RandomX (BELDEX variant)" : - major_version == network_version_11_infinite_staking ? "Cryptonight Turtle Light (Variant 2)" : + major_version >= hf::hf13_checkpointing ? "RandomX (BELDEX variant)" : + major_version == hf::hf11_infinite_staking ? "Cryptonight Turtle Light (Variant 2)" : "Cryptonight Heavy (Variant 2)"; res.status = STATUS_OK; @@ -1676,7 +1677,7 @@ namespace cryptonote { namespace rpc { throw rpc_error{ERROR_INTERNAL, "Internal error: failed to create block template"}; } - if (b.major_version >= network_version_13_checkpointing) + if (b.major_version >= hf::hf13_checkpointing) { uint64_t seed_height, next_height; crypto::hash seed_hash; @@ -1767,7 +1768,7 @@ namespace cryptonote { namespace rpc { res.status = STATUS_OK; - if(m_core.get_nettype() != FAKECHAIN) + if(m_core.get_nettype() != network_type::FAKECHAIN) throw rpc_error{ERROR_REGTEST_REQUIRED, "Regtest required when generating blocks"}; SUBMITBLOCK::request submit_req{}; @@ -1792,7 +1793,7 @@ namespace cryptonote { namespace rpc { throw rpc_error{ERROR_WRONG_BLOCKBLOB, "Wrong block blob"}; b.nonce = req.starting_nonce; miner::find_nonce_for_given_block([this](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash) { - hash = cryptonote::get_block_longhash_w_blockchain(cryptonote::FAKECHAIN, &(m_core.get_blockchain_storage()), b, height, threads); + hash = cryptonote::get_block_longhash_w_blockchain(cryptonote::network_type::FAKECHAIN, &(m_core.get_blockchain_storage()), b, height, threads); return true; }, b, template_res.difficulty, template_res.height); @@ -1820,7 +1821,7 @@ namespace cryptonote { namespace rpc { void core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash, bool get_tx_hashes) { PERF_TIMER(fill_block_header_response); - response.major_version = blk.major_version; + response.major_version = static_cast(blk.major_version); response.minor_version = blk.minor_version; response.timestamp = blk.timestamp; response.prev_hash = tools::type_to_hex(blk.prev_id); @@ -2107,8 +2108,8 @@ namespace cryptonote { namespace rpc { return res; const Blockchain &blockchain = m_core.get_blockchain_storage(); - uint8_t version = - req.version > 0 ? req.version : + auto version = + req.version > 0 ? static_cast(req.version) : req.height > 0 ? blockchain.get_network_version(req.height) : blockchain.get_network_version(); res.version = version; @@ -2386,8 +2387,8 @@ namespace cryptonote { namespace rpc { auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); res.fee_per_byte = fees.first; res.fee_per_output = fees.second; - res.flash_fee_fixed = FLASH_BURN_FIXED; - constexpr auto flash_percent = FLASH_MINER_TX_FEE_PERCENT + FLASH_BURN_TX_FEE_PERCENT_OLD; + res.flash_fee_fixed = beldex::FLASH_BURN_FIXED; + constexpr auto flash_percent = beldex::FLASH_MINER_TX_FEE_PERCENT + beldex::FLASH_BURN_TX_FEE_PERCENT_OLD; res.flash_fee_per_byte = res.fee_per_byte * flash_percent / 100; res.flash_fee_per_output = res.fee_per_output * flash_percent / 100; res.quantization_mask = Blockchain::get_fee_quantization_mask(); @@ -2455,10 +2456,10 @@ namespace cryptonote { namespace rpc { if (req.limit_down != 0) epee::net_utils::connection_basic::set_rate_down_limit( - req.limit_down == -1 ? nodetool::default_limit_down : req.limit_down); + req.limit_down == -1 ? p2p::DEFAULT_LIMIT_RATE_DOWN : req.limit_down); if (req.limit_up != 0) epee::net_utils::connection_basic::set_rate_up_limit( - req.limit_up == -1 ? nodetool::default_limit_up : req.limit_up); + req.limit_up == -1 ? p2p::DEFAULT_LIMIT_RATE_UP : req.limit_up); res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit(); res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit(); @@ -2847,7 +2848,7 @@ namespace cryptonote { namespace rpc { auto net = nettype(); for (size_t height = start; height != end;) { - uint8_t hf_version = get_network_version(net, height); + auto hf_version = get_network_version(net, height); { auto start_quorum_iterator = static_cast(0); auto end_quorum_iterator = master_nodes::max_quorum_type_for_hf(hf_version); @@ -2885,8 +2886,7 @@ namespace cryptonote { namespace rpc { else height--; } - if (uint8_t hf_version; add_curr_POS - && (hf_version = get_network_version(nettype(), curr_height)) >= network_version_17_POS) + if (auto hf_version = get_network_version(nettype(), curr_height); add_curr_POS && hf_version >= hf::hf17_POS) { cryptonote::Blockchain const &blockchain = m_core.get_blockchain_storage(); cryptonote::block_header const &top_header = blockchain.get_db().get_block_header_from_height(curr_height - 1); @@ -2940,7 +2940,7 @@ namespace cryptonote { namespace rpc { if (!m_core.master_node()) throw rpc_error{ERROR_WRONG_PARAM, "Daemon has not been started in master node mode, please relaunch with --master-node flag."}; - uint8_t hf_version = get_network_version(nettype(), m_core.get_current_blockchain_height()); + auto hf_version = get_network_version(nettype(), m_core.get_current_blockchain_height()); if (!master_nodes::make_registration_cmd(m_core.get_nettype(), hf_version, req.staking_requirement, req.args, m_core.get_master_keys(), res.registration_cmd, req.make_friendly)) throw rpc_error{ERROR_INTERNAL, "Failed to make registration command"}; @@ -3060,8 +3060,8 @@ namespace cryptonote { namespace rpc { entry.funded = info.is_fully_funded(); entry.state_height = info.is_fully_funded() ? (info.is_decommissioned() ? info.last_decommission_height : info.active_since_height) : info.last_reward_block_height; - uint8_t hf_version = m_core.get_blockchain_storage().get_network_version(); - entry.earned_downtime_blocks = master_nodes::quorum_cop::calculate_decommission_credit(info, current_height,hf_version); + auto hf_version = m_core.get_blockchain_storage().get_network_version(); + entry.earned_downtime_blocks = master_nodes::quorum_cop::calculate_decommission_credit(info, current_height, hf_version); entry.decommission_count = info.decommission_count; entry.last_decommission_reason_consensus_all = info.last_decommission_reason_consensus_all; entry.last_decommission_reason_consensus_any = info.last_decommission_reason_consensus_any; @@ -3157,7 +3157,7 @@ namespace cryptonote { namespace rpc { res.target_height = m_core.get_target_blockchain_height(); res.block_hash = tools::type_to_hex(m_core.get_block_id_by_height(res.height)); auto [hf, mnode_rev] = get_network_version_revision(nettype(), res.height); - res.hardfork = hf; + res.hardfork = static_cast(hf); res.mnode_revision = mnode_rev; if (!req.poll_block_hash.empty()) { @@ -3400,7 +3400,7 @@ namespace cryptonote { namespace rpc { MERROR("Could not query block at requested height: " << cryptonote::get_block_height(block.second)); continue; } - const uint8_t hard_fork_version = block.second.major_version; + const auto hard_fork_version = block.second.major_version; for (const auto& blob : blobs) { cryptonote::transaction tx; @@ -3414,7 +3414,7 @@ namespace cryptonote { namespace rpc { cryptonote::tx_extra_master_node_state_change state_change; if (!cryptonote::get_master_node_state_change_from_tx_extra(tx.extra, state_change, hard_fork_version)) { - LOG_ERROR("Could not get state change from tx, possibly corrupt tx, hf_version "<< std::to_string(hard_fork_version)); + LOG_ERROR("Could not get state change from tx, possibly corrupt tx, hf_version "<< static_cast(hard_fork_version)); continue; } @@ -3487,7 +3487,7 @@ namespace cryptonote { namespace rpc { //------------------------------------------------------------------------------------------------------------------------------ TEST_TRIGGER_UPTIME_PROOF::response core_rpc_server::invoke(TEST_TRIGGER_UPTIME_PROOF::request&& req, rpc_context context) { - if (m_core.get_nettype() != cryptonote::MAINNET) + if (m_core.get_nettype() != cryptonote::network_type::MAINNET) m_core.submit_uptime_proof(); TEST_TRIGGER_UPTIME_PROOF::response res{}; @@ -3503,7 +3503,7 @@ namespace cryptonote { namespace rpc { check_quantity_limit(req.entries.size(), BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES); std::optional height = m_core.get_current_blockchain_height(); - uint8_t hf_version = get_network_version(nettype(), *height); + auto hf_version = get_network_version(nettype(), *height); if (req.include_expired) height = std::nullopt; std::vector types; @@ -3668,7 +3668,7 @@ namespace cryptonote { namespace rpc { throw rpc_error{ERROR_WRONG_PARAM, "Unable to resolve BNS address: invalid 'name_hash' value '" + req.name_hash + "'"}; - uint8_t hf_version = m_core.get_blockchain_storage().get_network_version(); + auto hf_version = m_core.get_blockchain_storage().get_network_version(); auto type = static_cast(req.type); if (auto mapping = m_core.get_blockchain_storage().name_system_db().resolve( @@ -3708,8 +3708,8 @@ namespace cryptonote { namespace rpc { std::string reason; bns::mapping_type type = {}; - std::optional hf_version = m_core.get_blockchain_storage().get_network_version(); - if (!bns::validate_mapping_type(req.type, *hf_version, &type, &reason)) + auto hf_version = m_core.get_blockchain_storage().get_network_version(); + if (!bns::validate_mapping_type(req.type, hf_version, &type, &reason)) throw rpc_error{ERROR_INVALID_VALUE_LENGTH, "Invalid BNS type: " + reason}; if (!bns::validate_bns_name(req.name, &reason)) diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index d71a335e500..7f9f5194e10 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -735,7 +735,8 @@ KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(HARD_FORK_INFO::response) - KV_SERIALIZE(version) + KV_SERIALIZE_ENUM(version) + KV_SERIALIZE(revision) KV_SERIALIZE(enabled) KV_SERIALIZE(earliest_height) KV_SERIALIZE(last_height) @@ -1153,7 +1154,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::response::entry) KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(master_node_pubkey); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(registration_height); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(registration_hf_version); + if (all || res->fields.registration_hf_version) KV_SERIALIZE_ENUM(registration_hf_version); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(requested_unlock_height); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_reward_block_height); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_reward_transaction_index); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e6475a12adb..98b637b952e 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -599,7 +599,7 @@ namespace rpc { uint32_t threads_count; // Number of running mining threads. std::string address; // Account address daemon is mining to. Empty if not mining. std::string pow_algorithm; // Current hashing algorithm name - uint32_t block_target; // The expected time to solve per block, i.e. TARGET_BLOCK_TIME_OLD + uint32_t block_target; // The expected time to solve per block, i.e. TARGET_BLOCK_TIME_12 uint64_t block_reward; // Block reward for the current block being mined. uint64_t difficulty; // The difficulty for the current block being mined. @@ -1441,7 +1441,8 @@ namespace rpc { struct response { - uint8_t version; // The major block version for the fork. + hf version; // The major block version for the fork. + uint8_t revision; // The network revision of this daemon (e.g. 1 for HF 19.1). bool enabled; // Indicates whether hard fork is enforced (that is, at or above the requested hardfork) std::optional earliest_height; // Block height at which hard fork will be enabled. std::optional last_height; // The last block height at which this hard fork will be active; will be omitted if this beldexd is not aware of any future hard fork. @@ -2092,7 +2093,7 @@ namespace rpc { struct entry { std::string master_node_pubkey; // The public key of the Master Node. uint64_t registration_height; // The height at which the registration for the Master Node arrived on the blockchain. - uint16_t registration_hf_version; // The hard fork at which the registration for the Master Node arrived on the blockchain. + hf registration_hf_version; // The hard fork at which the registration for the Master Node arrived on the blockchain. uint64_t requested_unlock_height; // The height at which contributions will be released and the Master Node expires. 0 if not requested yet. uint64_t last_reward_block_height; // The height that determines when this service node will next receive a reward. This field is updated when receiving a reward, but is also updated when a MN is activated, recommissioned, or has an IP change position reset. uint32_t last_reward_transaction_index; // When multiple Master Nodes register (or become active/reactivated) at the same height (i.e. have the same last_reward_block_height), this field contains the activating transaction position in the block which is used to break ties in determining which MN is next in the reward list. diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index 4ad5d6d67c1..c332474782a 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -602,7 +602,7 @@ namespace cryptonote::rpc { auto net = m_server.nettype(); m_server_header = "beldexd/"s + (m_restricted ? std::to_string(BELDEX_VERSION[0]) : std::string{BELDEX_VERSION_FULL}) - + (net == MAINNET ? " mainnet" : net == TESTNET ? " testnet" : net == DEVNET ? " devnet" : net == FAKECHAIN ? " fakenet" : " unknown net"); + + (net == network_type::MAINNET ? " mainnet" : net == network_type::TESTNET ? " testnet" : net == network_type::DEVNET ? " devnet" : net == network_type::FAKECHAIN ? " fakenet" : " unknown net"); m_startup_promise.set_value(true); m_sent_startup = true; diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index aadd1f63793..3850a569277 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -1,5 +1,6 @@ #include "lmq_server.h" +#include "cryptonote_config.h" #include "oxenmq/oxenmq.h" #include @@ -133,10 +134,10 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // enough to support unix domain sockets, but for now the Windows default is just "don't listen" #ifndef _WIN32 // Push default .beldex/beldexd.sock - locals.push_back("ipc://" + core.get_config_directory().u8string() + "/" + CRYPTONOTE_NAME + "d.sock"); + locals.push_back("ipc://" + core.get_config_directory().u8string() + "/" + std::string{cryptonote::SOCKET_FILENAME}); // Pushing old default beldexd.sock onto the list. A symlink from .beldex -> .beldex so the user should be able // to communicate via the old .beldex/beldexd.sock - locals.push_back("ipc://" + core.get_config_directory().u8string() + "/beldexd.sock"); + locals.push_back("ipc://" + core.get_config_directory().u8string() + "/" + std::string{cryptonote::SOCKET_FILENAME}); #endif } else if (locals.size() == 1 && locals[0] == "none") { locals.clear(); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ccc9997e339..e77e51b3e82 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -867,10 +867,10 @@ bool simple_wallet::print_fee_info(const std::vector &args/* = std: typical_fees << print_money(typical_fee) << " (" << tools::allowed_priority_strings[1] << ")"; auto hf_version = m_wallet->get_hard_fork_version(); - if (hf_version && *hf_version >= HF_VERSION_FLASH) + if (hf_version && *hf_version >= feature::FLASH) { uint64_t pct = m_wallet->get_fee_percent(tools::tx_priority_flash, txtype::standard); - uint64_t fixed = FLASH_BURN_FIXED; + uint64_t fixed = beldex::FLASH_BURN_FIXED; uint64_t typical_flash_fee = (base_fee.first * typical_size + base_fee.second * typical_outs) * pct / 100 + fixed; @@ -2439,7 +2439,7 @@ bool simple_wallet::set_ignore_outputs_above(const std::vector &arg return true; } if (amount == 0) - amount = MONEY_SUPPLY; + amount = beldex::MONEY_SUPPLY; m_wallet->ignore_outputs_above(amount); m_wallet->rewrite(m_wallet_file, pwd_container->password()); } @@ -3442,7 +3442,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("Can't specify more than one of --testnet and --devnet"); return false; } - network_type const nettype = testnet ? TESTNET : devnet ? DEVNET : MAINNET; + network_type const nettype = testnet ? network_type::TESTNET : devnet ? network_type::DEVNET : network_type::MAINNET; epee::wipeable_string multisig_keys; epee::wipeable_string password; @@ -4775,7 +4775,7 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, tr("idx ") << subaddr_index; } - const uint64_t warn_height = m_wallet->nettype() == TESTNET ? 1000000 : m_wallet->nettype() == DEVNET ? 0 : 1650000; + const uint64_t warn_height = m_wallet->nettype() == network_type::TESTNET ? 1000000 : m_wallet->nettype() == network_type::DEVNET ? 0 : 1650000; if (height >= warn_height) { std::vector tx_extra_fields; @@ -5700,7 +5700,7 @@ bool simple_wallet::confirm_and_send_tx(std::vector 0) { - float days = lock_time_in_blocks / (double) BLOCKS_PER_DAY; + double days = lock_time_in_blocks / (double) BLOCKS_PER_DAY; prompt << boost::format(tr(".\nThis transaction (including %s change) will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % cryptonote::print_money(change) % ((unsigned long long)unlock_block) % days; } @@ -5714,7 +5714,7 @@ bool simple_wallet::confirm_and_send_tx(std::vector(&vin)) { - if (in_to_key->key_offsets.size() != CRYPTONOTE_DEFAULT_TX_MIXIN + 1) + if (in_to_key->key_offsets.size() != cryptonote::TX_OUTPUT_DECOYS + 1) default_ring_size = false; } } @@ -5975,7 +5975,7 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vector hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) { fail_msg_writer() << tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED; @@ -5983,7 +5983,7 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vectorcreate_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params); + ptx_vector = m_wallet->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params); if (ptx_vector.empty()) { @@ -6960,14 +6960,14 @@ bool simple_wallet::bns_encrypt(std::vector args) return false; } - std::string reason; - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) { tools::fail_msg_writer() << tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED; return false; } + std::string reason; if (!bns::validate_bns_name(name, &reason)) { tools::fail_msg_writer() << "Invalid BNS name '" << name << "': " << reason; @@ -6981,7 +6981,7 @@ bool simple_wallet::bns_encrypt(std::vector args) return false; } - bool old_argon2 = type == bns::mapping_type::bchat && *hf_version < cryptonote::network_version_17_POS; + bool old_argon2 = type == bns::mapping_type::bchat && *hf_version < hf::hf17_POS; if (!mval.encrypt(name, nullptr, old_argon2)) { tools::fail_msg_writer() << "Value encryption failed"; @@ -7397,7 +7397,7 @@ bool simple_wallet::coin_burn(std::vector args) { std::vector extra; std::vector ptx_vector; - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) { fail_msg_writer() << tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED; @@ -7407,7 +7407,7 @@ bool simple_wallet::coin_burn(std::vector args) beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::coin_burn, priority, burn_amount); // transaction process called if(burn_amount){ - ptx_vector = m_wallet->create_transactions_2({}, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params); + ptx_vector = m_wallet->create_transactions_2({}, cryptonote::TX_OUTPUT_DECOYS, 0, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params); }else{ tools::wallet2::transfer_container transfers; bool available = false; @@ -7436,7 +7436,7 @@ bool simple_wallet::coin_burn(std::vector args) return false; } - ptx_vector = m_wallet->create_transactions_burn(ki, outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, priority, extra); + ptx_vector = m_wallet->create_transactions_burn(ki, outputs, cryptonote::TX_OUTPUT_DECOYS, 0, priority, extra); } if (ptx_vector.empty()) @@ -7850,7 +7850,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, Transfer transf SCOPED_WALLET_UNLOCK(); try { - auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, unlock_block /* unlock_time */, priority, extra, account, subaddr_indices); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, cryptonote::TX_OUTPUT_DECOYS, unlock_block /* unlock_time */, priority, extra, account, subaddr_indices); sweep_main_internal(sweep_type_t::all_or_below, ptx_vector, info, priority == tools::tx_priority_flash); } catch (const std::exception &e) @@ -7946,7 +7946,7 @@ bool simple_wallet::sweep_single(const std::vector &args_) try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */, priority, extra); + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra); sweep_main_internal(sweep_type_t::single, ptx_vector, info, priority == tools::tx_priority_flash); } catch (const std::exception& e) @@ -9974,8 +9974,8 @@ bool simple_wallet::wallet_info(const std::vector &args) type = tr("Normal"); message_writer() << tr("Type: ") << type; message_writer() << tr("Network type: ") << ( - m_wallet->nettype() == cryptonote::TESTNET ? tr("Testnet") : - m_wallet->nettype() == cryptonote::DEVNET ? tr("Devnet") : tr("Mainnet")); + m_wallet->nettype() == cryptonote::network_type::TESTNET ? tr("Testnet") : + m_wallet->nettype() == cryptonote::network_type::DEVNET ? tr("Devnet") : tr("Mainnet")); return true; } @@ -10389,9 +10389,9 @@ bool simple_wallet::show_transfer(const std::vector &args) success_msg_writer() << "Timestamp: " << tools::get_human_readable_timestamp(pd.m_timestamp); success_msg_writer() << "Amount: " << print_money(pd.m_amount); success_msg_writer() << "Payment ID: " << payment_id; - if (pd.m_unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) + if (pd.m_unlock_time < MAX_BLOCK_NUMBER) { - uint64_t bh = std::max(pd.m_unlock_time, pd.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17); + uint64_t bh = std::max(pd.m_unlock_time, pd.m_block_height + DEFAULT_TX_SPENDABLE_AGE_V17); uint64_t suggested_threshold = 0; if (!pd.m_unmined_flash) { @@ -10410,7 +10410,7 @@ bool simple_wallet::show_transfer(const std::vector &args) uint64_t current_time = static_cast(time(NULL)); - uint64_t threshold = current_time + tools::to_seconds(CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V3); + uint64_t threshold = current_time + tools::to_seconds(LOCKED_TX_ALLOWED_DELTA_BLOCKS * TARGET_BLOCK_TIME); if (threshold >= pd.m_unlock_time) success_msg_writer() << "unlocked for " << tools::get_human_readable_timespan(std::chrono::seconds(threshold - pd.m_unlock_time)); else @@ -10449,9 +10449,9 @@ bool simple_wallet::show_transfer(const std::vector &args) success_msg_writer() << "Change: " << print_money(change); success_msg_writer() << "Fee: " << print_money(fee); success_msg_writer() << "Destinations: " << dests; - if (pd.m_unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) + if (pd.m_unlock_time < MAX_BLOCK_NUMBER) { - uint64_t bh = std::max(pd.m_unlock_time, pd.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17); + uint64_t bh = std::max(pd.m_unlock_time, pd.m_block_height + DEFAULT_TX_SPENDABLE_AGE_V17); if (bh >= last_block_height) success_msg_writer() << "Locked: " << (bh - last_block_height) << " blocks to unlock"; else @@ -10460,7 +10460,7 @@ bool simple_wallet::show_transfer(const std::vector &args) else { uint64_t current_time = static_cast(time(NULL)); - uint64_t threshold = current_time + tools::to_seconds(CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V3); + uint64_t threshold = current_time + tools::to_seconds(LOCKED_TX_ALLOWED_DELTA_BLOCKS * TARGET_BLOCK_TIME); if (threshold >= pd.m_unlock_time) success_msg_writer() << "unlocked for " << tools::get_human_readable_timespan(std::chrono::seconds(threshold - pd.m_unlock_time)); else diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 3965355ffad..4b54a8c6e4a 100755 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -373,7 +373,7 @@ namespace cryptonote { auto current_time = std::chrono::system_clock::now(); auto hf_version = cryptonote::get_network_version(nettype, height); - const auto node_update_threshold = (hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD) / 2; + const auto node_update_threshold = (hf_version >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12) / 2; if (node_update_threshold < current_time - m_blockchain_height_update_time || m_blockchain_height <= height) { update_blockchain_height(); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 8f1352d7e95..5a151f7c6f7 100755 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -44,7 +44,6 @@ target_link_libraries(wallet rpc_server_base cryptonote_core mnemonics - device_trezor net lmdb rpc_http_client @@ -54,6 +53,10 @@ target_link_libraries(wallet PRIVATE extra) +if(TARGET device_trezor) + target_link_libraries(wallet PUBLIC device_trezor) +endif() + beldex_add_executable(wallet_rpc_server "beldex-wallet-rpc" wallet_rpc_server.cpp wallet_rpc_server_commands_defs.cpp diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index acf1983dea7..df4e4267301 100755 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -95,9 +95,12 @@ function(combine_archives output_archive) endfunction(combine_archives) if (STATIC AND BUILD_STATIC_DEPS) - set(merged_protobuf) + set(optional_deps) if(TARGET protobuf_lite) - set(merged_protobuf protobuf_lite) + list(APPEND optional_deps protobuf_lite) + endif() + if(TARGET device_trezor) + list(APPEND optional_deps device_trezor) endif() combine_archives(wallet_merged @@ -116,17 +119,16 @@ if (STATIC AND BUILD_STATIC_DEPS) checkpoints version net - device_trezor epee blockchain_db rpc_http_client rpc_commands + ${optional_deps} # Static deps: Boost::program_options Boost::serialization Boost::system Boost::thread zlib sqlite3 - ${merged_protobuf} sodium libzmq CURL::libcurl diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 831c3cfb371..0ab68c7e0b5 100755 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -167,7 +167,8 @@ uint64_t PendingTransactionImpl::amount() const std::optional hf_version = m_wallet.hardForkVersion(); if (hf_version) { - if (master_nodes::tx_get_staking_components_and_amounts(static_cast(w->nettype()), *hf_version, ptx.tx, height, &sc) + auto hf = static_cast(*hf_version); + if (master_nodes::tx_get_staking_components_and_amounts(static_cast(w->nettype()), hf, ptx.tx, height, &sc) && sc.transferred > 0) result = sc.transferred; } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 1789589d8da..8261326aad7 100755 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -74,9 +74,9 @@ namespace { auto dir = tools::get_default_data_dir(); // remove .beldex, replace with .shared-ringdb dir.replace_filename(".shared-ringdb"); - if (nettype == cryptonote::TESTNET) + if (nettype == cryptonote::network_type::TESTNET) dir /= "testnet"; - else if (nettype == cryptonote::DEVNET) + else if (nettype == cryptonote::network_type::DEVNET) dir /= "devnet"; return dir; } @@ -324,7 +324,7 @@ EXPORT uint64_t Wallet::amountFromDouble(double amount) { std::stringstream ss; - ss << std::fixed << std::setprecision(CRYPTONOTE_DISPLAY_DECIMAL_POINT) << amount; + ss << std::fixed << std::setprecision(beldex::DISPLAY_DECIMAL_POINT) << amount; return amountFromString(ss.str()); } @@ -1645,7 +1645,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector hf_version = w->get_hard_fork_version(); + auto hf_version = w->get_hard_fork_version(); if (!hf_version) { setStatusError(tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED); @@ -1653,11 +1653,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorm_pending_tx = w->create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */, + transaction->m_pending_tx = w->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices, tx_params); } else { - transaction->m_pending_tx = w->create_transactions_all(0, info.address, info.is_subaddress, 1, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */, + transaction->m_pending_tx = w->create_transactions_all(0, info.address, info.is_subaddress, 1, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices); } @@ -1779,7 +1779,7 @@ PendingTransaction *WalletImpl::createSweepAllTransaction(uint32_t priority, uin break; } try { - transaction->m_pending_tx = w->create_transactions_all(0, info.address, info.is_subaddress, 1, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */, + transaction->m_pending_tx = w->create_transactions_all(0, info.address, info.is_subaddress, 1, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices); pendingTxPostProcess(transaction); @@ -3102,13 +3102,16 @@ void WalletImpl::hardForkInfo(uint8_t &version, uint64_t &earliest_height) const EXPORT std::optional WalletImpl::hardForkVersion() const { - return m_wallet_ptr->get_hard_fork_version(); + auto v = m_wallet_ptr->get_hard_fork_version(); + if (!v) + return std::nullopt; + return static_cast(*v); } EXPORT bool WalletImpl::useForkRules(uint8_t version, int64_t early_blocks) const { - return wallet()->use_fork_rules(version = 17,early_blocks); + return wallet()->use_fork_rules(static_cast(version), early_blocks); // have to update } EXPORT diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index ddf268b16ba..4289179819f 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -174,7 +174,7 @@ bool NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_heigh return true; } -std::optional NodeRPCProxy::get_hardfork_version() const +std::optional NodeRPCProxy::get_hardfork_version() const { if (m_offline) return std::nullopt; diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index bca24375507..80079360861 100755 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -54,7 +54,7 @@ class NodeRPCProxy bool get_earliest_height(uint8_t version, uint64_t &earliest_height) const; bool get_dynamic_base_fee_estimate(uint64_t grace_blocks, cryptonote::byte_and_output_fees &fees) const; bool get_fee_quantization_mask(uint64_t &fee_quantization_mask) const; - std::optional get_hardfork_version() const; + std::optional get_hardfork_version() const; std::pair> get_master_nodes(std::vector pubkeys) const; std::pair> get_all_master_nodes() const; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 794d3a77d08..5cda407f1d7 100755 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -103,11 +103,11 @@ fs::path get_rings_filename(fs::path filename) static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - uint8_t buffer[sizeof(key_image) + sizeof(key) + config::HASH_KEY_RINGDB.size() + sizeof(field)]; + uint8_t buffer[sizeof(key_image) + sizeof(key) + cryptonote::hashkey::RINGDB.size() + sizeof(field)]; memcpy(buffer, &key_image, sizeof(key_image)); memcpy(buffer + sizeof(key_image), &key, sizeof(key)); - memcpy(buffer + sizeof(key_image) + sizeof(key), config::HASH_KEY_RINGDB.data(), config::HASH_KEY_RINGDB.size()); - memcpy(buffer + sizeof(key_image) + sizeof(key) + config::HASH_KEY_RINGDB.size(), &field, sizeof(field)); + memcpy(buffer + sizeof(key_image) + sizeof(key), cryptonote::hashkey::RINGDB.data(), cryptonote::hashkey::RINGDB.size()); + memcpy(buffer + sizeof(key_image) + sizeof(key) + cryptonote::hashkey::RINGDB.size(), &field, sizeof(field)); crypto::hash hash; // if field is 0, backward compat mode: hash without the field crypto::cn_fast_hash(buffer, sizeof(buffer) - !field, hash.data); diff --git a/src/wallet/tx_construction_data.h b/src/wallet/tx_construction_data.h index dc00e908df4..ce3cce4cc9d 100755 --- a/src/wallet/tx_construction_data.h +++ b/src/wallet/tx_construction_data.h @@ -47,7 +47,7 @@ struct tx_construction_data uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer std::set subaddr_indices; // set of address indices used as inputs in this transfer - uint8_t hf_version; + cryptonote::hf hf_version; cryptonote::txtype tx_type; }; @@ -106,7 +106,7 @@ void serialize(Archive &a, wallet::tx_construction_data &x, const unsigned int v if (ver < 6) { x.tx_type = cryptonote::txtype::standard; - x.hf_version = cryptonote::network_version_14_enforce_checkpoints; + x.hf_version = cryptonote::hf::hf14_enforce_checkpoints; } } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6a7dbcb9ef1..d5a63a3e449 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -79,6 +79,9 @@ #include "ringct/rctSigs.h" #include "ringdb.h" #include "device/device_cold.hpp" +#ifdef DEVICE_TREZOR_READY +# include "device_trezor/device_trezor.hpp" +#endif #include "device_trezor/device_trezor.hpp" #include "cryptonote_core/master_node_list.h" @@ -115,7 +118,7 @@ namespace { constexpr float RECENT_OUTPUT_RATIO = 0.5f; // 50% of outputs are from the recent zone constexpr float RECENT_OUTPUT_DAYS = 1.8f; // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) constexpr time_t RECENT_OUTPUT_ZONE = RECENT_OUTPUT_DAYS * 86400; - + constexpr uint64_t RECENT_OUTPUT_BLOCKS = RECENT_OUTPUT_DAYS * BLOCKS_PER_DAY; constexpr uint64_t FEE_ESTIMATE_GRACE_BLOCKS = 10; // estimate fee valid for that many blocks @@ -134,11 +137,11 @@ namespace { constexpr double GAMMA_SCALE = 1/1.61; constexpr uint32_t DEFAULT_MIN_OUTPUT_COUNT = 5; - constexpr uint64_t DEFAULT_MIN_OUTPUT_VALUE = 2*COIN; + constexpr uint64_t DEFAULT_MIN_OUTPUT_VALUE = 2 * beldex::COIN; constexpr auto DEFAULT_INACTIVITY_LOCK_TIMEOUT = 10min; - constexpr uint8_t IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION = 12; + constexpr hf IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION = hf::hf12_security_signature; constexpr std::string_view SIG_MAGIC = "SigV1"sv; constexpr std::string_view MULTISIG_MAGIC = "MultisigV1"sv; @@ -323,7 +326,7 @@ std::unique_ptr make_basic(const boost::program_options::variabl const bool testnet = command_line::get_arg(vm, opts.testnet); const bool devnet = command_line::get_arg(vm, opts.devnet); const bool fakenet = command_line::get_arg(vm, opts.regtest); - network_type nettype = testnet ? TESTNET : devnet ? DEVNET : fakenet ? FAKECHAIN : MAINNET; + network_type nettype = testnet ? network_type::TESTNET : devnet ? network_type::DEVNET : fakenet ? network_type::FAKECHAIN : network_type::MAINNET; THROW_WALLET_EXCEPTION_IF(testnet + devnet + fakenet > 1, tools::error::wallet_internal_error, "At most one of --testnet, --devnet, or --regtest may be specified"); @@ -475,7 +478,7 @@ std::pair, tools::password_container> generate_f { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool devnet = command_line::get_arg(vm, opts.devnet); - const network_type nettype = testnet ? TESTNET : devnet ? DEVNET : MAINNET; + const network_type nettype = testnet ? network_type::TESTNET : devnet ? network_type::DEVNET : network_type::MAINNET; /* GET_FIELD_FROM_JSON_RETURN_ON_ERROR Is a generic macro that can return false. Gcc will coerce this into unique_ptr(nullptr), but clang correctly @@ -908,12 +911,12 @@ gamma_picker::gamma_picker(const std::vector &rct_offsets, double shap rct_offsets(rct_offsets) { gamma = std::gamma_distribution(shape, scale); - THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17, error::wallet_internal_error, "Bad offset calculation"); + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= DEFAULT_TX_SPENDABLE_AGE_V17, error::wallet_internal_error, "Bad offset calculation"); const size_t blocks_in_a_year = BLOCKS_PER_DAY * 365; const size_t blocks_to_consider = std::min(rct_offsets.size(), blocks_in_a_year); const double outputs_to_consider = rct_offsets.back() - (blocks_to_consider < rct_offsets.size() ? rct_offsets[rct_offsets.size() - blocks_to_consider - 1] : 0); begin = rct_offsets.data(); - end = rct_offsets.data() + rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17; + end = rct_offsets.data() + rct_offsets.size() - (std::max(1, DEFAULT_TX_SPENDABLE_AGE_V17) - 1); num_rct_outputs = *(end - 1); THROW_WALLET_EXCEPTION_IF(num_rct_outputs == 0, error::wallet_internal_error, "No rct outputs"); average_output_time = tools::to_seconds(TARGET_BLOCK_TIME) * blocks_to_consider / outputs_to_consider; // this assumes constant target over the whole rct range @@ -1059,10 +1062,10 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_segregate_pre_fork_outputs(true), m_key_reuse_mitigation2(true), m_segregation_height(0), - m_ignore_outputs_above(MONEY_SUPPLY), + m_ignore_outputs_above(beldex::MONEY_SUPPLY), m_ignore_outputs_below(0), m_track_uses(false), - m_inactivity_lock_timeout(m_nettype == MAINNET ? DEFAULT_INACTIVITY_LOCK_TIMEOUT : 0s), + m_inactivity_lock_timeout(m_nettype == network_type::MAINNET ? DEFAULT_INACTIVITY_LOCK_TIMEOUT : 0s), m_is_initialized(false), m_kdf_rounds(kdf_rounds), is_old_file_format(false), @@ -1829,7 +1832,7 @@ void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::has } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector &o_indices, - uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool flash, bool double_spend_seen, + uint64_t height, hf block_version, uint64_t ts, bool miner_tx, bool pool, bool flash, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map, size_t> *output_tracker_cache) { if (!tx.is_transfer() || tx.version <= txversion::v1) @@ -3299,7 +3302,7 @@ void wallet2::process_pool_state(const std::vector &txs) const time_t now = time(NULL); for (const auto &e: txs) { - process_new_transaction(e.tx_hash, e.tx, std::vector(), 0, 0, now, false, true, e.flash, e.double_spend_seen, {}); + process_new_transaction(e.tx_hash, e.tx, std::vector(), 0, hf::none, now, false, true, e.flash, e.double_spend_seen, {}); m_scanned_pool_txs[0].insert(e.tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { @@ -4030,7 +4033,7 @@ std::optional wallet2::get_keys_file_data(const epee::w value2.SetUint64(m_min_output_value); json.AddMember("min_output_value", value2, json.GetAllocator()); - value2.SetInt(CRYPTONOTE_DISPLAY_DECIMAL_POINT); + value2.SetInt(beldex::DISPLAY_DECIMAL_POINT); json.AddMember("default_decimal_point", value2, json.GetAllocator()); value2.SetInt(m_merge_destinations ? 1 :0); @@ -4045,7 +4048,7 @@ std::optional wallet2::get_keys_file_data(const epee::w value2.SetInt(m_confirm_export_overwrite ? 1 :0); json.AddMember("confirm_export_overwrite", value2, json.GetAllocator()); - value2.SetUint(m_nettype); + value2.SetUint(static_cast>(m_nettype)); json.AddMember("nettype", value2, json.GetAllocator()); value2.SetInt(m_segregate_pre_fork_outputs ? 1 : 0); @@ -4128,7 +4131,7 @@ void wallet2::setup_keys(const epee::wipeable_string &password) static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); epee::mlocked> cache_key_data; memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; + cache_key_data[HASH_SIZE] = hashkey::WALLET_CACHE; cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); get_ringdb_key(); } @@ -4227,7 +4230,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_segregate_pre_fork_outputs = true; m_key_reuse_mitigation2 = true; m_segregation_height = 0; - m_ignore_outputs_above = MONEY_SUPPLY; + m_ignore_outputs_above = beldex::MONEY_SUPPLY; m_ignore_outputs_below = 0; m_track_uses = false; m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; @@ -4375,21 +4378,21 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st THROW_WALLET_EXCEPTION_IF(static_cast(m_nettype) != field_nettype, error::wallet_internal_error, (boost::format("%s wallet cannot be opened as %s wallet") % (field_nettype == 0 ? "Mainnet" : field_nettype == 1 ? "Testnet" : "Devnet") - % (m_nettype == MAINNET ? "mainnet" : m_nettype == TESTNET ? "testnet" : "devnet")).str()); + % (m_nettype == network_type::MAINNET ? "mainnet" : m_nettype == network_type::TESTNET ? "testnet" : "devnet")).str()); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, segregate_pre_fork_outputs, int, Int, false, true); m_segregate_pre_fork_outputs = field_segregate_pre_fork_outputs; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_reuse_mitigation2, int, Int, false, true); m_key_reuse_mitigation2 = field_key_reuse_mitigation2; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, segregation_height, int, Uint, false, 0); m_segregation_height = field_segregation_height; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_above, uint64_t, Uint64, false, MONEY_SUPPLY); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_above, uint64_t, Uint64, false, beldex::MONEY_SUPPLY); m_ignore_outputs_above = field_ignore_outputs_above; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_below, uint64_t, Uint64, false, 0); m_ignore_outputs_below = field_ignore_outputs_below; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, - m_nettype == MAINNET ? std::chrono::seconds{DEFAULT_INACTIVITY_LOCK_TIMEOUT}.count() : 0); + m_nettype == network_type::MAINNET ? std::chrono::seconds{DEFAULT_INACTIVITY_LOCK_TIMEOUT}.count() : 0); m_inactivity_lock_timeout = std::chrono::seconds{field_inactivity_lock_timeout}; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR); m_subaddress_lookahead_major = field_subaddress_lookahead_major; @@ -4787,7 +4790,7 @@ void wallet2::generate(const fs::path& wallet_, const epee::wipeable_string& pas m_multisig_signers = multisig_signers; setup_keys(password); - create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); + create_keys_file(wallet_, false, password, m_nettype != network_type::MAINNET || create_address_file); setup_new_blockchain(); if (!wallet_.empty()) @@ -4827,7 +4830,7 @@ crypto::secret_key wallet2::generate(const fs::path& wallet_, const epee::wipeab m_refresh_from_block_height = estimate_blockchain_height(); } - create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); + create_keys_file(wallet_, false, password, m_nettype != network_type::MAINNET || create_address_file); setup_new_blockchain(); @@ -4903,7 +4906,7 @@ void wallet2::generate(const fs::path& wallet_, const epee::wipeable_string& pas m_account_public_address = account_public_address; setup_keys(password); - create_keys_file(wallet_, true, password, m_nettype != MAINNET || create_address_file); + create_keys_file(wallet_, true, password, m_nettype != network_type::MAINNET || create_address_file); setup_new_blockchain(); @@ -4972,7 +4975,7 @@ void wallet2::restore_from_device(const fs::path& wallet_, const epee::wipeable_ progress_callback(tr("Retrieved wallet address from device: ") + m_account.get_public_address_str(m_nettype)); m_device_name = device_name; - create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); + create_keys_file(wallet_, false, password, m_nettype != network_type::MAINNET || create_address_file); if (m_subaddress_lookahead_major == SUBADDRESS_LOOKAHEAD_MAJOR && m_subaddress_lookahead_minor == SUBADDRESS_LOOKAHEAD_MINOR) { // the default lookahead setting (50:200) is clearly too much for hardware wallet @@ -6082,10 +6085,10 @@ std::map>> wallet2:: else { uint64_t unlock_height = td.m_unmined_flash && td.m_block_height == 0 ? blockchain_height : td.m_block_height; - unlock_height += std::max(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17, CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS); - if (td.m_tx.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && td.m_tx.unlock_time > unlock_height) + unlock_height += std::max(DEFAULT_TX_SPENDABLE_AGE_V17, LOCKED_TX_ALLOWED_DELTA_BLOCKS); + if (td.m_tx.unlock_time < MAX_BLOCK_NUMBER && td.m_tx.unlock_time > unlock_height) unlock_height = td.m_tx.unlock_time; - uint64_t unlock_time = td.m_tx.unlock_time >= CRYPTONOTE_MAX_BLOCK_NUMBER ? td.m_tx.unlock_time : 0; + uint64_t unlock_time = td.m_tx.unlock_time >= MAX_BLOCK_NUMBER ? td.m_tx.unlock_time : 0; blocks_to_unlock = unlock_height > blockchain_height ? unlock_height - blockchain_height : 0; time_to_unlock = unlock_time > now ? unlock_time - now : 0; amount = 0; @@ -6287,7 +6290,7 @@ void wallet2::get_transfers(get_transfers_args_t args, std::vector(args.max_height, args.min_height); - args.max_height = std::min(args.max_height, CRYPTONOTE_MAX_BLOCK_NUMBER); + args.max_height = std::min(args.max_height, MAX_BLOCK_NUMBER); } int args_count = args.in + args.out + args.stake + args.pending + args.failed + args.pool + args.coinbase; @@ -6620,7 +6623,7 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, if(!is_tx_spendtime_unlocked(unlock_time, block_height)) return false; - if(block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17 > blockchain_height) + if(block_height + DEFAULT_TX_SPENDABLE_AGE_V17 > blockchain_height) return false; if (m_offline) @@ -7128,7 +7131,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector percents.size()) @@ -7761,12 +7764,12 @@ byte_and_output_fees wallet2::get_dynamic_base_fee_estimate() const if (m_node_rpc_proxy.get_dynamic_base_fee_estimate(FEE_ESTIMATE_GRACE_BLOCKS, fees)) return fees; - if (use_fork_rules(cryptonote::network_version_17_POS)) + if (use_fork_rules(hf::hf17_POS)) fees = {FEE_PER_BYTE, FEE_PER_OUTPUT_V17}; - if (use_fork_rules(HF_VERSION_PER_OUTPUT_FEE)) - fees = {FEE_PER_BYTE, FEE_PER_OUTPUT}; // v13 switches back from v12 per-byte fees, add per-output + if (use_fork_rules(feature::PER_OUTPUT_FEE)) + fees = {FEE_PER_BYTE, old::FEE_PER_OUTPUT}; // v13 switches back from v12 per-byte fees, add per-output else - fees = {FEE_PER_BYTE_V12, 0}; + fees = {old::FEE_PER_BYTE_V12, 0}; LOG_PRINT_L1("Failed to query base fee, using " << print_money(fees.first) << "/byte + " << print_money(fees.second) << "/output"); return fees; @@ -7790,7 +7793,7 @@ uint64_t wallet2::get_fee_quantization_mask() const return 1; } -beldex_construct_tx_params wallet2::construct_params(uint8_t hf_version, txtype tx_type, uint32_t priority, uint64_t extra_burn, bns::mapping_years map_years) +beldex_construct_tx_params wallet2::construct_params(hf hf_version, txtype tx_type, uint32_t priority, uint64_t extra_burn, bns::mapping_years map_years) { beldex_construct_tx_params tx_params; tx_params.hf_version = hf_version; @@ -7803,8 +7806,8 @@ beldex_construct_tx_params wallet2::construct_params(uint8_t hf_version, txtype } else if (priority == tools::tx_priority_flash) { - tx_params.burn_fixed = FLASH_BURN_FIXED; - tx_params.burn_percent = FLASH_BURN_TX_FEE_PERCENT_OLD; + tx_params.burn_fixed = beldex::FLASH_BURN_FIXED; + tx_params.burn_percent = beldex::FLASH_BURN_TX_FEE_PERCENT_OLD; } if (extra_burn) tx_params.burn_fixed += extra_burn; @@ -7850,7 +7853,9 @@ crypto::chacha_key wallet2::get_ringdb_key() } void wallet2::register_devices(){ - hw::trezor::register_all(); + #ifdef DEVICE_TREZOR_READY + hw::trezor::register_all(); + #endif } hw::device& wallet2::lookup_device(const std::string & device_descriptor){ @@ -8083,8 +8088,8 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ return result; } - const std::optional res = m_node_rpc_proxy.get_hardfork_version(); - if (!res) + const auto hf_version = m_node_rpc_proxy.get_hardfork_version(); + if (!hf_version) { result.status = stake_result_status::network_version_query_failed; result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; @@ -8102,9 +8107,8 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ total_existing_contributions++; // reserved contributor spot } - uint8_t const hf_version = *res; uint64_t max_contrib_total = mnode_info.staking_requirement - mnode_info.total_reserved; - uint64_t min_contrib_total = master_nodes::get_min_node_contribution(hf_version, mnode_info.staking_requirement, mnode_info.total_reserved, total_existing_contributions); + uint64_t min_contrib_total = master_nodes::get_min_node_contribution(*hf_version, mnode_info.staking_requirement, mnode_info.total_reserved, total_existing_contributions); bool is_preexisting_contributor = false; for (const auto& contributor : mnode_info.contributors) @@ -8132,7 +8136,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ return result; } - const bool full = mnode_info.contributors.size() >= MAX_NUMBER_OF_CONTRIBUTORS; + const bool full = mnode_info.contributors.size() >= beldex::MAX_NUMBER_OF_CONTRIBUTORS; if (full && !is_preexisting_contributor) { result.status = stake_result_status::master_node_contributors_maxed; @@ -8142,7 +8146,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ if (amount < min_contrib_total) { - const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS; + const uint64_t DUST = beldex::MAX_NUMBER_OF_CONTRIBUTORS; if (min_contrib_total - amount <= DUST) { amount = min_contrib_total; @@ -8237,7 +8241,7 @@ wallet2::stake_result wallet2::create_stake_tx(const crypto::public_key& master_ return result; } - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); if (!hf_version) { result.status = stake_result_status::network_version_query_failed; @@ -8246,7 +8250,7 @@ wallet2::stake_result wallet2::create_stake_tx(const crypto::public_key& master_ } beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::stake, priority); - auto ptx_vector = create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, unlock_at_block, priority, extra, 0, subaddr_indices, tx_params); + auto ptx_vector = create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, unlock_at_block, priority, extra, 0, subaddr_indices, tx_params); if (ptx_vector.size() == 1) { result.status = stake_result_status::success; @@ -8316,7 +8320,7 @@ wallet2::register_master_node_result wallet2::create_register_master_node_tx(con // // Parse Registration Contributor Args // - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); if (!hf_version) { result.status = register_master_node_result_status::network_version_query_failed; @@ -8464,7 +8468,7 @@ wallet2::register_master_node_result wallet2::create_register_master_node_tx(con { uint64_t amount_payable_by_operator = 0; { - const uint64_t DUST = MAX_NUMBER_OF_CONTRIBUTORS; + const uint64_t DUST = beldex::MAX_NUMBER_OF_CONTRIBUTORS; uint64_t amount_left = staking_requirement; for (size_t i = 0; i < contributor_args.portions.size(); i++) { @@ -8491,7 +8495,7 @@ wallet2::register_master_node_result wallet2::create_register_master_node_tx(con dest.address = address; beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::stake, priority); - auto ptx_vector = create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices, tx_params); + auto ptx_vector = create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices, tx_params); if (ptx_vector.size() == 1) { result.status = register_master_node_result_status::success; @@ -8564,9 +8568,9 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry std::string error_msg; uint64_t cur_height = get_daemon_blockchain_height(error_msg); - if(version >= cryptonote::network_version_18_bns) + if(version >= hf::hf18_bns) { - if(((p_contributor->amount) < master_nodes::SMALL_CONTRIBUTOR_THRESHOLD * COIN ) && ((cur_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)) + if(((p_contributor->amount) < master_nodes::SMALL_CONTRIBUTOR_THRESHOLD * beldex::COIN ) && ((cur_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)) { result.msg = tr("you can't give the unlock command! you have to wait upto ") + std::to_string(node_info.registration_height + master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER - cur_height) + " Blocks or "+ std::to_string((node_info.registration_height + master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER - cur_height)/ (master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER / 30)) + " days approx"; result.success = false; @@ -8611,7 +8615,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg.append(" other contributors will unlock at the same time."); } result.msg.append("\n\n"); - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); if (!hf_version) { result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; @@ -8875,21 +8879,21 @@ std::vector wallet2::bns_create_buy_mapping_tx(bns::mapping prepared_args.prev_txid); add_beldex_name_system_to_tx_extra(extra, entry); - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); if (!hf_version) { if (reason) *reason = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; return {}; } - if (hf_version <= cryptonote::network_version_17_POS) + if (hf_version <= hf::hf17_POS) { if (reason) *reason = ERR_MSG_BNS_HF_VERSION; return {}; } beldex_construct_tx_params tx_params = wallet2::construct_params(*hf_version, txtype::beldex_name_system, priority, 0, mapping_years); auto result = create_transactions_2({} /*dests*/, - CRYPTONOTE_DEFAULT_TX_MIXIN, + cryptonote::TX_OUTPUT_DECOYS, 0 /*unlock_at_block*/, priority, extra, @@ -8944,7 +8948,7 @@ std::vector wallet2::bns_create_renewal_tx( prepared_args.prev_txid); add_beldex_name_system_to_tx_extra(extra, entry); - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); if (!hf_version) { if (reason) *reason = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; @@ -8953,7 +8957,7 @@ std::vector wallet2::bns_create_renewal_tx( beldex_construct_tx_params tx_params = wallet2::construct_params(*hf_version, txtype::beldex_name_system, priority, 0, map_years); auto result = create_transactions_2({} /*dests*/, - CRYPTONOTE_DEFAULT_TX_MIXIN, + cryptonote::TX_OUTPUT_DECOYS, 0 /*unlock_at_block*/, priority, extra, @@ -9008,7 +9012,7 @@ std::vector wallet2::bns_create_update_mapping_tx(std::stri backup_owner ? &prepared_args.backup_owner : nullptr, prepared_args.prev_txid); add_beldex_name_system_to_tx_extra(extra, entry); - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); if (!hf_version) { if (reason) *reason = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; @@ -9017,7 +9021,7 @@ std::vector wallet2::bns_create_update_mapping_tx(std::stri beldex_construct_tx_params tx_params = wallet2::construct_params(*hf_version, txtype::beldex_name_system, priority, 0,(owner || backup_owner) ? bns::mapping_years::update_owner_record : bns::mapping_years::update_record_internal); auto result = create_transactions_2({} /*dests*/, - CRYPTONOTE_DEFAULT_TX_MIXIN, + cryptonote::TX_OUTPUT_DECOYS, 0 /*unlock_at_block*/, priority, extra, @@ -9117,7 +9121,7 @@ bool wallet2::tx_add_fake_output(std::vector> if (has_rct_distribution) { // check we're clear enough of rct start, to avoid corner cases below - THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17, + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= DEFAULT_TX_SPENDABLE_AGE_V17, error::get_output_distribution, "Not enough rct outputs"); THROW_WALLET_EXCEPTION_IF(rct_offsets.back() <= max_rct_index, error::get_output_distribution, "Daemon reports suspicious number of rct outputs"); @@ -9422,7 +9426,7 @@ void wallet2::get_outs(std::vector> const uint64_t amount = td.is_rct() ? 0 : td.amount(); std::unordered_set seen_indices; // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17 : 0); + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? MINED_MONEY_UNLOCK_WINDOW - DEFAULT_TX_SPENDABLE_AGE_V17 : 0); size_t start = get_outputs.size(); bool use_histogram = amount != 0 || !has_rct_distribution; @@ -9491,7 +9495,7 @@ void wallet2::get_outs(std::vector> else { // the base offset of the first rct output in the first unlocked block (or the one to be if there's none) - num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17]; + num_outs = gamma->get_num_rct_outs(); LOG_PRINT_L1("" << num_outs << " unlocked rct outputs"); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, "histogram reports no unlocked rct outputs, not even ours"); @@ -9769,7 +9773,7 @@ void wallet2::get_outs(std::vector> for(size_t idx: selected_transfers) { const transfer_details &td = m_transfers[idx]; - size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17 : 0); + size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? MINED_MONEY_UNLOCK_WINDOW - DEFAULT_TX_SPENDABLE_AGE_V17 : 0); outs.push_back(std::vector()); outs.back().reserve(fake_outputs_count + 1); const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); @@ -9789,7 +9793,7 @@ void wallet2::get_outs(std::vector> } bool use_histogram = amount != 0 || !has_rct_distribution; if (!use_histogram) - num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17]; + num_outs = gamma->get_num_rct_outs(); // make sure the real outputs we asked for are really included, along // with the correct key and mask: this guards against an active attack @@ -10236,7 +10240,7 @@ void wallet2::transfer_selected_rct(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector extra_plus; // Copy and modified from input if modification needed const std::vector &extra = burning ? extra_plus : extra_base; if (burning) @@ -11615,14 +11619,14 @@ std::vector wallet2::create_transactions_burn(const std::ve uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit(); std::vector> outs; - const bool clsag = use_fork_rules(HF_VERSION_CLSAG, 0); + const bool clsag = use_fork_rules(feature::CLSAG, 0); const rct::RCTConfig rct_config { rct::RangeProofType::PaddedBulletproof, clsag ? 3 : 2 }; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); uint64_t fixed_fee = 0; - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); beldex_construct_tx_params beldex_tx_params = tools::wallet2::construct_params(*hf_version, tx_type, priority); @@ -11633,7 +11637,7 @@ std::vector wallet2::create_transactions_burn(const std::ve // real transactions. std::swap(burn_fixed, beldex_tx_params.burn_fixed); std::swap(burn_percent, beldex_tx_params.burn_percent); - THROW_WALLET_EXCEPTION_IF(beldex_tx_params.hf_version < HF_VERSION_FEE_BURNING, error::wallet_internal_error, "cannot construct transaction: cannot burn amounts under the current hard fork"); + THROW_WALLET_EXCEPTION_IF(beldex_tx_params.hf_version < feature::FEE_BURNING, error::wallet_internal_error, "cannot construct transaction: cannot burn amounts under the current hard fork"); { std::vector extra_plus; // Copy and modified from input if modification needed extra_plus = extra_base; @@ -11810,14 +11814,14 @@ std::vector wallet2::create_transactions_from(const crypton uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit(); std::vector> outs; - const bool clsag = use_fork_rules(HF_VERSION_CLSAG, 0); + const bool clsag = use_fork_rules(feature::CLSAG, 0); const rct::RCTConfig rct_config { rct::RangeProofType::PaddedBulletproof, clsag ? 3 : 2 }; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); uint64_t fixed_fee = 0; - std::optional hf_version = get_hard_fork_version(); + auto hf_version = get_hard_fork_version(); THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); beldex_construct_tx_params beldex_tx_params = tools::wallet2::construct_params(*hf_version, tx_type, priority); @@ -11828,7 +11832,7 @@ std::vector wallet2::create_transactions_from(const crypton std::swap(burn_fixed, beldex_tx_params.burn_fixed); std::swap(burn_percent, beldex_tx_params.burn_percent); bool burning = burn_fixed || burn_percent; - THROW_WALLET_EXCEPTION_IF(burning && beldex_tx_params.hf_version < HF_VERSION_FEE_BURNING, error::wallet_internal_error, "cannot construct transaction: cannot burn amounts under the current hard fork"); + THROW_WALLET_EXCEPTION_IF(burning && beldex_tx_params.hf_version < feature::FEE_BURNING, error::wallet_internal_error, "cannot construct transaction: cannot burn amounts under the current hard fork"); std::vector extra_plus; // Copy and modified from input if modification needed const std::vector &extra = burning ? extra_plus : extra_base; if (burning) @@ -12045,10 +12049,10 @@ void wallet2::cold_sign_tx(const std::vector& 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, 0) ? 3 : 2; - std::optional hf_version = get_hard_fork_version(); + aux_data.bp_version = use_fork_rules(feature::CLSAG, 0) ? 3 : 2; + auto hf_version = get_hard_fork_version(); CHECK_AND_ASSERT_THROW_MES(hf_version, "Failed to query hard fork"); - aux_data.hard_fork = *hf_version; + aux_data.hard_fork = static_cast(*hf_version); dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data); tx_device_aux = aux_data.tx_device_aux; @@ -12093,7 +12097,7 @@ void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) con THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); } //---------------------------------------------------------------------------------------------------- -bool wallet2::use_fork_rules(uint8_t version, uint64_t early_blocks) const +bool wallet2::use_fork_rules(hf version, uint64_t early_blocks) const { // TODO: How to get fork rule info from light wallet node? if(m_light_wallet) @@ -12102,7 +12106,7 @@ bool wallet2::use_fork_rules(uint8_t version, uint64_t early_blocks) const if (!m_node_rpc_proxy.get_height(height)) THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); - if (!m_node_rpc_proxy.get_earliest_height(version, earliest_height)) + if (!m_node_rpc_proxy.get_earliest_height(static_cast(version), earliest_height)) THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); bool close_enough = height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand @@ -12120,7 +12124,7 @@ uint64_t wallet2::get_upper_transaction_weight_limit() const { if (m_upper_transaction_weight_limit > 0) return m_upper_transaction_weight_limit; - return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 / 2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + return BLOCK_GRANTED_FULL_REWARD_ZONE_V5 / 2 - COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- std::vector wallet2::select_available_outputs(const std::function &f) const @@ -14852,7 +14856,7 @@ bool wallet2::is_synced(uint64_t grace_blocks) const //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_segregation_fork_height() const { - if (m_nettype == MAINNET && m_segregation_height > 0) + if (m_nettype == network_type::MAINNET && m_segregation_height > 0) return m_segregation_height; return SEGREGATION_FORK_HEIGHT; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 2b4ea021116..c226978d9be 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -106,6 +106,7 @@ namespace tools uint64_t pick(); gamma_picker(const std::vector &rct_offsets); gamma_picker(const std::vector &rct_offsets, double shape, double scale); + uint64_t get_num_rct_outs() const { return num_rct_outputs; } private: struct gamma_engine @@ -295,7 +296,7 @@ namespace tools static bool verify_password(const fs::path& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); static bool query_device(hw::device::type& device_type, const fs::path& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); - wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false); + wallet2(cryptonote::network_type nettype = cryptonote::network_type::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false); ~wallet2(); struct tx_scan_info_t @@ -793,7 +794,7 @@ namespace tools bool coinbase = false; bool filter_by_height = false; uint64_t min_height = 0; - uint64_t max_height = CRYPTONOTE_MAX_BLOCK_NUMBER; + uint64_t max_height = cryptonote::MAX_BLOCK_NUMBER; std::set subaddr_indices; uint32_t account_index; bool all_accounts; @@ -1069,8 +1070,8 @@ namespace tools const transfer_details &get_transfer_details(size_t idx) const; void get_hard_fork_info (uint8_t version, uint64_t &earliest_height) const; - std::optional get_hard_fork_version() const { return m_node_rpc_proxy.get_hardfork_version(); } - bool use_fork_rules(uint8_t version, uint64_t early_blocks = 0) const; + std::optional get_hard_fork_version() const { return m_node_rpc_proxy.get_hardfork_version(); } + bool use_fork_rules(cryptonote::hf version, uint64_t early_blocks = 0) const; const fs::path& get_wallet_file() const; const fs::path& get_keys_file() const; @@ -1227,7 +1228,7 @@ namespace tools // params constructor, accumulates the burn amounts if the priority is // a flash and, or a bns tx. If it is a flash TX, bns_burn_type is ignored. - static cryptonote::beldex_construct_tx_params construct_params(uint8_t hf_version, cryptonote::txtype tx_type, uint32_t priority, uint64_t extra_burn = 0, bns::mapping_years bns_burn_type = static_cast(0)); + static cryptonote::beldex_construct_tx_params construct_params(cryptonote::hf hf_version, cryptonote::txtype tx_type, uint32_t priority, uint64_t extra_burn = 0, bns::mapping_years bns_burn_type = static_cast(0)); bool is_unattended() const { return m_unattended; } @@ -1481,7 +1482,7 @@ namespace tools */ bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password); bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, std::optional& keys_to_encrypt); - void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool flash, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map, size_t> *output_tracker_cache = NULL); + void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector &o_indices, uint64_t height, cryptonote::hf block_version, uint64_t ts, bool miner_tx, bool pool, bool flash, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map, size_t> *output_tracker_cache = NULL); bool should_skip_block(const cryptonote::block &b, uint64_t height) const; void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector &tx_cache_data, size_t tx_cache_data_offset, std::map, size_t> *output_tracker_cache = NULL); void detach_blockchain(uint64_t height, std::map, size_t> *output_tracker_cache = NULL); @@ -1662,7 +1663,7 @@ namespace tools bool m_light_wallet; /* sends view key to daemon for scanning */ uint64_t m_light_wallet_scanned_block_height; uint64_t m_light_wallet_blockchain_height; - uint64_t m_light_wallet_per_kb_fee = FEE_PER_BYTE_V12 * 1024; + uint64_t m_light_wallet_per_kb_fee = cryptonote::old::FEE_PER_BYTE_V12 * 1024; bool m_light_wallet_connected; uint64_t m_light_wallet_balance; uint64_t m_light_wallet_unlocked_balance; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 02bd6d9d8f9..beaa69a31cb 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1044,11 +1044,11 @@ namespace tools { uint32_t priority = convert_priority(req.priority); - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; cryptonote::beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, cryptonote::txtype::standard, priority); - std::vector ptx_vector = m_wallet->create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, tx_params); + std::vector ptx_vector = m_wallet->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, tx_params); if (ptx_vector.empty()) throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "No transaction created"}; @@ -1080,11 +1080,11 @@ namespace tools if(req.amount) { - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; cryptonote::beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, cryptonote::txtype::coin_burn, req.priority, req.amount); - ptx_vector = m_wallet->create_transactions_2({}, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, req.priority, extra, req.account_index, req.subaddr_indices, tx_params); + ptx_vector = m_wallet->create_transactions_2({}, cryptonote::TX_OUTPUT_DECOYS, 0, req.priority, extra, req.account_index, req.subaddr_indices, tx_params); } else { @@ -1112,7 +1112,7 @@ namespace tools throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "The txid already spent."}; if(!available) throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "No incoming available transfers"}; - ptx_vector = m_wallet->create_transactions_burn(ki, outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, 0, req.priority, extra); + ptx_vector = m_wallet->create_transactions_burn(ki, outputs, cryptonote::TX_OUTPUT_DECOYS, 0, req.priority, extra); } if (ptx_vector.empty()) throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "Failed to create coin_burn transaction:"}; @@ -1151,13 +1151,13 @@ namespace tools { uint32_t priority = convert_priority(req.priority); - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; cryptonote::beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, cryptonote::txtype::standard, priority); LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); - std::vector ptx_vector = m_wallet->create_transactions_2(dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, tx_params); + std::vector ptx_vector = m_wallet->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, tx_params); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); if (ptx_vector.empty()) @@ -1440,7 +1440,7 @@ namespace tools { uint32_t priority = convert_priority(req.priority); - std::vector ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, req.unlock_time, priority, extra, req.account_index, subaddr_indices); + std::vector ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra, req.account_index, subaddr_indices); fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list); @@ -1472,7 +1472,7 @@ namespace tools { uint32_t priority = convert_priority(req.priority); - std::vector ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, CRYPTONOTE_DEFAULT_TX_MIXIN, req.unlock_time, priority, extra); + std::vector ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra); if (ptx_vector.empty()) throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "No outputs found"}; @@ -2934,38 +2934,38 @@ namespace { { VALIDATE_ADDRESS::response res{}; cryptonote::address_parse_info info; - const struct { cryptonote::network_type type; const char *stype; } net_types[] = { - { cryptonote::MAINNET, "mainnet" }, - { cryptonote::TESTNET, "testnet" }, - { cryptonote::DEVNET, "devnet" }, + constexpr std::pair net_types[] = { + { cryptonote::network_type::MAINNET, "mainnet" }, + { cryptonote::network_type::TESTNET, "testnet" }, + { cryptonote::network_type::DEVNET, "devnet" }, }; if (!req.any_net_type && !m_wallet) require_open(); - for (const auto &net_type: net_types) + for (const auto& [type, type_str] : net_types) { - if (!req.any_net_type && (!m_wallet || net_type.type != m_wallet->nettype())) + if (!req.any_net_type && (!m_wallet || type != m_wallet->nettype())) continue; if (req.allow_openalias) { res.valid = false; try { - info = extract_account_addr(net_type.type, req.address); + info = extract_account_addr(type, req.address); res.valid = true; } catch (...) {} if (res.valid) - res.openalias_address = info.as_str(net_type.type); + res.openalias_address = info.as_str(type); } else { - res.valid = cryptonote::get_account_address_from_str(info, net_type.type, req.address); + res.valid = cryptonote::get_account_address_from_str(info, type, req.address); } if (res.valid) { res.integrated = info.has_payment_id; res.subaddress = info.is_subaddress; - res.nettype = net_type.stype; + res.nettype = type_str; return res; } } @@ -3456,7 +3456,7 @@ namespace { std::string reason; bns::mapping_type type = {}; - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; { if (!bns::validate_mapping_type(req.type, *hf_version, &type, &reason)) @@ -3490,7 +3490,7 @@ namespace { throw wallet_rpc_error{error_code::BNS_VALUE_TOO_LONG, "BNS value '" + req.value + "' is too long"}; std::string reason; - std::optional hf_version = m_wallet->get_hard_fork_version(); + auto hf_version = m_wallet->get_hard_fork_version(); if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; bns::mapping_type type; @@ -3504,7 +3504,7 @@ namespace { if (!bns::mapping_value::validate(m_wallet->nettype(), type, req.value, &value, &reason)) throw wallet_rpc_error{error_code::BNS_BAD_VALUE, "Invalid BNS value '" + req.value + "': " + reason}; - bool old_argon2 = type == bns::mapping_type::bchat && *hf_version < cryptonote::network_version_17_POS; + bool old_argon2 = type == bns::mapping_type::bchat && *hf_version < cryptonote::hf::hf17_POS; if (!value.encrypt(req.name, nullptr, old_argon2)) throw wallet_rpc_error{error_code::BNS_VALUE_ENCRYPT_FAILED, "Value encryption failure"}; diff --git a/src/wallet/wallet_rpc_server_commands_defs.cpp b/src/wallet/wallet_rpc_server_commands_defs.cpp index e843c123085..2045d27faf6 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.cpp +++ b/src/wallet/wallet_rpc_server_commands_defs.cpp @@ -645,7 +645,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSFERS::request) KV_SERIALIZE_OPT(coinbase, true); KV_SERIALIZE(filter_by_height); KV_SERIALIZE(min_height); - KV_SERIALIZE_OPT(max_height, (uint64_t)CRYPTONOTE_MAX_BLOCK_NUMBER); + KV_SERIALIZE_OPT(max_height, cryptonote::MAX_BLOCK_NUMBER); KV_SERIALIZE(account_index); KV_SERIALIZE(subaddr_indices); KV_SERIALIZE_OPT(all_accounts, false); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index f5568315f5e..a3ab94244a0 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -1354,7 +1354,7 @@ BELDEX_RPC_DOC_INTROSPECT bool coinbase = false; bool filter_by_height = false; uint64_t min_height = 0; - uint64_t max_height = CRYPTONOTE_MAX_BLOCK_NUMBER; + uint64_t max_height = cryptonote::MAX_BLOCK_NUMBER; std::set subaddr_indices; uint32_t account_index; bool all_accounts; diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 33c5a9b38f1..8433ded1b9c 100755 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -123,13 +123,13 @@ static void test(test_t t, uint64_t blocks) blockchain_objects_t bc_objects = {}; const std::vector hard_forks{ - {cryptonote::network_version_7, 0, 0, 0}, - {cryptonote::network_version_11_infinite_staking, 0, 5000, 0}}; + {cryptonote::hf::hf7, 0, 0, 0}, + {cryptonote::hf::hf11_infinite_staking, 0, 5000, 0}}; const cryptonote::test_options test_options{hard_forks, 5000}; auto& bc = bc_objects.m_blockchain; - if (!bc.init(new TestDB(), nullptr, cryptonote ::FAKECHAIN, true, &test_options, 0)) { + if (!bc.init(new TestDB(), nullptr, cryptonote ::network_type::FAKECHAIN, true, &test_options, 0)) { fprintf(stderr, "Failed to init blockchain\n"); exit(1); }; @@ -137,8 +137,8 @@ static void test(test_t t, uint64_t blocks) for (uint64_t h = 0; h < LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h) { cryptonote::block b; - b.major_version = cryptonote::network_version_7; - b.minor_version = cryptonote::network_version_7; + b.major_version = cryptonote::hf::hf7; + b.minor_version = static_cast(cryptonote::hf::hf7); bc.get_db().add_block(std::make_pair(b, ""), 300000, 300000, bc.get_db().height(), bc.get_db().height(), {}); if (!bc.update_next_cumulative_weight_limit()) { @@ -171,8 +171,8 @@ static void test(test_t t, uint64_t blocks) } uint64_t ltw = bc.get_next_long_term_block_weight(w); cryptonote::block b; - b.major_version = HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - b.minor_version = HF_VERSION_LONG_TERM_BLOCK_WEIGHT; + b.major_version = cryptonote::feature::LONG_TERM_BLOCK_WEIGHT; + b.minor_version = static_cast(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); bc.get_db().add_block(std::make_pair(std::move(b), ""), w, ltw, bc.get_db().height(), bc.get_db().height(), {}); if (!bc.update_next_cumulative_weight_limit()) diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index ef7633c60ef..5d0d1190a37 100755 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -97,7 +97,7 @@ namespace tests bool prepare_handle_incoming_blocks(const std::vector &blocks_entry, std::vector &blocks) { return true; } bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } - size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } + size_t get_block_sync_size(uint64_t height) const { return cryptonote::BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } virtual crypto::hash on_transaction_relayed(const cryptonote::blobdata& tx) { return crypto::null_hash; } cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; } bool get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const { return false; } @@ -106,7 +106,7 @@ namespace tests uint8_t get_ideal_hard_fork_version() const { return 0; } uint8_t get_ideal_hard_fork_version(uint64_t height) const { return 0; } uint8_t get_hard_fork_version(uint64_t height) const { return 0; } - uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return 0; } + uint64_t get_earliest_ideal_height_for_version(cryptonote::hf version) const { return 0; } cryptonote::difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return 0; } uint64_t prevalidate_block_hashes(uint64_t height, const std::list &hashes) { return 0; } uint64_t prevalidate_block_hashes(uint64_t height, const std::vector &hashes) { return 0; } diff --git a/tests/core_tests/beldex_tests.cpp b/tests/core_tests/beldex_tests.cpp index 83c6a6ffa0b..c9b19db1dc2 100755 --- a/tests/core_tests/beldex_tests.cpp +++ b/tests/core_tests/beldex_tests.cpp @@ -190,7 +190,7 @@ bool beldex_checkpointing_alt_chain_receive_checkpoint_votes_should_reorg_back:: gen.add_event_msg("Fork generate two checkpoints worth of blocks."); uint64_t first_checkpointed_height = fork.height(); - uint64_t first_checkpointed_height_hf = fork.top().block.major_version; + cryptonote::hf first_checkpointed_height_hf = fork.top().block.major_version; crypto::hash first_checkpointed_hash = cryptonote::get_block_hash(fork.top().block); std::shared_ptr first_quorum = fork.get_quorum(master_nodes::quorum_type::checkpointing, gen.height()); @@ -438,12 +438,12 @@ bool beldex_checkpointing_master_node_checkpoints_check_reorg_windows::generate( bool beldex_core_block_reward_unpenalized_pre_POS::generate(std::vector& events) { - auto hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_17_POS - 1); + auto hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf16); beldex_chain_generator gen(events, hard_forks); gen.add_blocks_until_version(hard_forks.back().version); - uint8_t newest_hf = hard_forks.back().version; - assert(newest_hf >= cryptonote::network_version_14_enforce_checkpoints); + cryptonote::hf newest_hf = hard_forks.back().version; + assert(newest_hf >= cryptonote::hf::hf14_enforce_checkpoints); gen.add_mined_money_unlock_blocks(); @@ -477,11 +477,11 @@ bool beldex_core_block_reward_unpenalized_pre_POS::generate(std::vector& events) { - std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_count -1, 150 /*Proof Of Stake Delay*/); + std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf20_bulletproof_plusplus, 150 /*Proof Of Stake Delay*/); beldex_chain_generator gen(events, hard_forks); - uint8_t const newest_hf = hard_forks.back().version; - assert(newest_hf >= cryptonote::network_version_14_enforce_checkpoints); + const cryptonote::hf newest_hf = hard_forks.back().version; + assert(newest_hf >= cryptonote::hf::hf14_enforce_checkpoints); gen.add_blocks_until_version(hard_forks.back().version); gen.add_mined_money_unlock_blocks(); @@ -497,12 +497,12 @@ bool beldex_core_block_reward_unpenalized_post_POS::generate(std::vector& events) beldex_chain_generator gen(events, hard_forks); gen.add_blocks_until_version(hard_forks.back().version); - uint8_t newest_hf = hard_forks.back().version; - assert(newest_hf >= cryptonote::network_version_15_flash); + cryptonote::hf newest_hf = hard_forks.back().version; + assert(newest_hf >= cryptonote::hf::hf15_flash); gen.add_mined_money_unlock_blocks(); @@ -622,12 +622,12 @@ bool beldex_core_fee_burning::generate(std::vector& events) bool beldex_core_governance_batched_reward::generate(std::vector& events) { - auto hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_10_bulletproofs); + auto hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf10_bulletproofs); uint64_t hf10_height = 0; for (const auto& [maj, mn_rev, height, ts] : hard_forks) { - if (maj == cryptonote::network_version_10_bulletproofs) + if (maj == cryptonote::hf::hf10_bulletproofs) { hf10_height = height; break; @@ -638,8 +638,8 @@ bool beldex_core_governance_batched_reward::generate(std::vector other_hard_forks = { - {7,0,0,0}, - {8,0,1,0}, - {9,0,hf10_height,0}}; + {cryptonote::hf::hf7,0,0,0}, + {cryptonote::hf::hf8,0,1,0}, + {cryptonote::hf::hf9_master_nodes,0,hf10_height,0}}; std::vector unused_events; beldex_chain_generator no_batched_governance_generator(unused_events, other_hard_forks); @@ -684,7 +684,7 @@ bool beldex_core_governance_batched_reward::generate(std::vector& events) { - constexpr auto& network = cryptonote::get_config(cryptonote::FAKECHAIN); - std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_16); - hard_forks.push_back({cryptonote::network_version_17_POS,0, hard_forks.back().height + network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS + 10}); - hard_forks.push_back({cryptonote::network_version_18_bns,0, hard_forks.back().height + network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS}); + constexpr auto& network = cryptonote::get_config(cryptonote::network_type::FAKECHAIN); + std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf16); + hard_forks.push_back({cryptonote::hf::hf17_POS,0, hard_forks.back().height + network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS + 10}); + hard_forks.push_back({cryptonote::hf::hf18_bns,0, hard_forks.back().height + network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS}); beldex_chain_generator batched_governance_generator(events, hard_forks); - batched_governance_generator.add_blocks_until_version(cryptonote::network_version_18_bns); + batched_governance_generator.add_blocks_until_version(cryptonote::hf::hf18_bns); batched_governance_generator.add_n_blocks(network.GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS); uint64_t hf15_height = 0, hf16_height = 0, hf17_height = 0; for (const auto &hf : hard_forks) { - if (hf.version == cryptonote::network_version_16) + if (hf.version == cryptonote::hf::hf16) hf15_height = hf.height; - else if (hf.version == cryptonote::network_version_17_POS) + else if (hf.version == cryptonote::hf::hf17_POS) hf16_height = hf.height; else hf17_height = hf.height; @@ -729,12 +729,12 @@ bool beldex_core_block_rewards_lrc6::generate(std::vector& eve for (size_t block_height = hf15_height; block_height < hf16_height; ++block_height) { const cryptonote::block &block = blockchain[block_height]; - CHECK_EQ(block.miner_tx.vout.at(0).amount, MINER_REWARD_HF16); - CHECK_EQ(block.miner_tx.vout.at(1).amount, MN_REWARD_HF16); - if (cryptonote::block_has_governance_output(cryptonote::FAKECHAIN, block)) + CHECK_EQ(block.miner_tx.vout.at(0).amount, beldex::MINER_REWARD_HF16); + CHECK_EQ(block.miner_tx.vout.at(1).amount, beldex::MN_REWARD_HF16); + if (cryptonote::block_has_governance_output(cryptonote::network_type::FAKECHAIN, block)) { hf15_gov++; - CHECK_EQ(block.miner_tx.vout.at(2).amount, FOUNDATION_REWARD_HF17 * interval); + CHECK_EQ(block.miner_tx.vout.at(2).amount, beldex::FOUNDATION_REWARD_HF17 * interval); CHECK_EQ(block.miner_tx.vout.size(), 3); } else @@ -744,8 +744,8 @@ bool beldex_core_block_rewards_lrc6::generate(std::vector& eve for (size_t block_height = hf16_height; block_height < hf17_height; ++block_height) { const cryptonote::block &block = blockchain[block_height]; - CHECK_EQ(block.miner_tx.vout.at(0).amount, MN_REWARD_HF16); - if (cryptonote::block_has_governance_output(cryptonote::FAKECHAIN, block)) + CHECK_EQ(block.miner_tx.vout.at(0).amount, beldex::MN_REWARD_HF16); + if (cryptonote::block_has_governance_output(cryptonote::network_type::FAKECHAIN, block)) { hf16_gov++; CHECK_EQ(block.miner_tx.vout.at(1).amount, 0); @@ -758,11 +758,11 @@ bool beldex_core_block_rewards_lrc6::generate(std::vector& eve for (size_t block_height = hf17_height; block_height < height; ++block_height) { const cryptonote::block &block = blockchain[block_height]; - CHECK_EQ(block.miner_tx.vout.at(0).amount, MN_REWARD_HF16); - if (cryptonote::block_has_governance_output(cryptonote::FAKECHAIN, block)) + CHECK_EQ(block.miner_tx.vout.at(0).amount, beldex::MN_REWARD_HF16); + if (cryptonote::block_has_governance_output(cryptonote::network_type::FAKECHAIN, block)) { hf17_gov++; - CHECK_EQ(block.miner_tx.vout.at(1).amount, FOUNDATION_REWARD_HF17 * interval); + CHECK_EQ(block.miner_tx.vout.at(1).amount, beldex::FOUNDATION_REWARD_HF17 * interval); CHECK_EQ(block.miner_tx.vout.size(), 2); } else @@ -781,7 +781,7 @@ bool beldex_core_block_rewards_lrc6::generate(std::vector& eve bool beldex_core_test_deregister_preferred::generate(std::vector &events) { - std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_9_master_nodes); + std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf9_master_nodes); beldex_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); @@ -838,7 +838,7 @@ bool beldex_core_test_deregister_preferred::generate(std::vector &events) { - std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_9_master_nodes); + std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf9_master_nodes); beldex_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); @@ -887,7 +887,7 @@ bool beldex_core_test_deregister_safety_buffer::generate(std::vector& events) { - std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_9_master_nodes); + std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf9_master_nodes); beldex_chain_generator gen(events, hard_forks); gen.add_blocks_until_version(hard_forks.back().version); @@ -1052,8 +1052,8 @@ static bool verify_bns_mapping_record(char const *perr_context, if (expiration_height) CHECK_EQ(*record.expiration_height, *expiration_height); CHECK_EQ(record.txid, txid); - CHECK_TEST_CONDITION_MSG(record.owner == owner, record.owner.to_string(cryptonote::FAKECHAIN) << " == "<< owner.to_string(cryptonote::FAKECHAIN)); - CHECK_TEST_CONDITION_MSG(record.backup_owner == backup_owner, record.backup_owner.to_string(cryptonote::FAKECHAIN) << " == "<< backup_owner.to_string(cryptonote::FAKECHAIN)); + CHECK_TEST_CONDITION_MSG(record.owner == owner, record.owner.to_string(cryptonote::network_type::FAKECHAIN) << " == "<< owner.to_string(cryptonote::network_type::FAKECHAIN)); + CHECK_TEST_CONDITION_MSG(record.backup_owner == backup_owner, record.backup_owner.to_string(cryptonote::network_type::FAKECHAIN) << " == "<< backup_owner.to_string(cryptonote::network_type::FAKECHAIN)); return true; } @@ -1115,7 +1115,7 @@ static bns_keys_t make_bns_keys(cryptonote::account_base const &src) // belnet FAKECHAIN BNS expiry blocks uint64_t bns_expiry(bns::mapping_years map_years) { - auto exp = bns::expiry_blocks(cryptonote::FAKECHAIN, map_years); + auto exp = bns::expiry_blocks(cryptonote::network_type::FAKECHAIN, map_years); if (!exp) throw std::logic_error{"test suite bug: bns_expiry called with non-mapping years"}; return *exp; } @@ -1154,8 +1154,8 @@ bool beldex_name_system_expiration::generate(std::vector &even CHECK_EQ(owner.loaded, true); CHECK_EQ(owner.id, 1); CHECK_TEST_CONDITION_MSG(miner_key.owner == owner.address, - miner_key.owner.to_string(cryptonote::FAKECHAIN) - << " == " << owner.address.to_string(cryptonote::FAKECHAIN)); + miner_key.owner.to_string(cryptonote::network_type::FAKECHAIN) + << " == " << owner.address.to_string(cryptonote::network_type::FAKECHAIN)); bns::mapping_record record = bns_db.get_mapping(name_hash); CHECK_TEST_CONDITION(verify_bns_mapping_record(perr_context, record, bns::mapping_type::belnet, record.encrypted_belnet_value, name, miner_key.belnet_value, height_of_bns_entry, height_of_bns_entry + bns_expiry(mapping_years), tx_hash, miner_key.owner, {} /*backup_owner*/)); @@ -1175,8 +1175,8 @@ bool beldex_name_system_expiration::generate(std::vector &even CHECK_EQ(owner.loaded, true); CHECK_EQ(owner.id, 1); CHECK_TEST_CONDITION_MSG(miner_key.owner == owner.address, - miner_key.owner.to_string(cryptonote::FAKECHAIN) - << " == " << owner.address.to_string(cryptonote::FAKECHAIN)); + miner_key.owner.to_string(cryptonote::network_type::FAKECHAIN) + << " == " << owner.address.to_string(cryptonote::network_type::FAKECHAIN)); bns::mapping_record record = bns_db.get_mapping(name_hash); CHECK_TEST_CONDITION(verify_bns_mapping_record(perr_context, record, bns::mapping_type::belnet, record.encrypted_belnet_value, name, miner_key.belnet_value, height_of_bns_entry, height_of_bns_entry + bns_expiry(mapping_years), tx_hash, miner_key.owner, {} /*backup_owner*/)); @@ -1256,7 +1256,7 @@ bool beldex_name_system_get_mappings_by_owner::generate(std::vector void { uint64_t new_height = cryptonote::get_block_height(gen.top().block) + 1; - uint8_t new_hf_version = gen.get_hf_version_at(new_height); + auto new_hf_version = gen.get_hf_version_at(new_height); uint64_t burn_requirement = bns::burn_needed(new_hf_version, mapping_years); std::vector extra; @@ -1903,8 +1903,8 @@ bool beldex_name_system_name_renewal::generate(std::vector &ev CHECK_EQ(owner.loaded, true); CHECK_EQ(owner.id, 1); CHECK_TEST_CONDITION_MSG(miner_key.owner == owner.address, - miner_key.owner.to_string(cryptonote::FAKECHAIN) - << " == " << owner.address.to_string(cryptonote::FAKECHAIN)); + miner_key.owner.to_string(cryptonote::network_type::FAKECHAIN) + << " == " << owner.address.to_string(cryptonote::network_type::FAKECHAIN)); std::string name_hash = bns::name_to_base64_hash(name); bns::mapping_record record = bns_db.get_mapping(name_hash); @@ -1929,8 +1929,8 @@ bool beldex_name_system_name_renewal::generate(std::vector &ev CHECK_EQ(owner.loaded, true); CHECK_EQ(owner.id, 1); CHECK_TEST_CONDITION_MSG(miner_key.owner == owner.address, - miner_key.owner.to_string(cryptonote::FAKECHAIN) - << " == " << owner.address.to_string(cryptonote::FAKECHAIN)); + miner_key.owner.to_string(cryptonote::network_type::FAKECHAIN) + << " == " << owner.address.to_string(cryptonote::network_type::FAKECHAIN)); std::string name_hash = bns::name_to_base64_hash(name); bns::mapping_record record = bns_db.get_mapping(name_hash); @@ -1957,7 +1957,7 @@ bool beldex_name_system_name_value_max_lengths::generate(std::vector void { uint64_t new_height = cryptonote::get_block_height(gen.top().block) + 1; - uint8_t new_hf_version = gen.get_hf_version_at(new_height); + auto new_hf_version = gen.get_hf_version_at(new_height); uint64_t burn_requirement = bns::burn_needed(new_hf_version, mapping_years); std::vector extra; cryptonote::add_beldex_name_system_to_tx_extra(extra, data); @@ -2056,8 +2056,8 @@ bool beldex_name_system_update_mapping_after_expiry_fails::generate(std::vector< CHECK_EQ(owner.loaded, true); CHECK_EQ(owner.id, 1); CHECK_TEST_CONDITION_MSG(miner_key.owner == owner.address, - miner_key.owner.to_string(cryptonote::FAKECHAIN) - << " == " << owner.address.to_string(cryptonote::FAKECHAIN)); + miner_key.owner.to_string(cryptonote::network_type::FAKECHAIN) + << " == " << owner.address.to_string(cryptonote::network_type::FAKECHAIN)); std::string name_hash = bns::name_to_base64_hash(name); bns::mapping_record record = bns_db.get_mapping(name_hash); @@ -2070,8 +2070,8 @@ bool beldex_name_system_update_mapping_after_expiry_fails::generate(std::vector< return true; } -uint8_t beldex_name_system_update_mapping::hf() { return cryptonote::network_version_count - 1; } -uint8_t beldex_name_system_update_mapping_argon2::hf() { return cryptonote::network_version_16; } +cryptonote::hf beldex_name_system_update_mapping::hf() { return cryptonote::hf::hf20_bulletproof_plusplus; } +cryptonote::hf beldex_name_system_update_mapping_argon2::hf() { return cryptonote::hf::hf16; } bool beldex_name_system_update_mapping::generate(std::vector &events) { auto hard_forks = beldex_generate_hard_fork_table(hf()); @@ -2108,7 +2108,7 @@ bool beldex_name_system_update_mapping::generate(std::vector & }); // Test update mapping with same name fails - if (hf() == cryptonote::network_version_16) { + if (hf() == cryptonote::hf::hf16) { cryptonote::transaction tx1 = gen.create_beldex_name_system_tx_update(miner, gen.hardfork(), bns::mapping_type::bchat, bchat_name1, &miner_key.bchat_value, &miner_key.wallet_value, &miner_key.belnet_value, &miner_key.eth_addr_value); gen.add_tx(tx1, false /*can_be_added_to_blockchain*/, "Can not add a BNS TX that re-updates the underlying value to same value"); } @@ -2560,7 +2560,7 @@ bool beldex_name_system_wrong_burn::generate(std::vector &even assert("Unhandled type enum" == nullptr); uint64_t new_height = cryptonote::get_block_height(gen.top().block) + 1; - uint8_t new_hf_version = gen.get_hf_version_at(new_height); + auto new_hf_version = gen.get_hf_version_at(new_height); uint64_t burn = bns::burn_needed(new_hf_version, mapping_years); if (under_burn) burn -= 1; else burn += 1; @@ -2592,7 +2592,7 @@ bool beldex_name_system_wrong_version::generate(std::vector &e data.encrypted_bchat_value = miner_key.bchat_value.make_encrypted(name).to_string(); uint64_t new_height = cryptonote::get_block_height(gen.top().block) + 1; - uint8_t new_hf_version = gen.get_hf_version_at(new_height); + auto new_hf_version = gen.get_hf_version_at(new_height); uint64_t burn_requirement = bns::burn_needed(new_hf_version, mapping_years); std::vector extra; @@ -2701,7 +2701,7 @@ bool beldex_master_nodes_checkpoint_quorum_size::generate(std::vector &events) { - const auto hard_forks = beldex_generate_hard_fork_table(cryptonote::network_version_9_master_nodes); + const auto hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf9_master_nodes); beldex_chain_generator gen(events, hard_forks); const auto miner = gen.first_miner(); const auto alice = gen.add_account(); @@ -2742,16 +2742,16 @@ bool beldex_master_nodes_gen_nodes::generate(std::vector &even return true; }); - for (auto i = 0u; i < master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,cryptonote::network_version_17_POS); ++i) + for (auto i = 0u; i < master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN,cryptonote::hf::hf17_POS); ++i) gen.create_and_add_next_block(); beldex_register_callback(events, "check_expired", [&events, alice](cryptonote::core &c, size_t ev_index) { DEFINE_TESTS_ERROR_CONTEXT("check_expired"); - const auto stake_lock_time = master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,cryptonote::network_version_17_POS); + const auto stake_lock_time = master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN,cryptonote::hf::hf17_POS); std::vector blocks; - size_t count = 15 + (2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time; + size_t count = 15 + (2 * cryptonote::MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time; bool r = c.get_blocks((uint64_t)0, count, blocks); CHECK_TEST_CONDITION(r); std::vector chain; @@ -2855,7 +2855,7 @@ bool beldex_master_nodes_test_rollback::generate(std::vector& bool beldex_master_nodes_test_swarms_basic::generate(std::vector& events) { const std::vector hard_forks = { - {7,0,0,0}, {8,0,1,0}, {9,0,2,0}, {10,0,150,0}}; + {cryptonote::hf::hf7,0,0,0}, {cryptonote::hf::hf8,0,1,0}, {cryptonote::hf::hf9_master_nodes,0,2,0}, {cryptonote::hf::hf10_bulletproofs,0,150,0}}; beldex_chain_generator gen(events, hard_forks); gen.add_blocks_until_version(hard_forks.rbegin()[1].version); @@ -2871,9 +2871,9 @@ bool beldex_master_nodes_test_swarms_basic::generate(std::vector &eve result.add_blocks_until_version(hard_forks.back().version); result.add_mined_money_unlock_blocks(); - std::vector registration_txs(master_nodes::POS_min_master_nodes(cryptonote::FAKECHAIN)); - for (auto i = 0u; i < master_nodes::POS_min_master_nodes(cryptonote::FAKECHAIN); ++i) + std::vector registration_txs(master_nodes::POS_min_master_nodes(cryptonote::network_type::FAKECHAIN)); + for (auto i = 0u; i < master_nodes::POS_min_master_nodes(cryptonote::network_type::FAKECHAIN); ++i) registration_txs[i] = result.create_and_add_registration_tx(result.first_miner()); // NOTE: Generate Valid Blocks @@ -3105,7 +3105,7 @@ bool beldex_POS_non_participating_validator::generate(std::vector active_mnode_list = params.prev.master_node_state.active_master_nodes_infos(); std::vector entropy = master_nodes::get_POS_entropy_for_next_block(gen.db_, params.prev.block, entry.block.POS.round); - quorum = generate_POS_quorum(cryptonote::FAKECHAIN, params.block_leader.key, entry.block.major_version, active_mnode_list, entropy, entry.block.POS.round); + quorum = generate_POS_quorum(cryptonote::network_type::FAKECHAIN, params.block_leader.key, entry.block.major_version, active_mnode_list, entropy, entry.block.POS.round); assert(quorum.validators.size() == master_nodes::POS_QUORUM_NUM_VALIDATORS); assert(quorum.workers.size() == 1); } @@ -3195,7 +3195,7 @@ bool beldex_POS_generate_blocks::generate(std::vector &events) gen.add_blocks_until_version(hard_forks.back().version); gen.add_mined_money_unlock_blocks(); - add_master_nodes(gen, master_nodes::POS_min_master_nodes(cryptonote::FAKECHAIN)); + add_master_nodes(gen, master_nodes::POS_min_master_nodes(cryptonote::network_type::FAKECHAIN)); gen.add_n_blocks(40); // Chain genereator will generate blocks via POS quorums beldex_register_callback(events, "check_POS_blocks", [](cryptonote::core &c, size_t ev_index) @@ -3219,7 +3219,7 @@ bool beldex_POS_fallback_to_pow_and_back::generate(std::vector gen.add_blocks_until_version(hard_forks.back().version); gen.add_mined_money_unlock_blocks(); - add_master_nodes(gen, master_nodes::POS_min_master_nodes(cryptonote::FAKECHAIN)); + add_master_nodes(gen, master_nodes::POS_min_master_nodes(cryptonote::network_type::FAKECHAIN)); gen.create_and_add_next_block(); gen.add_event_msg("Deregister 1 node, we now have insufficient nodes for POS"); @@ -3266,7 +3266,7 @@ bool beldex_POS_chain_split::generate(std::vector &events) gen.add_blocks_until_version(hard_forks.back().version); gen.add_mined_money_unlock_blocks(); - add_master_nodes(gen, std::max(master_nodes::POS_min_master_nodes(cryptonote::FAKECHAIN), master_nodes::CHECKPOINT_QUORUM_SIZE)); + add_master_nodes(gen, std::max(master_nodes::POS_min_master_nodes(cryptonote::network_type::FAKECHAIN), master_nodes::CHECKPOINT_QUORUM_SIZE)); gen.create_and_add_next_block(); @@ -3311,7 +3311,7 @@ bool beldex_POS_chain_split_with_no_checkpoints::generate(std::vector& events); }; struct beldex_name_system_name_value_max_lengths : public test_chain_unit_base { bool generate(std::vector& events); }; struct beldex_name_system_update_mapping_after_expiry_fails : public test_chain_unit_base { bool generate(std::vector& events); }; -struct beldex_name_system_update_mapping : public test_chain_unit_base { bool generate(std::vector& events); virtual uint8_t hf(); }; -struct beldex_name_system_update_mapping_argon2 : public beldex_name_system_update_mapping { uint8_t hf() override; }; +struct beldex_name_system_update_mapping : public test_chain_unit_base { bool generate(std::vector& events); virtual cryptonote::hf hf(); }; +struct beldex_name_system_update_mapping_argon2 : public beldex_name_system_update_mapping { cryptonote::hf hf() override; }; struct beldex_name_system_update_mapping_multiple_owners : public test_chain_unit_base { bool generate(std::vector& events); }; struct beldex_name_system_update_mapping_non_existent_name_fails : public test_chain_unit_base { bool generate(std::vector& events); }; struct beldex_name_system_update_mapping_invalid_signature : public test_chain_unit_base { bool generate(std::vector& events); }; diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp index bbed7916051..1d190ea1834 100755 --- a/tests/core_tests/block_reward.cpp +++ b/tests/core_tests/block_reward.cpp @@ -74,13 +74,13 @@ namespace } bool construct_max_weight_block(test_generator& generator, block& blk, const block& blk_prev, const account_base& miner_account, - size_t median_block_count = CRYPTONOTE_REWARD_BLOCKS_WINDOW) + size_t median_block_count = cryptonote::REWARD_BLOCKS_WINDOW) { std::vector block_weights; generator.get_last_n_block_weights(block_weights, get_block_hash(blk_prev), median_block_count); size_t median = tools::median(block_weights.begin(), block_weights.end()); - median = std::max(median, static_cast(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1)); + median = std::max(median, static_cast(cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V1)); transaction miner_tx; bool r = construct_miner_tx_by_weight(miner_tx, get_block_height(blk_prev) + 1, generator.get_already_generated_coins(blk_prev), @@ -88,7 +88,7 @@ namespace if (!r) return false; - return generator.construct_block_manually(blk, blk_prev, miner_account, test_generator::bf_miner_tx, 0, 0, 0, + return generator.construct_block_manually(blk, blk_prev, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); } @@ -137,19 +137,19 @@ bool gen_block_reward::generate(std::vector& events) const // Test: miner transactions without outputs (block reward == 0) block blk_0r; - if (!rewind_blocks(events, generator, blk_0r, blk_0, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW)) + if (!rewind_blocks(events, generator, blk_0r, blk_0, miner_account, cryptonote::REWARD_BLOCKS_WINDOW)) return false; - // Test: block reward is calculated using median of the latest CRYPTONOTE_REWARD_BLOCKS_WINDOW blocks + // Test: block reward is calculated using median of the latest cryptonote::REWARD_BLOCKS_WINDOW blocks DO_CALLBACK(events, "mark_invalid_block"); block blk_1_bad_1; - if (!construct_max_weight_block(generator, blk_1_bad_1, blk_0r, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW + 1)) + if (!construct_max_weight_block(generator, blk_1_bad_1, blk_0r, miner_account, cryptonote::REWARD_BLOCKS_WINDOW + 1)) return false; events.push_back(blk_1_bad_1); DO_CALLBACK(events, "mark_invalid_block"); block blk_1_bad_2; - if (!construct_max_weight_block(generator, blk_1_bad_2, blk_0r, miner_account, CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1)) + if (!construct_max_weight_block(generator, blk_1_bad_2, blk_0r, miner_account, cryptonote::REWARD_BLOCKS_WINDOW - 1)) return false; events.push_back(blk_1_bad_2); @@ -170,7 +170,7 @@ bool gen_block_reward::generate(std::vector& events) const DO_CALLBACK(events, "mark_checked_block"); block blk_5r; - if (!rewind_blocks(events, generator, blk_5r, blk_5, miner_account, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)) + if (!rewind_blocks(events, generator, blk_5r, blk_5, miner_account, cryptonote::MINED_MONEY_UNLOCK_WINDOW)) return false; // Test: fee increases block reward @@ -193,7 +193,7 @@ bool gen_block_reward::generate(std::vector& events) const uint64_t txs_fee = get_tx_miner_fee(tx_1, true) + get_tx_miner_fee(tx_2, true); std::vector block_weights; - generator.get_last_n_block_weights(block_weights, get_block_hash(blk_7), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + generator.get_last_n_block_weights(block_weights, get_block_hash(blk_7), cryptonote::REWARD_BLOCKS_WINDOW); size_t median = tools::median(block_weights.begin(), block_weights.end()); transaction miner_tx; @@ -208,7 +208,7 @@ bool gen_block_reward::generate(std::vector& events) const block blk_8; generator.construct_block_manually(blk_8, blk_7, miner_account, test_generator::bf_miner_tx | test_generator::bf_tx_hashes, - 0, 0, 0, crypto::hash(), 0, miner_tx, txs_1_hashes, txs_1_weight); + hf::none, 0, 0, crypto::hash(), 0, miner_tx, txs_1_hashes, txs_1_weight); events.push_back(blk_8); DO_CALLBACK(events, "mark_checked_block"); @@ -249,11 +249,11 @@ bool gen_block_reward::check_block_rewards(cryptonote::core& /*c*/, size_t /*ev_ DEFINE_TESTS_ERROR_CONTEXT("gen_block_reward_without_txs::check_block_rewards"); std::array blk_rewards; - blk_rewards[0] = MONEY_SUPPLY >> EMISSION_SPEED_FACTOR_PER_MINUTE; + blk_rewards[0] = beldex::MONEY_SUPPLY >> EMISSION_SPEED_FACTOR_PER_MINUTE; uint64_t cumulative_reward = blk_rewards[0]; for (size_t i = 1; i < blk_rewards.size(); ++i) { - blk_rewards[i] = (MONEY_SUPPLY - cumulative_reward) >> EMISSION_SPEED_FACTOR_PER_MINUTE; + blk_rewards[i] = (beldex::MONEY_SUPPLY - cumulative_reward) >> EMISSION_SPEED_FACTOR_PER_MINUTE; cumulative_reward += blk_rewards[i]; } diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index 5e6c09ac2bc..223ef73952b 100755 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -46,13 +46,13 @@ namespace for (size_t i = 0; i < new_block_count; ++i) { block blk_next; - difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME_OLD), cryptonote::difficulty_calc_mode::normal); + difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12), cryptonote::difficulty_calc_mode::normal); if (!generator.construct_block_manually(blk_next, blk_prev, miner_account, - test_generator::bf_timestamp | test_generator::bf_diffic, 0, 0, blk_prev.timestamp, crypto::hash(), diffic)) + test_generator::bf_timestamp | test_generator::bf_diffic, hf::none, 0, blk_prev.timestamp, crypto::hash(), diffic)) return false; cummulative_diffic += diffic; - if (timestamps.size() == DIFFICULTY_WINDOW) + if (timestamps.size() == cryptonote::old::DIFFICULTY_WINDOW) { timestamps.erase(timestamps.begin()); cummulative_difficulties.erase(cummulative_difficulties.begin()); @@ -80,7 +80,7 @@ bool gen_block_big_major_version::generate(std::vector& events BLOCK_VALIDATION_INIT_GENERATE(); block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_major_ver, 255); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_major_ver, hf::none); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -93,7 +93,7 @@ bool gen_block_big_minor_version::generate(std::vector& events BLOCK_VALIDATION_INIT_GENERATE(); block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_minor_ver, 0, 255); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_minor_ver, hf::none, 0); events.push_back(blk_1); DO_CALLBACK(events, "check_block_accepted"); @@ -107,7 +107,7 @@ bool gen_block_ts_not_checked::generate(std::vector& events) c REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_account, BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - 2); block blk_1; - generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, 0, 0, blk_0.timestamp - 60 * 60); + generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, hf::none, 0, blk_0.timestamp - 60 * 60); events.push_back(blk_1); DO_CALLBACK(events, "check_block_accepted"); @@ -122,7 +122,7 @@ bool gen_block_ts_in_past::generate(std::vector& events) const uint64_t ts_below_median = var::get(events[BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW / 2 - 1]).timestamp; block blk_1; - generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, 0, 0, ts_below_median); + generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_timestamp, hf::none, 0, ts_below_median); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -135,7 +135,7 @@ bool gen_block_ts_in_future::generate(std::vector& events) con BLOCK_VALIDATION_INIT_GENERATE(); block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_timestamp, 0, 0, time(NULL) + 60*60 + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_timestamp, hf::none, 0, time(NULL) + 60*60 + cryptonote::old::BLOCK_FUTURE_TIME_LIMIT_V2); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -150,7 +150,7 @@ bool gen_block_invalid_prev_id::generate(std::vector& events) block blk_1; crypto::hash prev_id = get_block_hash(blk_0); reinterpret_cast(prev_id) ^= 1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_prev_id, 0, 0, 0, prev_id); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_prev_id, hf::none, 0, 0, prev_id); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -176,7 +176,7 @@ bool gen_block_invalid_nonce::generate(std::vector& events) co return false; // Create invalid nonce - difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME_OLD), cryptonote::difficulty_calc_mode::normal); + difficulty_type diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12), cryptonote::difficulty_calc_mode::normal); assert(1 < diffic); const block& blk_last = var::get(events.back()); uint64_t timestamp = blk_last.timestamp; @@ -186,7 +186,7 @@ bool gen_block_invalid_nonce::generate(std::vector& events) co ++timestamp; blk_3.miner_tx.set_null(); if (!generator.construct_block_manually(blk_3, blk_last, miner_account, - test_generator::bf_diffic | test_generator::bf_timestamp, 0, 0, timestamp, crypto::hash(), diffic)) + test_generator::bf_diffic | test_generator::bf_timestamp, hf::none, 0, timestamp, crypto::hash(), diffic)) return false; } while (0 == blk_3.nonce); @@ -204,7 +204,7 @@ bool gen_block_no_miner_tx::generate(std::vector& events) cons miner_tx.set_null(); block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -220,7 +220,7 @@ bool gen_block_unlock_time_is_low::generate(std::vector& event --miner_tx.unlock_time; block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -236,7 +236,7 @@ bool gen_block_unlock_time_is_high::generate(std::vector& even ++miner_tx.unlock_time; block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -252,7 +252,7 @@ bool gen_block_unlock_time_is_timestamp_in_past::generate(std::vector& events) co var::get(miner_tx.vin[0]).height--; block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -300,7 +300,7 @@ bool gen_block_height_is_high::generate(std::vector& events) c var::get(miner_tx.vin[0]).height++; block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -319,7 +319,7 @@ bool gen_block_miner_tx_has_2_tx_gen_in::generate(std::vector& miner_tx.vin.push_back(in); block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -335,14 +335,14 @@ bool gen_block_miner_tx_has_2_in::generate(std::vector& events transaction tmp_tx; - if (!beldex_tx_builder(events, tmp_tx, blk_0r, miner_account, miner_account.get_keys().m_account_address, blk_0.miner_tx.vout[0].amount, cryptonote::network_version_7).build()) + if (!beldex_tx_builder(events, tmp_tx, blk_0r, miner_account, miner_account.get_keys().m_account_address, blk_0.miner_tx.vout[0].amount, cryptonote::hf::hf7).build()) return false; MAKE_MINER_TX_MANUALLY(miner_tx, blk_0r); miner_tx.vin.push_back(tmp_tx.vin[0]); block blk_1; - generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0r, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -362,14 +362,14 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); transaction tmp_tx; - if (!beldex_tx_builder(events, tmp_tx, blk_1r, miner_account, miner_account.get_keys().m_account_address, blk_1.miner_tx.vout[0].amount, cryptonote::network_version_7).build()) + if (!beldex_tx_builder(events, tmp_tx, blk_1r, miner_account, miner_account.get_keys().m_account_address, blk_1.miner_tx.vout[0].amount, cryptonote::hf::hf7).build()) return false; MAKE_MINER_TX_MANUALLY(miner_tx, blk_1); miner_tx.vin[0] = tmp_tx.vin[0]; block blk_2; - generator.construct_block_manually(blk_2, blk_1r, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_2, blk_1r, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_2); DO_CALLBACK(events, "check_block_purged"); @@ -385,7 +385,7 @@ bool gen_block_miner_tx_out_is_big::generate(std::vector& even miner_tx.vout[0].amount *= 2; block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -402,7 +402,7 @@ bool gen_block_miner_tx_has_no_out::generate(std::vector& even miner_tx.version = txversion::v1; block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_purged"); @@ -438,10 +438,10 @@ static bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx, in.height = height; tx.vin.push_back(in); - // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - const int hard_fork_version = 7; // NOTE(beldex): We know this test doesn't need the new block reward formula + // This will work, until size of constructed block is less then cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE + const auto hard_fork_version = hf::hf7; // NOTE(beldex): We know this test doesn't need the new block reward formula uint64_t block_reward, block_reward_unpenalized; - if (!get_base_block_reward(0, 0, already_generated_coins, block_reward, block_reward_unpenalized, 1, 0)) { + if (!get_base_block_reward(0, 0, already_generated_coins, block_reward, block_reward_unpenalized, hf::hf1, 0)) { LOG_PRINT_L0("Block is too big"); return false; } @@ -453,7 +453,7 @@ static bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx, } tx.version = txversion::v1; - tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + tx.unlock_time = height + cryptonote::MINED_MONEY_UNLOCK_WINDOW; /// half of the miner reward goes to the other account const auto miner_reward = block_reward / 2; @@ -479,7 +479,7 @@ static bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx, } tx.vout.push_back({governance_reward, out_eph_public_key}); - tx.output_unlock_times.push_back(height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + tx.output_unlock_times.push_back(height + cryptonote::MINED_MONEY_UNLOCK_WINDOW); } return true; @@ -502,7 +502,7 @@ bool gen_block_miner_tx_has_out_to_alice::generate(std::vector construct_miner_tx_with_extra_output(miner_tx, miner_address, height+1, coins, alice_address); block blk_1; - generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx); + generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx); events.push_back(blk_1); DO_CALLBACK(events, "check_block_accepted"); @@ -533,7 +533,7 @@ bool gen_block_is_too_big::generate(std::vector& events) const // Creating a huge miner_tx, it will have a lot of outs MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); miner_tx.version = txversion::v1; - static const size_t tx_out_count = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2; + static const size_t tx_out_count = cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2; uint64_t amount = get_outs_money_amount(miner_tx); uint64_t portion = amount / tx_out_count; @@ -558,7 +558,7 @@ bool gen_block_is_too_big::generate(std::vector& events) const // Block reward will be incorrect, as it must be reduced if cumulative block size is very big, // but in this test it doesn't matter block blk_1; - if (!generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx)) + if (!generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, hf::none, 0, 0, crypto::hash(), 0, miner_tx)) return false; events.push_back(blk_1); @@ -626,8 +626,8 @@ bool gen_block_invalid_binary_format::generate(std::vector& ev // Unlock blk_0 outputs block blk_last = blk_0; - assert(CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW < DIFFICULTY_WINDOW); - for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + assert(cryptonote::MINED_MONEY_UNLOCK_WINDOW < DIFFICULTY_WINDOW); + for (size_t i = 0; i < cryptonote::MINED_MONEY_UNLOCK_WINDOW; ++i) { MAKE_NEXT_BLOCK(events, blk_curr, blk_last, miner_account); timestamps.push_back(blk_curr.timestamp); @@ -640,7 +640,7 @@ bool gen_block_invalid_binary_format::generate(std::vector& ev do { blk_last = var::get(events.back()); - diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME_OLD), cryptonote::difficulty_calc_mode::normal); + diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME_12), cryptonote::difficulty_calc_mode::normal); if (!lift_up_difficulty(events, timestamps, cummulative_difficulties, generator, 1, blk_last, miner_account)) return false; std::cout << "Block #" << events.size() << ", difficulty: " << diffic << std::endl; @@ -655,9 +655,9 @@ bool gen_block_invalid_binary_format::generate(std::vector& ev std::vector tx_hashes; tx_hashes.push_back(get_transaction_hash(tx_0)); size_t txs_weight = get_transaction_weight(tx_0); - diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME_OLD), cryptonote::difficulty_calc_mode::normal); + diffic = next_difficulty_v2(timestamps, cummulative_difficulties,tools::to_seconds(TARGET_BLOCK_TIME_12), cryptonote::difficulty_calc_mode::normal); if (!generator.construct_block_manually(blk_test, blk_last, miner_account, - test_generator::bf_diffic | test_generator::bf_timestamp | test_generator::bf_tx_hashes, 0, 0, blk_last.timestamp, + test_generator::bf_diffic | test_generator::bf_timestamp | test_generator::bf_tx_hashes, hf::none, 0, blk_last.timestamp, crypto::hash(), diffic, transaction(), tx_hashes, txs_weight)) return false; diff --git a/tests/core_tests/block_validation.h b/tests/core_tests/block_validation.h index 4cccf4d445e..feaa2525ae2 100755 --- a/tests/core_tests/block_validation.h +++ b/tests/core_tests/block_validation.h @@ -89,12 +89,12 @@ struct gen_block_big_minor_version : public gen_block_accepted_base<2> bool generate(std::vector& events) const; }; -struct gen_block_ts_not_checked : public gen_block_accepted_base +struct gen_block_ts_not_checked : public gen_block_accepted_base { bool generate(std::vector& events) const; }; -struct gen_block_ts_in_past : public gen_block_verification_base +struct gen_block_ts_in_past : public gen_block_verification_base { bool generate(std::vector& events) const; }; @@ -155,12 +155,12 @@ struct gen_block_miner_tx_has_2_tx_gen_in : public gen_block_verification_base<1 bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_has_2_in : public gen_block_verification_base +struct gen_block_miner_tx_has_2_in : public gen_block_verification_base { bool generate(std::vector& events) const; }; -struct gen_block_miner_tx_with_txin_to_key : public gen_block_verification_base +struct gen_block_miner_tx_with_txin_to_key : public gen_block_verification_base { bool generate(std::vector& events) const; }; diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp index 422cd7932a9..90f3ea37393 100755 --- a/tests/core_tests/bulletproofs.cpp +++ b/tests/core_tests/bulletproofs.cpp @@ -43,7 +43,7 @@ using namespace cryptonote; // Tests bool gen_bp_tx_validation_base::generate_with(std::vector& events, - size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t target_hf, + size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, cryptonote::hf target_hf, const std::function &sources, std::vector &destinations, size_t tx_idx)> &pre_tx, const std::function &post_tx, size_t extra_blocks) const { @@ -52,8 +52,8 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve GENERATE_ACCOUNT(miner_account); MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); - if (target_hf == 0) - target_hf = cryptonote::network_version_count - 1; + if (target_hf == cryptonote::hf::none) + target_hf = cryptonote::hf::hf20_bulletproof_plusplus; // NOTE: Monero tests use multiple null terminated entries in their arrays { int amounts_paid_len = 0; @@ -62,7 +62,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve } std::vector hard_forks = { - {7,0,0}, {8,0,1}, {target_hf, 0, NUM_UNLOCKED_BLOCKS + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW + 1}, + {cryptonote::hf::hf7,0,0}, {cryptonote::hf::hf8,0,1}, {target_hf, 0, NUM_UNLOCKED_BLOCKS + cryptonote::MINED_MONEY_UNLOCK_WINDOW + 1}, }; event_replay_settings settings = {}; settings.hard_forks = hard_forks; @@ -73,13 +73,13 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve cryptonote::account_base miner_accounts[NUM_MINERS]; const cryptonote::block *prev_block = &blk_0; - cryptonote::block blocks[NUM_UNLOCKED_BLOCKS + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW]; + cryptonote::block blocks[NUM_UNLOCKED_BLOCKS + cryptonote::MINED_MONEY_UNLOCK_WINDOW]; for (size_t i = 0; i < NUM_MINERS; ++i) miner_accounts[i].generate(); - uint8_t const first_hf = hard_forks[1].version; - uint8_t const last_hf = hard_forks.back().version; + const cryptonote::hf first_hf = hard_forks[1].version; + const cryptonote::hf last_hf = hard_forks.back().version; generator.m_hf_version = first_hf; for (size_t n = 0; n < NUM_UNLOCKED_BLOCKS; ++n) { CHECK_AND_ASSERT_MES( @@ -88,8 +88,8 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve miner_accounts[n % NUM_MINERS], test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, first_hf, - first_hf, - prev_block->timestamp + tools::to_seconds((first_hf>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)) * 2, // v2 has blocks twice as long + static_cast(first_hf), + prev_block->timestamp + tools::to_seconds((first_hf >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), @@ -105,7 +105,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve cryptonote::block blk_r, blk_last; { blk_last = blocks[NUM_UNLOCKED_BLOCKS - 1]; - for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + for (size_t i = 0; i < cryptonote::MINED_MONEY_UNLOCK_WINDOW; ++i) { CHECK_AND_ASSERT_MES( generator.construct_block_manually(blocks[NUM_UNLOCKED_BLOCKS + i], @@ -113,8 +113,8 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve miner_account, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, first_hf, - first_hf, - blk_last.timestamp + tools::to_seconds((generator.m_hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)) * 2, // v2 has blocks twice as long + static_cast(first_hf), + blk_last.timestamp + tools::to_seconds((generator.m_hf_version >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), @@ -142,8 +142,8 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve miner_account, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version, generator.m_hf_version, - generator.m_hf_version, - blk_last.timestamp + tools::to_seconds((generator.m_hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)) * 2, // v2 has blocks twice as long + static_cast(generator.m_hf_version), + blk_last.timestamp + tools::to_seconds((generator.m_hf_version >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), @@ -182,7 +182,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve amounts_paid, amounts_paid_len, TESTS_DEFAULT_FEE, - CRYPTONOTE_DEFAULT_TX_MIXIN, + cryptonote::TX_OUTPUT_DECOYS, sources, destinations, true, @@ -253,7 +253,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve for (size_t i = 0; i < 8; i++) tx_hash.data[i] = 0x01 + (0x22 * i); static std::mt19937_64 rng{std::random_device{}()}; - std::uniform_int_distribution unif{std::numeric_limits::min()}; + std::uniform_int_distribution unif{std::numeric_limits::min()}; for (size_t i = 8; i < sizeof(tx_hash.data); i++) tx_hash.data[i] = unif(rng); } @@ -296,7 +296,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version, - generator.m_hf_version, generator.m_hf_version, blk_last.timestamp + tools::to_seconds((generator.m_hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)) * 2, // v2 has blocks twice as long + generator.m_hf_version, static_cast(generator.m_hf_version), blk_last.timestamp + tools::to_seconds((generator.m_hf_version >= cryptonote::hf::hf17_POS ? TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, txn_fee), false, "Failed to generate block"); if (!valid) @@ -346,7 +346,7 @@ bool gen_bp_tx_valid_1_old::generate(std::vector& events) cons const uint64_t amounts_paid[] = {MK_COINS(120), (uint64_t)-1}; const size_t bp_sizes[] = {1, (size_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof, 0 } }; - return generate_with(events, 1, amounts_paid, true, rct_config, HF_VERSION_MIN_2_OUTPUTS-1, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1_before_clsag"); }); + return generate_with(events, 1, amounts_paid, true, rct_config, hf_prev(feature::MIN_2_OUTPUTS), NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1_before_clsag"); }); } bool gen_bp_tx_invalid_1_new::generate(std::vector& events) const @@ -356,7 +356,7 @@ bool gen_bp_tx_invalid_1_new::generate(std::vector& events) co const uint64_t amounts_paid[] = {10000, (uint64_t)-1}; const size_t bp_sizes[] = {1, (size_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof, 0 } }; - return generate_with(events, 1, amounts_paid, false, rct_config, HF_VERSION_MIN_2_OUTPUTS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_invalid_1_from_clsag"); }); + return generate_with(events, 1, amounts_paid, false, rct_config, feature::MIN_2_OUTPUTS, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_invalid_1_from_clsag"); }); } bool gen_bp_tx_valid_2::generate(std::vector& events) const @@ -364,7 +364,7 @@ bool gen_bp_tx_valid_2::generate(std::vector& events) const const uint64_t amounts_paid[] = {MK_COINS(60), MK_COINS(60), (uint64_t)-1}; const size_t bp_sizes[] = {2, (size_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof, 0 } }; - return generate_with(events, 1, amounts_paid, true, rct_config, 0, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); }); + return generate_with(events, 1, amounts_paid, true, rct_config, hf::none, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); }); } bool gen_bp_tx_valid_3::generate(std::vector& events) const @@ -373,7 +373,7 @@ bool gen_bp_tx_valid_3::generate(std::vector& events) const const uint64_t amounts_paid[] = {MK_COINS(40), MK_COINS(40), MK_COINS(40), (uint64_t)-1}; const size_t bp_sizes[] = {4, (size_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof , 0 } }; - return generate_with(events, 1, amounts_paid, true, rct_config, 0, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); }); + return generate_with(events, 1, amounts_paid, true, rct_config, hf::none, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); }); } bool gen_bp_tx_valid_16::generate(std::vector& events) const @@ -382,7 +382,7 @@ bool gen_bp_tx_valid_16::generate(std::vector& events) const const uint64_t amounts_paid[] = {MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), MK_COINS(15), (uint64_t)-1}; const size_t bp_sizes[] = {16, (size_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof , 0 } }; - return generate_with(events, 1, amounts_paid, true, rct_config, 0, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); }); + return generate_with(events, 1, amounts_paid, true, rct_config, hf::none, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); }); } bool gen_bp_txs_valid_2_and_2::generate(std::vector& events) const @@ -392,7 +392,7 @@ bool gen_bp_txs_valid_2_and_2::generate(std::vector& events) c const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof, 0 }, {rct::RangeProofType::PaddedBulletproof, 0 } }; - return generate_with(events, 2, amounts_paid, true, rct_config, 0, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); }); + return generate_with(events, 2, amounts_paid, true, rct_config, hf::none, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); }); } bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector& events) const @@ -402,7 +402,7 @@ bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector& events) const @@ -410,7 +410,7 @@ bool gen_bp_tx_invalid_wrong_amount::generate(std::vector& eve DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_wrong_amount"); const uint64_t amounts_paid[] = {10, (uint64_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof, 0 } }; - return generate_with(events, 1, amounts_paid, false, rct_config, 0, NULL, [&](cryptonote::transaction &tx, size_t idx){ + return generate_with(events, 1, amounts_paid, false, rct_config, hf::none, NULL, [&](cryptonote::transaction &tx, size_t idx){ CHECK_TEST_CONDITION(rct::is_rct_bulletproof(tx.rct_signatures.type)); CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty()); tx.rct_signatures.p.bulletproofs.back() = rct::bulletproof_PROVE(1000, rct::skGen()); @@ -423,7 +423,7 @@ bool gen_rct2_tx_clsag_malleability::generate(std::vector& eve DEFINE_TESTS_ERROR_CONTEXT("gen_rct_tx_clsag_malleability"); const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1}; const rct::RCTConfig rct_config[] = { { rct::RangeProofType::PaddedBulletproof, 3 } }; - return generate_with(events, 1, amounts_paid, false, rct_config, 0, NULL, [&](cryptonote::transaction &tx, size_t tx_idx) { + return generate_with(events, 1, amounts_paid, false, rct_config, hf::none, NULL, [&](cryptonote::transaction &tx, size_t tx_idx) { CHECK_TEST_CONDITION(tx.version == cryptonote::txversion::v4_tx_types); CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTType::CLSAG); CHECK_TEST_CONDITION(!tx.rct_signatures.p.CLSAGs.empty()); diff --git a/tests/core_tests/bulletproofs.h b/tests/core_tests/bulletproofs.h index e7faa079d73..e8f7be0a535 100755 --- a/tests/core_tests/bulletproofs.h +++ b/tests/core_tests/bulletproofs.h @@ -83,7 +83,7 @@ struct gen_bp_tx_validation_base : public test_chain_unit_base } bool generate_with(std::vector& events, - size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version, + size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, cryptonote::hf hf_version, const std::function &sources, std::vector &destinations, size_t)> &pre_tx, const std::function &post_tx, size_t extra_blocks = 1) const; diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index 79d09d45ca9..b09f26c1dd8 100755 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -146,8 +146,8 @@ bool gen_chain_switch_1::check_split_not_switched(cryptonote::core& c, size_t ev std::vector blocks; bool r = c.get_blocks(0, 10000, blocks); CHECK_TEST_CONDITION(r); - CHECK_EQ(5 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size()); - CHECK_TEST_CONDITION(blocks.back() == var::get(events[20 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_4 + CHECK_EQ(5 + 3 * cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks.size()); + CHECK_TEST_CONDITION(blocks.back() == var::get(events[20 + 3 * cryptonote::MINED_MONEY_UNLOCK_WINDOW])); // blk_4 CHECK_EQ(2, c.get_alternative_blocks_count()); @@ -183,11 +183,11 @@ bool gen_chain_switch_1::check_split_switched(cryptonote::core& c, size_t ev_ind std::vector blocks; bool r = c.get_blocks(0, 10000, blocks); CHECK_TEST_CONDITION(r); - CHECK_EQ(6 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks.size()); + CHECK_EQ(6 + 3 * cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks.size()); auto it = blocks.end(); --it; --it; --it; CHECK_TEST_CONDITION(std::equal(blocks.begin(), it, m_chain_1.begin())); - CHECK_TEST_CONDITION(blocks.back() == var::get(events[24 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW])); // blk_7 + CHECK_TEST_CONDITION(blocks.back() == var::get(events[24 + 3 * cryptonote::MINED_MONEY_UNLOCK_WINDOW])); // blk_7 std::vector alt_blocks; r = c.get_alternative_blocks(alt_blocks); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 87e559c8973..2729a4a1084 100755 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -73,16 +73,16 @@ void beldex_register_callback(std::vector &events, } std::vector -beldex_generate_hard_fork_table(uint8_t hf_version, uint64_t pos_delay) +beldex_generate_hard_fork_table(cryptonote::hf hf_version, uint64_t pos_delay) { - assert(hf_version < cryptonote::network_version_count); + assert(hf_version < cryptonote::hf::_next); // We always need block 0 == v7 for the genesis block: - std::vector result{{cryptonote::network_version_7, 0,0}}; + std::vector result{{cryptonote::hf::hf7, 0,0}}; uint64_t version_height = 1; // HF15 reduces and HF16+ eliminates miner block rewards, so we need to ensure we have enough // HF14 blocks to generate enough BELDEX for tests: - if (hf_version > cryptonote::network_version_15_flash) { - result.push_back({cryptonote::network_version_15_flash,0, version_height}); + if (hf_version > cryptonote::hf::hf15_flash) { + result.push_back({cryptonote::hf::hf15_flash,0, version_height}); version_height += pos_delay; } @@ -168,7 +168,7 @@ beldex_chain_generator::beldex_chain_generator(std::vector &ev : events_(events) , hard_forks_(hard_forks) { - bool init = bns_db_->init(nullptr, cryptonote::FAKECHAIN, bns::init_beldex_name_system("", false /*read_only*/)); + bool init = bns_db_->init(nullptr, cryptonote::network_type::FAKECHAIN, bns::init_beldex_name_system("", false /*read_only*/)); assert(init); first_miner_.generate(); @@ -236,7 +236,7 @@ beldex_blockchain_entry &beldex_chain_generator::add_block(beldex_blockchain_ent db_.tx_table[tx_hash] = tx; } - if (can_be_added_to_blockchain && entry.block.major_version >= cryptonote::network_version_16) + if (can_be_added_to_blockchain && entry.block.major_version >= cryptonote::hf::hf16) { bns_db_->add_block(entry.block, entry.txs); } @@ -268,7 +268,7 @@ cryptonote::account_base beldex_chain_generator::add_account() return account; } -void beldex_chain_generator::add_blocks_until_version(uint8_t hf_version) +void beldex_chain_generator::add_blocks_until_version(cryptonote::hf hf_version) { assert(hard_forks_.size()); assert(hf_version_ <= hard_forks_.back().version); @@ -315,12 +315,12 @@ void beldex_chain_generator::add_master_node_checkpoint(uint64_t block_height, s void beldex_chain_generator::add_mined_money_unlock_blocks() { - add_n_blocks(CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + add_n_blocks(cryptonote::MINED_MONEY_UNLOCK_WINDOW); } -void beldex_chain_generator::add_transfer_unlock_blocks(uint8_t hf_version) +void beldex_chain_generator::add_transfer_unlock_blocks(cryptonote::hf hf_version) { - add_n_blocks((hf_version>=cryptonote::network_version_17_POS?CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE_V17:CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE)); + add_n_blocks((hf_version >= cryptonote::hf::hf17_POS ? cryptonote::DEFAULT_TX_SPENDABLE_AGE_V17 : cryptonote::old::DEFAULT_TX_SPENDABLE_AGE)); } void beldex_chain_generator::add_tx(cryptonote::transaction const &tx, bool can_be_added_to_blockchain, std::string const &fail_msg, bool kept_by_block) @@ -330,7 +330,7 @@ void beldex_chain_generator::add_tx(cryptonote::transaction const &tx, bool can_ cryptonote::transaction beldex_chain_generator::create_and_add_beldex_name_system_tx(cryptonote::account_base const &src, - uint8_t hf_version, + cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::mapping_value const &value_bchat, @@ -348,7 +348,7 @@ beldex_chain_generator::create_and_add_beldex_name_system_tx(cryptonote::account cryptonote::transaction beldex_chain_generator::create_and_add_beldex_name_system_tx_update(cryptonote::account_base const &src, - uint8_t hf_version, + cryptonote::hf hf_version, bns::mapping_type type, std::string const &name, bns::mapping_value const *value_bchat, @@ -367,7 +367,7 @@ beldex_chain_generator::create_and_add_beldex_name_system_tx_update(cryptonote:: cryptonote::transaction beldex_chain_generator::create_and_add_beldex_name_system_tx_renew(cryptonote::account_base const &src, - uint8_t hf_version, + cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::generic_signature *signature, @@ -469,17 +469,17 @@ beldex_chain_generator::create_registration_tx(const cryptonote::account_base &s } uint64_t new_height = get_block_height(top().block) + 1; - uint8_t new_hf_version = get_hf_version_at(new_height); + cryptonote::hf new_hf_version = get_hf_version_at(new_height); const auto staking_requirement = master_nodes::get_staking_requirement(new_height); uint64_t amount = master_nodes::portions_to_amount(portions[0], staking_requirement); uint64_t unlock_time = 0; - if (new_hf_version < cryptonote::network_version_11_infinite_staking) - unlock_time = new_height + master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,cryptonote::network_version_17_POS); + if (new_hf_version < cryptonote::hf::hf11_infinite_staking) + unlock_time = new_height + master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN,cryptonote::hf::hf17_POS); std::vector extra; cryptonote::add_master_node_pubkey_to_tx_extra(extra, master_node_keys.pub); - const uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW; + const uint64_t exp_timestamp = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW); crypto::hash hash; if (!cryptonote::get_registration_hash(contributors, src_operator_cut, portions, exp_timestamp, hash)) @@ -511,11 +511,11 @@ cryptonote::transaction beldex_chain_generator::create_staking_tx(const crypto:: cryptonote::add_master_node_contributor_to_tx_extra(extra, src.get_keys().m_account_address); uint64_t new_height = get_block_height(top().block) + 1; - uint8_t new_hf_version = get_hf_version_at(new_height); + cryptonote::hf new_hf_version = get_hf_version_at(new_height); uint64_t unlock_time = 0; - if (new_hf_version < cryptonote::network_version_11_infinite_staking) - unlock_time = new_height + master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,cryptonote::network_version_17_POS); + if (new_hf_version < cryptonote::hf::hf11_infinite_staking) + unlock_time = new_height + master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN,cryptonote::hf::hf17_POS); beldex_tx_builder(events_, result, top().block, src /*from*/, src.get_keys().m_account_address /*to*/, amount, new_hf_version) .with_tx_type(cryptonote::txtype::stake) @@ -546,7 +546,7 @@ cryptonote::transaction beldex_chain_generator::create_state_change_tx(master_no using scver = cryptonote::tx_extra_master_node_state_change::version_t; cryptonote::tx_extra_master_node_state_change state_change_extra( - hf_version >= cryptonote::network_version_18_bns ? scver::v4_reasons : scver::v0, + hf_version >= cryptonote::hf::hf18_bns ? scver::v4_reasons : scver::v0, state, height, worker_index, reasons_all, reasons_any, {}); if (voters.size()) { @@ -609,7 +609,7 @@ cryptonote::checkpoint_t beldex_chain_generator::create_master_node_checkpoint(u } cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx(cryptonote::account_base const &src, - uint8_t hf_version, + cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::mapping_value const &value_bchat, @@ -632,7 +632,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx(cry cryptonote::block const &head = top().block; uint64_t new_height = get_block_height(top().block) + 1; - uint8_t new_hf_version = get_hf_version_at(new_height); + auto new_hf_version = get_hf_version_at(new_height); uint64_t burn = burn_override.value_or(bns::burn_needed(new_hf_version,mapping_years)); auto lcname = tools::lowercase_ascii_string(name); @@ -643,19 +643,19 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx(cry prev_txid = mapping.txid; bns::mapping_value encrypted_bchat_value = value_bchat; - bool encrypted_bchat = encrypted_bchat_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_bchat = encrypted_bchat_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); assert(encrypted_bchat); bns::mapping_value encrypted_wallet_value = value_wallet; - bool encrypted_wallet = encrypted_wallet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_wallet = encrypted_wallet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); assert(encrypted_wallet); bns::mapping_value encrypted_belnet_value = value_belnet; - bool encrypted_belnet = encrypted_belnet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_belnet = encrypted_belnet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); assert(encrypted_belnet); bns::mapping_value encrypted_eth_addr_value = value_eth_addr; - bool encrypted_eth_addr = encrypted_eth_addr_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_eth_addr = encrypted_eth_addr_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); assert(encrypted_eth_addr); std::vector extra; @@ -673,7 +673,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx(cry } cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_update(cryptonote::account_base const &src, - uint8_t hf_version, + cryptonote::hf hf_version, bns::mapping_type type, std::string const &name, bns::mapping_value const *value_bchat, @@ -702,7 +702,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_upd if (!encrypted_bchat_value.encrypted) { assert(!signature); // Can't specify a signature with an unencrypted value because encrypting generates a new nonce and would invalidate it - bool encrypted_bchat = encrypted_bchat_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_bchat = encrypted_bchat_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); if (use_asserts) assert(encrypted_bchat); } } @@ -714,7 +714,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_upd if (!encrypted_wallet_value.encrypted) { assert(!signature); // Can't specify a signature with an unencrypted value because encrypting generates a new nonce and would invalidate it - bool encrypted_wallet = encrypted_wallet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_wallet = encrypted_wallet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); if (use_asserts) assert(encrypted_wallet); } } @@ -726,7 +726,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_upd if (!encrypted_belnet_value.encrypted) { assert(!signature); // Can't specify a signature with an unencrypted value because encrypting generates a new nonce and would invalidate it - bool encrypted_belnet = encrypted_belnet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_belnet = encrypted_belnet_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); if (use_asserts) assert(encrypted_belnet); } } @@ -738,7 +738,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_upd if (!encrypted_eth_addr_value.encrypted) { assert(!signature); // Can't specify a signature with an unencrypted value because encrypting generates a new nonce and would invalidate it - bool encrypted_eth_addr = encrypted_eth_addr_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::network_version_16); + bool encrypted_eth_addr = encrypted_eth_addr_value.encrypt(lcname, &name_hash, hf_version <= cryptonote::hf::hf16); if (use_asserts) assert(encrypted_eth_addr); } } @@ -761,7 +761,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_upd cryptonote::block const &head = top().block; uint64_t new_height = get_block_height(top().block) + 1; - uint8_t new_hf_version = get_hf_version_at(new_height); + auto new_hf_version = get_hf_version_at(new_height); cryptonote::transaction result = {}; beldex_tx_builder(events_, result, head, src /*from*/, src.get_keys().m_account_address, 0 /*amount*/, new_hf_version) @@ -774,14 +774,14 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_upd } cryptonote::transaction -beldex_chain_generator::create_beldex_name_system_tx_update_w_extra(cryptonote::account_base const &src, uint8_t hf_version, cryptonote::tx_extra_beldex_name_system const &ons_extra) const +beldex_chain_generator::create_beldex_name_system_tx_update_w_extra(cryptonote::account_base const &src, cryptonote::hf hf_version, cryptonote::tx_extra_beldex_name_system const &ons_extra) const { std::vector extra; cryptonote::add_beldex_name_system_to_tx_extra(extra, ons_extra); cryptonote::block const &head = top().block; uint64_t new_height = get_block_height(top().block) + 1; - uint8_t new_hf_version = get_hf_version_at(new_height); + auto new_hf_version = get_hf_version_at(new_height); cryptonote::transaction result = {}; beldex_tx_builder(events_, result, head, src /*from*/, src.get_keys().m_account_address, 0 /*amount*/, new_hf_version) @@ -793,7 +793,7 @@ beldex_chain_generator::create_beldex_name_system_tx_update_w_extra(cryptonote:: } cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_renew(cryptonote::account_base const &src, - uint8_t hf_version, + cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::generic_signature *signature, @@ -808,7 +808,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_ren prev_txid = mapping.txid; } - uint8_t new_hf_version = get_hf_version_at(get_block_height(top().block) + 1); + auto new_hf_version = get_hf_version_at(get_block_height(top().block) + 1); uint64_t burn = burn_override.value_or(bns::burn_needed(new_hf_version, mapping_years)); bns::generic_signature signature_ = {}; @@ -843,7 +843,7 @@ cryptonote::transaction beldex_chain_generator::create_beldex_name_system_tx_ren static void fill_nonce_with_test_generator(test_generator *generator, cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height) { cryptonote::randomx_longhash_context randomx_context = {}; - if (generator->m_hf_version >= cryptonote::network_version_13_checkpointing) + if (generator->m_hf_version >= cryptonote::hf::hf13_checkpointing) { randomx_context.seed_height = crypto::rx_seedheight(height); cryptonote::block prev = blk; @@ -859,7 +859,7 @@ static void fill_nonce_with_test_generator(test_generator *generator, cryptonote blk.nonce = 0; auto get_block_hash = [&randomx_context](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash) { - hash = cryptonote::get_block_longhash(cryptonote::FAKECHAIN, randomx_context, b, height, threads); + hash = cryptonote::get_block_longhash(cryptonote::network_type::FAKECHAIN, randomx_context, b, height, threads); return true; }; @@ -870,7 +870,7 @@ static void fill_nonce_with_test_generator(test_generator *generator, cryptonote void fill_nonce_with_beldex_generator(beldex_chain_generator const *generator, cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height) { cryptonote::randomx_longhash_context randomx_context = {}; - if (generator->blocks().size() && generator->hardfork() >= cryptonote::network_version_13_checkpointing) + if (generator->blocks().size() && generator->hardfork() >= cryptonote::hf::hf13_checkpointing) { randomx_context.seed_height = crypto::rx_seedheight(height); randomx_context.seed_block_hash = cryptonote::get_block_hash(generator->blocks()[randomx_context.seed_height].block); @@ -879,7 +879,7 @@ void fill_nonce_with_beldex_generator(beldex_chain_generator const *generator, c blk.nonce = 0; auto get_block_hash = [&randomx_context](const cryptonote::block &blk, uint64_t height, unsigned int threads, crypto::hash &hash) { - hash = cryptonote::get_block_longhash(cryptonote::FAKECHAIN, randomx_context, blk, height, threads); + hash = cryptonote::get_block_longhash(cryptonote::network_type::FAKECHAIN, randomx_context, blk, height, threads); return true; }; @@ -893,7 +893,7 @@ beldex_blockchain_entry beldex_chain_generator::create_genesis_block(const crypt beldex_blockchain_entry result = {}; cryptonote::block &blk = result.block; blk.major_version = hf_version_; - blk.minor_version = hf_version_; + blk.minor_version = static_cast(hf_version_); blk.timestamp = timestamp; blk.prev_id = crypto::null_hash; @@ -908,7 +908,7 @@ beldex_blockchain_entry beldex_chain_generator::create_genesis_block(const crypt target_block_weight, 0 /*total_fee*/, blk.miner_tx, - cryptonote::beldex_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner.get_keys().m_account_address), + cryptonote::beldex_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner.get_keys().m_account_address), cryptonote::blobdata(), hf_version_); assert(constructed); @@ -966,7 +966,7 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ entry = {}; cryptonote::block &blk = entry.block; blk.major_version = params.hf_version; - blk.minor_version = params.hf_version; + blk.minor_version = static_cast(params.hf_version); blk.timestamp = params.timestamp; blk.prev_id = get_block_hash(params.prev.block); @@ -978,7 +978,7 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ { blk.tx_hashes.push_back(get_transaction_hash(tx)); uint64_t fee = 0; - bool r = get_tx_miner_fee(tx, fee, blk.major_version >= HF_VERSION_FEE_BURNING); + bool r = get_tx_miner_fee(tx, fee, blk.major_version >= cryptonote::feature::FEE_BURNING); CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); txs_weight += get_transaction_weight(tx); if (calc_total_fee) total_fee += fee; @@ -990,7 +990,7 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ std::vector active_snode_list = params.prev.master_node_state.active_master_nodes_infos(); - bool POS_block_is_possible = blk.major_version >= cryptonote::network_version_17_POS && active_snode_list.size() >= master_nodes::POS_min_master_nodes(cryptonote::FAKECHAIN); + bool POS_block_is_possible = blk.major_version >= cryptonote::hf::hf17_POS && active_snode_list.size() >= master_nodes::POS_min_master_nodes(cryptonote::network_type::FAKECHAIN); bool make_POS_block = (params.type == beldex_create_block_type::automatic && POS_block_is_possible) || params.type == beldex_create_block_type::POS; if (make_POS_block) @@ -1003,7 +1003,7 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ // NOTE: Get POS Quorum necessary for this block std::vector entropy = master_nodes::get_POS_entropy_for_next_block(db_, params.prev.block, blk.POS.round); - POS_quorum = master_nodes::generate_POS_quorum(cryptonote::FAKECHAIN, params.block_leader.key, blk.major_version, active_snode_list, entropy, blk.POS.round); + POS_quorum = master_nodes::generate_POS_quorum(cryptonote::network_type::FAKECHAIN, params.block_leader.key, blk.major_version, active_snode_list, entropy, blk.POS.round); assert(POS_quorum.validators.size() == master_nodes::POS_QUORUM_NUM_VALIDATORS); assert(POS_quorum.workers.size() == 1); @@ -1020,25 +1020,23 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ block_producer = master_nodes::master_node_info_to_payout(block_producer_key, *(it->second)); } - miner_tx_context = cryptonote::beldex_miner_tx_context::POS_block(cryptonote::FAKECHAIN, block_producer, params.block_leader); + miner_tx_context = cryptonote::beldex_miner_tx_context::POS_block(cryptonote::network_type::FAKECHAIN, block_producer, params.block_leader); } else { - miner_tx_context = cryptonote::beldex_miner_tx_context::miner_block(cryptonote::FAKECHAIN, params.miner_acc.get_keys().m_account_address, params.block_leader); + miner_tx_context = cryptonote::beldex_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, params.miner_acc.get_keys().m_account_address, params.block_leader); } - if (blk.major_version >= cryptonote::network_version_10_bulletproofs && - cryptonote::height_has_governance_output(cryptonote::FAKECHAIN, blk.major_version, height)) + if (blk.major_version >= cryptonote::hf::hf10_bulletproofs && + cryptonote::height_has_governance_output(cryptonote::network_type::FAKECHAIN, blk.major_version, height)) { - constexpr uint64_t num_blocks = cryptonote::get_config(cryptonote::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; + constexpr uint64_t num_blocks = cryptonote::get_config(cryptonote::network_type::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; uint64_t start_height = height - num_blocks; - static_assert(cryptonote::network_version_count == cryptonote::network_version_19 + 1, - "The code below needs to be updated to support higher hard fork versions"); - if (blk.major_version <= cryptonote::network_version_16) + if (blk.major_version <= cryptonote::hf::hf16) miner_tx_context.batched_governance = 0; - else if (blk.major_version >= cryptonote::network_version_17_POS) - miner_tx_context.batched_governance = (FOUNDATION_REWARD_HF17) * num_blocks; + else if (blk.major_version >= cryptonote::hf::hf17_POS) + miner_tx_context.batched_governance = (beldex::FOUNDATION_REWARD_HF17) * num_blocks; else { for (int i = (int)get_block_height(params.prev.block), count = 0; @@ -1046,8 +1044,8 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ i--, count++) { beldex_blockchain_entry const &historical_entry = db_.blocks[i]; - if (historical_entry.block.major_version < cryptonote::network_version_10_bulletproofs) break; - miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::FAKECHAIN, historical_entry.block, blk.major_version); + if (historical_entry.block.major_version < cryptonote::hf::hf10_bulletproofs) break; + miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::network_type::FAKECHAIN, historical_entry.block, blk.major_version); } } } @@ -1135,7 +1133,7 @@ bool beldex_chain_generator::block_begin(beldex_blockchain_entry &entry, beldex_ void beldex_chain_generator::block_end(beldex_blockchain_entry &entry, beldex_create_block_params const ¶ms) const { entry.master_node_state = params.prev.master_node_state; - entry.master_node_state.update_from_block(db_, cryptonote::FAKECHAIN, state_history_, {} /*state_archive*/, {} /*alt_states*/, entry.block, entry.txs, nullptr); + entry.master_node_state.update_from_block(db_, cryptonote::network_type::FAKECHAIN, state_history_, {} /*state_archive*/, {} /*alt_states*/, entry.block, entry.txs, nullptr); } bool beldex_chain_generator::create_block(beldex_blockchain_entry &entry, @@ -1160,9 +1158,9 @@ beldex_create_block_params beldex_chain_generator::next_block_params() const beldex_create_block_params result = {}; result.prev = prev; result.miner_acc = first_miner_; - result.block_weights = last_n_block_weights(height(), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + result.block_weights = last_n_block_weights(height(), cryptonote::REWARD_BLOCKS_WINDOW); result.hf_version = get_hf_version_at(next_height); - result.timestamp = prev.block.timestamp + tools::to_seconds((result.hf_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)); + result.timestamp = prev.block.timestamp + tools::to_seconds((result.hf_version>=cryptonote::hf::hf17_POS ? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)); result.block_leader = prev.master_node_state.get_block_leader(); result.total_fee = 0; // Request chain generator to calculate the fee return result; @@ -1183,9 +1181,9 @@ beldex_blockchain_entry beldex_chain_generator::create_next_block(const std::vec return result; } -uint8_t beldex_chain_generator::get_hf_version_at(uint64_t height) const { +cryptonote::hf beldex_chain_generator::get_hf_version_at(uint64_t height) const { - uint8_t cur_hf_ver = 0; + cryptonote::hf cur_hf_ver = cryptonote::hf::none; for (auto i = 0u; i < hard_forks_.size(); ++i) { if (height < hard_forks_[i].height) break; @@ -1289,17 +1287,17 @@ void test_generator::add_block(const cryptonote::block& blk, size_t txs_weight, static void manual_calc_batched_governance(const test_generator &generator, const crypto::hash &head, cryptonote::beldex_miner_tx_context &miner_tx_context, - int hard_fork_version, + cryptonote::hf hard_fork_version, uint64_t height) { miner_tx_context.batched_governance = 0; - if (hard_fork_version >= cryptonote::network_version_10_bulletproofs && - cryptonote::height_has_governance_output(cryptonote::FAKECHAIN, hard_fork_version, height)) + if (hard_fork_version >= cryptonote::hf::hf10_bulletproofs && + cryptonote::height_has_governance_output(cryptonote::network_type::FAKECHAIN, hard_fork_version, height)) { - uint64_t num_blocks = cryptonote::get_config(cryptonote::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; + uint64_t num_blocks = cryptonote::get_config(cryptonote::network_type::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; uint64_t start_height = height - num_blocks; - if (hard_fork_version >= cryptonote::network_version_16) + if (hard_fork_version >= cryptonote::hf::hf16) { miner_tx_context.batched_governance = num_blocks * cryptonote::governance_reward_formula(0, hard_fork_version); return; @@ -1321,8 +1319,8 @@ static void manual_calc_batched_governance(const test_generator &generator, if (block_height < start_height) continue; - if (entry.major_version >= cryptonote::network_version_10_bulletproofs) - miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::FAKECHAIN, entry, hard_fork_version); + if (entry.major_version >= cryptonote::hf::hf10_bulletproofs) + miner_tx_context.batched_governance += cryptonote::derive_governance_from_block_reward(cryptonote::network_type::FAKECHAIN, entry, hard_fork_version); } } } @@ -1339,7 +1337,7 @@ bool test_generator::construct_block(cryptonote::block &blk, { /// a temporary workaround blk.major_version = m_hf_version; - blk.minor_version = m_hf_version; + blk.minor_version = static_cast(m_hf_version); blk.timestamp = timestamp; blk.prev_id = prev_id; @@ -1357,13 +1355,13 @@ bool test_generator::construct_block(cryptonote::block &blk, for (auto& tx : tx_list) { uint64_t fee = 0; - bool r = get_tx_miner_fee(tx, fee, blk.major_version >= HF_VERSION_FEE_BURNING); + bool r = get_tx_miner_fee(tx, fee, blk.major_version >= cryptonote::feature::FEE_BURNING); CHECK_AND_ASSERT_MES(r, false, "wrong transaction passed to construct_block"); total_fee += fee; txs_weight += get_transaction_weight(tx); } - auto miner_tx_context = cryptonote::beldex_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc.get_keys().m_account_address, block_leader); + auto miner_tx_context = cryptonote::beldex_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner_acc.get_keys().m_account_address, block_leader); blk.miner_tx = {}; size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height); @@ -1444,10 +1442,10 @@ bool test_generator::construct_block(cryptonote::block &blk, uint64_t height = var::get(blk_prev.miner_tx.vin.front()).height + 1; crypto::hash prev_id = get_block_hash(blk_prev); // Keep difficulty unchanged - uint64_t timestamp = blk_prev.timestamp + tools::to_seconds((blk_prev.major_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)); + uint64_t timestamp = blk_prev.timestamp + tools::to_seconds((blk_prev.major_version >= cryptonote::hf::hf17_POS ? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)); uint64_t already_generated_coins = get_already_generated_coins(prev_id); std::vector block_weights; - get_last_n_block_weights(block_weights, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + get_last_n_block_weights(block_weights, prev_id, cryptonote::REWARD_BLOCKS_WINDOW); return construct_block(blk, height, prev_id, miner_acc, timestamp, already_generated_coins, block_weights, tx_list, block_leader); } @@ -1457,7 +1455,7 @@ bool test_generator::construct_block_manually( const cryptonote::block &prev_block, const cryptonote::account_base &miner_acc, int actual_params /* = bf_none*/, - uint8_t major_ver /* = 0*/, + cryptonote::hf major_ver /* = 0*/, uint8_t minor_ver /* = 0*/, uint64_t timestamp /* = 0*/, const crypto::hash &prev_id /* = crypto::hash()*/, @@ -1467,29 +1465,29 @@ bool test_generator::construct_block_manually( size_t txs_weight /* = 0*/, size_t miner_fee /*= 0*/) { - blk.major_version = actual_params & bf_major_ver ? major_ver : static_cast(cryptonote::network_version_7); - blk.minor_version = actual_params & bf_minor_ver ? minor_ver : static_cast(cryptonote::network_version_7); - blk.timestamp = actual_params & bf_timestamp ? timestamp : prev_block.timestamp + tools::to_seconds(blk.major_version>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD); // Keep difficulty unchanged + blk.major_version = actual_params & bf_major_ver ? major_ver : cryptonote::hf::hf7; + blk.minor_version = actual_params & bf_minor_ver ? minor_ver : static_cast(cryptonote::hf::hf7); + blk.timestamp = actual_params & bf_timestamp ? timestamp : prev_block.timestamp + tools::to_seconds(blk.major_version>=cryptonote::hf::hf17_POS? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12); // Keep difficulty unchanged blk.prev_id = actual_params & bf_prev_id ? prev_id : get_block_hash(prev_block); blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector(); size_t height = get_block_height(prev_block) + 1; uint64_t already_generated_coins = get_already_generated_coins(prev_block); std::vector block_weights; - get_last_n_block_weights(block_weights, get_block_hash(prev_block), CRYPTONOTE_REWARD_BLOCKS_WINDOW); + get_last_n_block_weights(block_weights, get_block_hash(prev_block), cryptonote::REWARD_BLOCKS_WINDOW); if (actual_params & bf_miner_tx) { blk.miner_tx = miner_tx; } else { - // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + // TODO: This will work, until size of constructed block is less then cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE cryptonote::beldex_miner_tx_context miner_tx_context = {}; - miner_tx_context.nettype = cryptonote::FAKECHAIN; + miner_tx_context.nettype = cryptonote::network_type::FAKECHAIN; manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height); size_t current_block_weight = txs_weight + get_transaction_weight(blk.miner_tx); - if (!construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, current_block_weight, miner_fee, blk.miner_tx, cryptonote::beldex_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc.get_keys().m_account_address), cryptonote::blobdata(), m_hf_version)) + if (!construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, current_block_weight, miner_fee, blk.miner_tx, cryptonote::beldex_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner_acc.get_keys().m_account_address), cryptonote::blobdata(), m_hf_version)) return false; } @@ -1507,7 +1505,7 @@ bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const c const cryptonote::account_base& miner_acc, const std::vector& tx_hashes, size_t txs_weight) { - return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, cryptonote::transaction(), tx_hashes, txs_weight, 0); + return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, cryptonote::hf::none, 0, 0, crypto::hash(), 0, cryptonote::transaction(), tx_hashes, txs_weight, 0); } cryptonote::transaction make_registration_tx(std::vector& events, @@ -1517,7 +1515,7 @@ cryptonote::transaction make_registration_tx(std::vector& even const std::vector& contributors, const std::vector& portions, const cryptonote::block& head, - uint8_t hf_version) + cryptonote::hf hf_version) { const auto new_height = cryptonote::get_block_height(head) + 1; const auto staking_requirement = master_nodes::get_staking_requirement(new_height); @@ -1525,12 +1523,12 @@ cryptonote::transaction make_registration_tx(std::vector& even cryptonote::transaction tx; uint64_t unlock_time = 0; - if (hf_version < cryptonote::network_version_11_infinite_staking) - unlock_time = new_height + master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,cryptonote::network_version_17_POS); + if (hf_version < cryptonote::hf::hf11_infinite_staking) + unlock_time = new_height + master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN,cryptonote::hf::hf17_POS); std::vector extra; cryptonote::add_master_node_pubkey_to_tx_extra(extra, master_node_keys.pub); - const uint64_t exp_timestamp = time(nullptr) + STAKING_AUTHORIZATION_EXPIRATION_WINDOW; + const uint64_t exp_timestamp = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW); crypto::hash hash; if (!cryptonote::get_registration_hash(contributors, operator_cut, portions, exp_timestamp, hash)) @@ -1545,7 +1543,7 @@ cryptonote::transaction make_registration_tx(std::vector& even add_master_node_contributor_to_tx_extra(extra, contributors.at(0)); cryptonote::txtype tx_type = cryptonote::txtype::standard; - if (hf_version >= cryptonote::network_version_16) tx_type = cryptonote::txtype::stake; // NOTE: txtype stake was not introduced until HF14 + if (hf_version >= cryptonote::hf::hf16) tx_type = cryptonote::txtype::stake; // NOTE: txtype stake was not introduced until HF14 beldex_tx_builder(events, tx, head, account, account.get_keys().m_account_address, amount, hf_version).with_tx_type(tx_type).with_extra(extra).with_unlock_time(unlock_time).build(); events.push_back(tx); return tx; @@ -1788,7 +1786,7 @@ bool fill_tx_sources(std::vector& sources, const st const output_index& oi = outs[sender_out]; if (oi.spent) continue; - if (!cryptonote::rules::is_output_unlocked(oi.unlock_time, cryptonote::get_block_height(blk_head),cryptonote::FAKECHAIN)) continue; + if (!cryptonote::rules::is_output_unlocked(oi.unlock_time, cryptonote::get_block_height(blk_head),cryptonote::network_type::FAKECHAIN)) continue; cryptonote::tx_source_entry ts; const auto& tx = *oi.p_tx; @@ -2289,7 +2287,7 @@ cryptonote::transaction construct_tx_with_fee(std::vector &eve uint64_t fee) { cryptonote::transaction tx; - beldex_tx_builder(events, tx, blk_head, acc_from, acc_to.get_keys().m_account_address, amount, cryptonote::network_version_7).with_fee(fee).build(); + beldex_tx_builder(events, tx, blk_head, acc_from, acc_to.get_keys().m_account_address, amount, cryptonote::hf::hf7).with_fee(fee).build(); events.push_back(tx); return tx; } @@ -2334,7 +2332,7 @@ uint64_t get_unlocked_balance(const cryptonote::account_base& addr, const std::v return false; for (const size_t out_idx : outs_mine) { - const auto unlocked = cryptonote::rules::is_output_unlocked(outs[out_idx].unlock_time, get_block_height(blockchain.back()),cryptonote::FAKECHAIN); + const auto unlocked = cryptonote::rules::is_output_unlocked(outs[out_idx].unlock_time, get_block_height(blockchain.back()),cryptonote::network_type::FAKECHAIN); if (outs[out_idx].spent || !unlocked) continue; res += outs[out_idx].amount; } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index d3638c1cb8c..3c8c44c019b 100755 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -315,7 +315,7 @@ class test_generator bf_hf_version= 1 << 8 }; - explicit test_generator(int hf_version = 7) : m_hf_version(hf_version) {} + explicit test_generator(cryptonote::hf hf_version = cryptonote::hf::hf7) : m_hf_version(hf_version) {} void get_block_chain(std::vector& blockchain, const crypto::hash& head, size_t n) const; void get_block_chain(std::vector& blockchain, const crypto::hash& head, size_t n) const; void get_last_n_block_weights(std::vector& block_weights, const crypto::hash& head, size_t n) const; @@ -331,7 +331,7 @@ class test_generator const std::list& tx_list = std::list(), const master_nodes::payout &block_leader = {}); bool construct_block_manually(cryptonote::block& blk, const cryptonote::block& prev_block, - const cryptonote::account_base& miner_acc, int actual_params = bf_none, uint8_t major_ver = 0, + const cryptonote::account_base& miner_acc, int actual_params = bf_none, cryptonote::hf major_ver = cryptonote::hf::none, uint8_t minor_ver = 0, uint64_t timestamp = 0, const crypto::hash& prev_id = crypto::hash(), const cryptonote::difficulty_type& diffic = 1, const cryptonote::transaction& miner_tx = cryptonote::transaction(), const std::vector& tx_hashes = std::vector(), size_t txs_sizes = 0, size_t txn_fee = 0); @@ -339,7 +339,7 @@ class test_generator const cryptonote::account_base& miner_acc, const std::vector& tx_hashes, size_t txs_size); - int m_hf_version; + cryptonote::hf m_hf_version = cryptonote::hf::none; std::unordered_map m_blocks_info; private: @@ -907,7 +907,7 @@ inline bool replay_events_through_core_plain(cryptonote::core& cr, const std::ve //-------------------------------------------------------------------------- template struct get_test_options { - const std::vector hard_forks = {{7, 0, 0, 0}}; + const std::vector hard_forks = {{cryptonote::hf::hf7, 0, 0, 0}}; const cryptonote::test_options test_options = { hard_forks, 0 }; @@ -1082,12 +1082,12 @@ inline bool do_replay_file(const std::string& filename) BLK_NAME = blk_last; \ } -#define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) +#define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, cryptonote::MINED_MONEY_UNLOCK_WINDOW) // NOTE(beldex): These macros assume hardfork version 7 and are from the old Monero testing code #define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ cryptonote::transaction TX_NAME; \ - beldex_tx_builder(VEC_EVENTS, TX_NAME, HEAD, FROM, TO.get_keys().m_account_address, AMOUNT, cryptonote::network_version_7).build(); \ + beldex_tx_builder(VEC_EVENTS, TX_NAME, HEAD, FROM, TO.get_keys().m_account_address, AMOUNT, cryptonote::hf::hf7).build(); \ VEC_EVENTS.push_back(TX_NAME); #define MAKE_TX_MIX_RCT(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ @@ -1100,7 +1100,7 @@ inline bool do_replay_file(const std::string& filename) #define MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ { \ cryptonote::transaction t; \ - beldex_tx_builder(VEC_EVENTS, t, HEAD, FROM, TO.get_keys().m_account_address, AMOUNT, cryptonote::network_version_7).build(); \ + beldex_tx_builder(VEC_EVENTS, t, HEAD, FROM, TO.get_keys().m_account_address, AMOUNT, cryptonote::hf::hf7).build(); \ SET_NAME.push_back(t); \ VEC_EVENTS.push_back(t); \ } @@ -1142,7 +1142,7 @@ inline bool do_replay_file(const std::string& filename) TX, \ cryptonote::beldex_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_account.get_keys().m_account_address), \ {}, \ - 7)) \ + cryptonote::hf::hf7)) \ return false; #define MAKE_TX_LIST_START_RCT(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ @@ -1235,7 +1235,7 @@ inline bool do_replay_file(const std::string& filename) #define CHECK_TEST_CONDITION_MSG(cond, msg) CHECK_AND_ASSERT_MES(cond, false, "[" << perr_context << "] failed: \"" << QUOTEME(cond) << "\", msg: " << msg) #define CHECK_EQ(v1, v2) CHECK_AND_ASSERT_MES(v1 == v2, false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " == " << QUOTEME(v2) << "\", " << v1 << " != " << v2) #define CHECK_NOT_EQ(v1, v2) CHECK_AND_ASSERT_MES(!(v1 == v2), false, "[" << perr_context << "] failed: \"" << QUOTEME(v1) << " != " << QUOTEME(v2) << "\", " << v1 << " == " << v2) -#define MK_COINS(amount) (UINT64_C(amount) * COIN) +#define MK_COINS(amount) (UINT64_C(amount) * beldex::COIN) inline std::string make_junk() { std::string junk; @@ -1276,7 +1276,7 @@ class beldex_tx_builder { const cryptonote::account_base& from, const cryptonote::account_public_address& to, uint64_t amount, - uint8_t hf_version) + cryptonote::hf hf_version) : m_events(events) , m_tx(tx) , m_head(head) @@ -1367,7 +1367,7 @@ class beldex_tx_builder { void fill_nonce_with_beldex_generator (struct beldex_chain_generator const *generator, cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); void beldex_register_callback (std::vector &events, std::string const &callback_name, beldex_callback callback); -std::vector beldex_generate_hard_fork_table(uint8_t max_hf_version = cryptonote::network_version_count - 1, uint64_t pos_delay = 60); +std::vector beldex_generate_hard_fork_table(cryptonote::hf max_hf_version = cryptonote::hf::hf20_bulletproof_plusplus, uint64_t pos_delay = 60); struct beldex_blockchain_entry { @@ -1411,7 +1411,7 @@ enum struct beldex_create_block_type struct beldex_create_block_params { beldex_create_block_type type; - uint8_t hf_version; + cryptonote::hf hf_version; beldex_blockchain_entry prev; cryptonote::account_base miner_acc; uint64_t timestamp; @@ -1432,7 +1432,7 @@ struct beldex_chain_generator uint64_t last_cull_height_ = 0; std::shared_ptr bns_db_ = std::make_shared(); beldex_chain_generator_db db_; - uint8_t hf_version_ = cryptonote::network_version_7; + cryptonote::hf hf_version_ = cryptonote::hf::hf7; std::vector& events_; const std::vector hard_forks_; cryptonote::account_base first_miner_; @@ -1441,41 +1441,41 @@ struct beldex_chain_generator uint64_t height() const { return cryptonote::get_block_height(db_.blocks.back().block); } uint64_t chain_height() const { return height() + 1; } - const std::vector& blocks() const { return db_.blocks; } + const std::vector& blocks() const { return db_.blocks; } size_t event_index() const { return events_.size() - 1; } - uint8_t hardfork() const { return get_hf_version_at(height()); } + cryptonote::hf hardfork() const { return get_hf_version_at(height()); } - const beldex_blockchain_entry& top() const { return db_.blocks.back(); } + const beldex_blockchain_entry& top() const { return db_.blocks.back(); } master_nodes::quorum_manager top_quorum() const; master_nodes::quorum_manager quorum(uint64_t height) const; std::shared_ptr get_quorum(master_nodes::quorum_type type, uint64_t height) const; - master_nodes::master_node_keys get_cached_keys(const crypto::public_key &pubkey) const; + master_nodes::master_node_keys get_cached_keys(const crypto::public_key &pubkey) const; cryptonote::account_base add_account(); - beldex_blockchain_entry &add_block(beldex_blockchain_entry const &entry, bool can_be_added_to_blockchain = true, std::string const &fail_msg = {}); - void add_blocks_until_version(uint8_t hf_version); + beldex_blockchain_entry &add_block(beldex_blockchain_entry const &entry, bool can_be_added_to_blockchain = true, std::string const &fail_msg = {}); + void add_blocks_until_version(cryptonote::hf hf_version); void add_n_blocks(int n); bool add_blocks_until_next_checkpointable_height(); void add_master_node_checkpoint(uint64_t block_height, size_t num_votes); - void add_mined_money_unlock_blocks(); // NOTE: Unlock all Loki generated from mining prior to this call i.e. CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - void add_transfer_unlock_blocks(uint8_t hf_version); // Unlock funds from (standard) transfers prior to this call, i.e. CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE + void add_mined_money_unlock_blocks(); // NOTE: Unlock all Loki generated from mining prior to this call i.e. cryptonote::MINED_MONEY_UNLOCK_WINDOW + void add_transfer_unlock_blocks(cryptonote::hf hf_version); // Unlock funds from (standard) transfers prior to this call, i.e. DEFAULT_TX_SPENDABLE_AGE // NOTE: Add an event that is just a user specified message to signify progress in the test void add_event_msg(std::string const &msg) { events_.push_back(msg); } void add_tx(cryptonote::transaction const &tx, bool can_be_added_to_blockchain = true, std::string const &fail_msg = {}, bool kept_by_block = false); - beldex_create_block_params next_block_params() const; + beldex_create_block_params next_block_params() const; // NOTE: Add constructed TX to events_ and assume that it is valid to add to the blockchain. If the TX is meant to be unaddable to the blockchain use the individual create + add functions to // be able to mark the add TX event as something that should trigger a failure. - cryptonote::transaction create_and_add_beldex_name_system_tx(cryptonote::account_base const &src, uint8_t hf_version, bns::mapping_years mapping_years, std::string const &name, bns::mapping_value const &value_bchat, bns::mapping_value const &value_wallet, bns::mapping_value const &value_belnet, bns::mapping_value const &value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, bool kept_by_block = false); - cryptonote::transaction create_and_add_beldex_name_system_tx_update(cryptonote::account_base const &src, uint8_t hf_version, bns::mapping_type type, std::string const &name, bns::mapping_value const *value_bchat, bns::mapping_value const *value_wallet, bns::mapping_value const *value_belnet, bns::mapping_value const *value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, bns::generic_signature *signature = nullptr, bool kept_by_block = false); - cryptonote::transaction create_and_add_beldex_name_system_tx_renew(cryptonote::account_base const &src, uint8_t hf_version, bns::mapping_years mapping_years, std::string const &name, bns::generic_signature *signature = nullptr, bool kept_by_block = false); + cryptonote::transaction create_and_add_beldex_name_system_tx(cryptonote::account_base const &src, cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::mapping_value const &value_bchat, bns::mapping_value const &value_wallet, bns::mapping_value const &value_belnet, bns::mapping_value const &value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, bool kept_by_block = false); + cryptonote::transaction create_and_add_beldex_name_system_tx_update(cryptonote::account_base const &src, cryptonote::hf hf_version, bns::mapping_type type, std::string const &name, bns::mapping_value const *value_bchat, bns::mapping_value const *value_wallet, bns::mapping_value const *value_belnet, bns::mapping_value const *value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, bns::generic_signature *signature = nullptr, bool kept_by_block = false); + cryptonote::transaction create_and_add_beldex_name_system_tx_renew(cryptonote::account_base const &src, cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::generic_signature *signature = nullptr, bool kept_by_block = false); cryptonote::transaction create_and_add_tx (const cryptonote::account_base& src, const cryptonote::account_public_address& dest, uint64_t amount, uint64_t fee = TESTS_DEFAULT_FEE, bool kept_by_block = false); cryptonote::transaction create_and_add_state_change_tx(master_nodes::new_state state, const crypto::public_key& pub_key, uint16_t reasons_all, uint16_t reasons_any, uint64_t height = -1, const std::vector& voters = {}, uint64_t fee = 0, bool kept_by_block = false); cryptonote::transaction create_and_add_registration_tx(const cryptonote::account_base& src, const cryptonote::keypair& sn_keys = cryptonote::keypair{hw::get_device("default")}, bool kept_by_block = false); cryptonote::transaction create_and_add_staking_tx (const crypto::public_key &pub_key, const cryptonote::account_base &src, uint64_t amount, bool kept_by_block = false); - beldex_blockchain_entry &create_and_add_next_block (const std::vector& txs = {}, cryptonote::checkpoint_t const *checkpoint = nullptr, bool can_be_added_to_blockchain = true, std::string const &fail_msg = {}); + beldex_blockchain_entry &create_and_add_next_block (const std::vector& txs = {}, cryptonote::checkpoint_t const *checkpoint = nullptr, bool can_be_added_to_blockchain = true, std::string const &fail_msg = {}); // Same as create_and_add_tx, but also adds 95kB of junk into tx_extra to bloat up the tx size. cryptonote::transaction create_and_add_big_tx(const cryptonote::account_base& src, const cryptonote::account_public_address& dest, uint64_t amount, uint64_t junk_size = 95000, uint64_t fee = TESTS_DEFAULT_FEE, bool kept_by_block = false); @@ -1483,7 +1483,7 @@ struct beldex_chain_generator cryptonote::transaction create_tx(const cryptonote::account_base &src, const cryptonote::account_public_address &dest, uint64_t amount, uint64_t fee) const; cryptonote::transaction create_registration_tx(const cryptonote::account_base &src, const cryptonote::keypair &master_node_keys = cryptonote::keypair{hw::get_device("default")}, - uint64_t src_portions = STAKING_PORTIONS, + uint64_t src_portions = cryptonote::old::STAKING_PORTIONS, uint64_t src_operator_cut = 0, std::array const &contributors = {}, int num_contributors = 0) const; @@ -1493,20 +1493,20 @@ struct beldex_chain_generator // value: Takes the binary value NOT the human readable version, of the name->value mapping static const uint64_t ONS_AUTO_BURN = static_cast(-1); - cryptonote::transaction create_beldex_name_system_tx(cryptonote::account_base const &src, uint8_t hf_version, bns::mapping_years mapping_years, std::string const &name, bns::mapping_value const &value_bchat, bns::mapping_value const &value_wallet, bns::mapping_value const &value_belnet, bns::mapping_value const &value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, std::optional burn_override = std::nullopt) const; - cryptonote::transaction create_beldex_name_system_tx_update(cryptonote::account_base const &src, uint8_t hf_version, bns::mapping_type type, std::string const &name, bns::mapping_value const *value_bchat, bns::mapping_value const *value_wallet, bns::mapping_value const *value_belnet, bns::mapping_value const *value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, bns::generic_signature *signature = nullptr, bool use_asserts = false) const; - cryptonote::transaction create_beldex_name_system_tx_update_w_extra(cryptonote::account_base const &src, uint8_t hf_version, cryptonote::tx_extra_beldex_name_system const &ons_extra) const; - cryptonote::transaction create_beldex_name_system_tx_renew(cryptonote::account_base const &src, uint8_t hf_version, bns::mapping_years mapping_years, std::string const &name, bns::generic_signature *signature = nullptr, std::optional burn_override = std::nullopt) const; + cryptonote::transaction create_beldex_name_system_tx(cryptonote::account_base const &src, cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::mapping_value const &value_bchat, bns::mapping_value const &value_wallet, bns::mapping_value const &value_belnet, bns::mapping_value const &value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, std::optional burn_override = std::nullopt) const; + cryptonote::transaction create_beldex_name_system_tx_update(cryptonote::account_base const &src, cryptonote::hf hf_version, bns::mapping_type type, std::string const &name, bns::mapping_value const *value_bchat, bns::mapping_value const *value_wallet, bns::mapping_value const *value_belnet, bns::mapping_value const *value_eth_addr, bns::generic_owner const *owner = nullptr, bns::generic_owner const *backup_owner = nullptr, bns::generic_signature *signature = nullptr, bool use_asserts = false) const; + cryptonote::transaction create_beldex_name_system_tx_update_w_extra(cryptonote::account_base const &src, cryptonote::hf hf_version, cryptonote::tx_extra_beldex_name_system const &ons_extra) const; + cryptonote::transaction create_beldex_name_system_tx_renew(cryptonote::account_base const &src, cryptonote::hf hf_version, bns::mapping_years mapping_years, std::string const &name, bns::generic_signature *signature = nullptr, std::optional burn_override = std::nullopt) const; - beldex_blockchain_entry create_genesis_block(const cryptonote::account_base &miner, uint64_t timestamp); - beldex_blockchain_entry create_next_block(const std::vector& txs = {}, cryptonote::checkpoint_t const *checkpoint = nullptr); + beldex_blockchain_entry create_genesis_block(const cryptonote::account_base &miner, uint64_t timestamp); + beldex_blockchain_entry create_next_block(const std::vector& txs = {}, cryptonote::checkpoint_t const *checkpoint = nullptr); bool create_block(beldex_blockchain_entry &entry, beldex_create_block_params ¶ms, const std::vector &tx_list) const; bool block_begin(beldex_blockchain_entry &entry, beldex_create_block_params ¶ms, const std::vector &tx_list) const; void block_fill_POS_data(beldex_blockchain_entry &entry, beldex_create_block_params const ¶ms, uint8_t round) const; void block_end(beldex_blockchain_entry &entry, beldex_create_block_params const ¶ms) const; - uint8_t get_hf_version_at(uint64_t height) const; + cryptonote::hf get_hf_version_at(uint64_t height) const; std::vector last_n_block_weights(uint64_t height, uint64_t num) const; const cryptonote::account_base& first_miner() const { return first_miner_; } }; diff --git a/tests/core_tests/chaingen001.cpp b/tests/core_tests/chaingen001.cpp index 1ea1e509c7e..7d505caf78f 100755 --- a/tests/core_tests/chaingen001.cpp +++ b/tests/core_tests/chaingen001.cpp @@ -108,7 +108,7 @@ static void make_rct_tx(eventV& events, { txs.emplace_back(); - bool success = beldex_tx_builder(events, txs.back(), blk_head, from, to.get_keys().m_account_address, amount, cryptonote::network_version_7).build(); + bool success = beldex_tx_builder(events, txs.back(), blk_head, from, to.get_keys().m_account_address, amount, cryptonote::hf::hf7).build(); /// TODO: beter error message if (!success) throw std::exception(); events.push_back(txs.back()); @@ -117,7 +117,7 @@ static void make_rct_tx(eventV& events, /// generate 30 more blocks to unlock outputs static void rewind_blocks(test_generator& gen, eventV& events, std::vector& chain, const cryptonote::account_base& miner) { - for (auto i = 0u; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) { + for (auto i = 0u; i < cryptonote::MINED_MONEY_UNLOCK_WINDOW; ++i) { chain.emplace_back(); const auto idx = chain.size() - 1; gen.construct_block(chain[idx], chain[idx - 1], miner); diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index c4719571219..5550a58078c 100755 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -233,7 +233,7 @@ int main(int argc, char* argv[]) // TODO(beldex): Tests we need to fix #if 0 - //GENERATE_AND_PLAY(gen_ring_signature_big); // Takes up to XXX hours (if CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW == 10) + //GENERATE_AND_PLAY(gen_ring_signature_big); // Takes up to XXX hours (if cryptonote::MINED_MONEY_UNLOCK_WINDOW == 10) // Transaction verification tests GENERATE_AND_PLAY(gen_tx_mixed_key_offset_not_exist); // TODO(beldex): See comment in the function diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index 44ded7edf1d..6d171b81c7c 100755 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -97,7 +97,7 @@ bool gen_double_spend_base::check_double_spend(cryptonote::core& CHECK_NOT_EQ(invalid_index_value, m_invalid_block_index); std::vector blocks; - bool r = c.get_blocks(0, 100 + 2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + bool r = c.get_blocks(0, 100 + 2 * cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); CHECK_TEST_CONDITION(m_last_valid_block == blocks.back()); diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index c95bcd0c4e8..75eacb19332 100755 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -112,8 +112,8 @@ bool gen_uint_overflow_1::generate(std::vector& events) const { beldex_blockchain_entry entry = gen.create_next_block(); cryptonote::transaction &miner_tx = entry.block.miner_tx; - split_miner_tx_outs(miner_tx, MONEY_SUPPLY); - gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We purposely overflow miner tx by MONEY_SUPPLY in the miner tx"); + split_miner_tx_outs(miner_tx, beldex::MONEY_SUPPLY); + gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We purposely overflow miner tx by beldex::MONEY_SUPPLY in the miner tx"); } // Problem 2. block_reward overflow @@ -173,10 +173,10 @@ bool gen_uint_overflow_2::generate(std::vector& events) const std::vector destinations; const account_public_address& bob_addr = bob_account.get_keys().m_account_address; - destinations.push_back(tx_destination_entry(MONEY_SUPPLY, bob_addr, false)); - destinations.push_back(tx_destination_entry(MONEY_SUPPLY - 1, bob_addr, false)); + destinations.push_back(tx_destination_entry(beldex::MONEY_SUPPLY, bob_addr, false)); + destinations.push_back(tx_destination_entry(beldex::MONEY_SUPPLY - 1, bob_addr, false)); // sources.front().amount = destinations[0].amount + destinations[2].amount + destinations[3].amount + TESTS_DEFAULT_FEE - destinations.push_back(tx_destination_entry(sources.front().amount - MONEY_SUPPLY - MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr, false)); + destinations.push_back(tx_destination_entry(sources.front().amount - beldex::MONEY_SUPPLY - beldex::MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr, false)); cryptonote::transaction tx_1; if (!construct_tx(miner_account.get_keys(), sources, destinations, std::nullopt, std::vector(), tx_1, 0)) @@ -191,7 +191,7 @@ bool gen_uint_overflow_2::generate(std::vector& events) const for (size_t i = 0; i < tx_1.vout.size(); ++i) { auto& tx_1_out = tx_1.vout[i]; - if (tx_1_out.amount < MONEY_SUPPLY - 1) + if (tx_1_out.amount < beldex::MONEY_SUPPLY - 1) continue; append_tx_source_entry(sources, tx_1, i); @@ -200,7 +200,7 @@ bool gen_uint_overflow_2::generate(std::vector& events) const destinations.clear(); cryptonote::tx_destination_entry de; de.addr = alice_account.get_keys().m_account_address; - de.amount = MONEY_SUPPLY - TESTS_DEFAULT_FEE; + de.amount = beldex::MONEY_SUPPLY - TESTS_DEFAULT_FEE; destinations.push_back(de); destinations.push_back(de); diff --git a/tests/core_tests/master_nodes.cpp b/tests/core_tests/master_nodes.cpp index 403e745394f..69e956c8327 100755 --- a/tests/core_tests/master_nodes.cpp +++ b/tests/core_tests/master_nodes.cpp @@ -129,7 +129,7 @@ bool gen_master_nodes::generate(std::vector &events) const const auto miner = gen.first_miner(); const auto alice = gen.create_account(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); gen.rewind_blocks_n(10); gen.rewind_blocks(); @@ -145,7 +145,7 @@ bool gen_master_nodes::generate(std::vector &events) const DO_CALLBACK(events, "check_registered"); - for (auto i = 0u; i < master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,network_version_9_master_nodes); ++i) { + for (auto i = 0u; i < master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN, hf::hf9_master_nodes); ++i) { gen.create_block(); } @@ -161,7 +161,7 @@ bool gen_master_nodes::check_registered(cryptonote::core& c, size_t ev_index, co cryptonote::account_base alice = boost::get(events[1]); std::vector blocks; - size_t count = 15 + (2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + size_t count = 15 + (2 * cryptonote::MINED_MONEY_UNLOCK_WINDOW); bool r = c.get_blocks((uint64_t)0, count, blocks); CHECK_TEST_CONDITION(r); std::vector chain; @@ -189,10 +189,10 @@ bool gen_master_nodes::check_expired(cryptonote::core& c, size_t ev_index, const cryptonote::account_base alice = boost::get(events[1]); - const auto stake_lock_time = master_nodes::staking_num_lock_blocks(cryptonote::FAKECHAIN,0); + const auto stake_lock_time = master_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN,0); std::vector blocks; - size_t count = 15 + (2 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time; + size_t count = 15 + (2 * cryptonote::MINED_MONEY_UNLOCK_WINDOW) + stake_lock_time; bool r = c.get_blocks((uint64_t)0, count, blocks); CHECK_TEST_CONDITION(r); std::vector chain; @@ -227,7 +227,7 @@ bool test_prefer_deregisters::generate(std::vector &events) const auto miner = gen.first_miner(); const auto alice = gen.create_account(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); /// give miner some outputs to spend and unlock them gen.rewind_blocks_n(60); @@ -311,7 +311,7 @@ bool test_zero_fee_deregister::generate(std::vector &events) gen.create_genesis_block(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); /// give miner some outputs to spend and unlock them gen.rewind_blocks_n(20); @@ -354,7 +354,7 @@ bool test_deregister_safety_buffer::generate(std::vector &even const auto miner = gen.first_miner(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); /// give miner some outputs to spend and unlock them gen.rewind_blocks_n(40); @@ -462,7 +462,7 @@ bool test_deregisters_on_split::generate(std::vector &events) linear_chain_generator gen(events, test_options.hard_forks); gen.create_genesis_block(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); /// generate some outputs and unlock them gen.rewind_blocks_n(20); @@ -565,7 +565,7 @@ bool deregister_too_old::generate(std::vector& events) linear_chain_generator gen(events, test_options.hard_forks); gen.create_genesis_block(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); /// generate some outputs and unlock them gen.rewind_blocks_n(20); @@ -610,7 +610,7 @@ bool mn_test_rollback::generate(std::vector& events) linear_chain_generator gen(events, test_options.hard_forks); gen.create_genesis_block(); - gen.rewind_until_version(network_version_9_master_nodes); + gen.rewind_until_version(hf::hf9_master_nodes); /// generate some outputs and unlock them gen.rewind_blocks_n(20); @@ -755,12 +755,12 @@ bool test_swarms_basic::generate(std::vector& events) /// create a few blocks with active master nodes gen.rewind_blocks_n(5); - if (gen.get_hf_version() != network_version_9_master_nodes) { + if (gen.get_hf_version() != hf::hf9_master_nodes) { std::cerr << "wrong hf version\n"; return false; } - gen.rewind_until_version(network_version_10_bulletproofs); + gen.rewind_until_version(hf::hf10_bulletproofs); /// test that we now have swarms DO_CALLBACK(events, "test_initial_swarms"); diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index ce7123c0766..7b724a84cfc 100755 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -164,7 +164,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vectortimestamp + tools::to_seconds((block_major>=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)) * 2, // v2 has blocks twice as long + block_major, block_minor, prev_block->timestamp + tools::to_seconds((block_major >= cryptonote::hf::hf17_POS ? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector(), 0), false, "Failed to generate block"); events.push_back(blocks[n]); @@ -190,12 +190,12 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector=cryptonote::network_version_17_POS?TARGET_BLOCK_TIME:TARGET_BLOCK_TIME_OLD)) * 2, // v2 has blocks twice as long + block_major, block_minor, blk_last.timestamp + tools::to_seconds((block_major >= cryptonote::hf::hf17_POS ? cryptonote::TARGET_BLOCK_TIME : cryptonote::old::TARGET_BLOCK_TIME_12)) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector(), 0), false, "Failed to generate block"); events.push_back(blk); @@ -368,7 +368,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector additional_tx_secret_keys; auto sources_copy = sources; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::network_version_8; + tx_params.hf_version = cryptonote::hf::hf8; r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, std::nullopt, std::vector(), tx, 0, tx_key, additional_tx_secret_keys, { rct::RangeProofType::PaddedBulletproof, 3 }, msoutp, tx_params); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); @@ -483,167 +483,167 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 4, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); + return generate_with(events, 4, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 2, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_22_2_1::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 2, 2, {1}, NULL, NULL); } bool gen_multisig_tx_valid_33_1_23::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL); } bool gen_multisig_tx_valid_33_3_21::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL); } bool gen_multisig_tx_valid_23_1_2::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 3, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_23_1_3::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 3, 1, {3}, NULL, NULL); } bool gen_multisig_tx_valid_23_2_1::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 3, 2, {1}, NULL, NULL); } bool gen_multisig_tx_valid_23_2_3::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 3, 2, {3}, NULL, NULL); } bool gen_multisig_tx_valid_45_1_234::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL); } bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 4, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL); + return generate_with(events, 4, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL); } bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL); } bool gen_multisig_tx_valid_24_1_2::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 4, 1, {2}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 4, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_24_1_2_many_inputs::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 4, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 4, 1, {2}, NULL, NULL); + return generate_with(events, 4, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 4, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_25_1_2::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 5, 1, {2}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 5, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_25_1_2_many_inputs::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 4, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 2, 5, 1, {2}, NULL, NULL); + return generate_with(events, 4, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 2, 5, 1, {2}, NULL, NULL); } bool gen_multisig_tx_valid_48_1_234::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL); } bool gen_multisig_tx_valid_48_1_234_many_inputs::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 4, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL); + return generate_with(events, 4, cryptonote::TX_OUTPUT_DECOYS, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL); } bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 2, 2, 1, {}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 2, 2, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 3, 3, 1, {}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 3, 3, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 3, 3, 1, {2}, NULL, NULL); } bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 3, 3, 1, {3}, NULL, NULL); } bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 2, 3, 1, {}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 2, 3, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL); } bool gen_multisig_tx_invalid_24_1_no_signers::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 2, 4, 1, {}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 2, 4, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_25_1_no_signers::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 2, 5, 1, {}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 2, 5, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_48_1_no_signers::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 4, 8, 1, {}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 4, 8, 1, {}, NULL, NULL); } bool gen_multisig_tx_invalid_48_1_23_no_threshold::generate(std::vector& events) const { const uint64_t amount_paid = 10000; - return generate_with(events, 2, CRYPTONOTE_DEFAULT_TX_MIXIN, amount_paid, false, 4, 8, 1, {2, 3}, NULL, NULL); + return generate_with(events, 2, cryptonote::TX_OUTPUT_DECOYS, amount_paid, false, 4, 8, 1, {2, 3}, NULL, NULL); } diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h index c80f9cad511..8c10a6520eb 100755 --- a/tests/core_tests/multisig.h +++ b/tests/core_tests/multisig.h @@ -82,7 +82,7 @@ struct gen_multisig_tx_validation_base : public test_chain_unit_base template<> struct get_test_options { - const std::vector hard_forks = {{7,0,0,0}}; + const std::vector hard_forks = {{cryptonote::hf::hf7,0,0,0}}; const cryptonote::test_options test_options = { hard_forks, 0 }; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 03b048fa8d1..0b9ced119bb 100755 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -42,7 +42,7 @@ using namespace cryptonote; // Tests bool gen_rct_tx_validation_base::generate_with_full(std::vector& events, - const int *out_idx, int mixin, uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool valid, + const int *out_idx, int mixin, uint64_t amount_paid, size_t second_rewind, cryptonote::hf last_version, const rct::RCTConfig &rct_config, bool valid, const std::function &sources, std::vector &destinations)> &pre_tx, const std::function &post_tx) const { @@ -59,7 +59,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vectortimestamp + tools::to_seconds(TARGET_BLOCK_TIME_OLD) * 2, // v2 has blocks twice as long + hf::hf1, 2, prev_block->timestamp + tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector(), 0), false, "Failed to generate block"); events.push_back(blocks[n]); @@ -70,12 +70,12 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector(), 0), false, "Failed to generate block"); events.push_back(blk); @@ -122,7 +122,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector subaddresses; subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::network_version_8; + tx_params.hf_version = cryptonote::hf::hf8; bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::tx_destination_entry{}, std::vector(), rct_txes[n], 0, tx_key, additional_tx_keys, {}, nullptr, tx_params); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); events.push_back(rct_txes[n]); @@ -144,7 +144,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector(last_version), blk_last.timestamp + tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector(), 0), false, "Failed to generate block"); events.push_back(blk); @@ -228,7 +228,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector subaddresses; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::network_version_8; + tx_params.hf_version = cryptonote::hf::hf8; bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::tx_destination_entry{}, std::vector(), tx, 0, tx_key, additional_tx_keys, rct_config, nullptr, tx_params); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); @@ -249,7 +249,7 @@ bool gen_rct_tx_validation_base::generate_with(std::vector& ev const std::function &post_tx) const { const rct::RCTConfig rct_config { rct::RangeProofType::Borromean, 0 }; - return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, 4, rct_config, valid, pre_tx, post_tx); + return generate_with_full(events, out_idx, mixin, amount_paid, cryptonote::old::DEFAULT_TX_SPENDABLE_AGE, hf::hf14_enforce_checkpoints, rct_config, valid, pre_tx, post_tx); } bool gen_rct_tx_valid_from_pre_rct::generate(std::vector& events) const @@ -528,5 +528,5 @@ bool gen_rct_tx_uses_output_too_early::generate(std::vector& e const int out_idx[] = {1, -1}; const uint64_t amount_paid = 10000; const rct::RCTConfig rct_config { rct::RangeProofType::PaddedBulletproof, 2 }; - return generate_with_full(events, out_idx, mixin, amount_paid, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE-1, HF_VERSION_ENFORCE_MIN_AGE, rct_config, false, NULL, NULL); + return generate_with_full(events, out_idx, mixin, amount_paid, cryptonote::old::DEFAULT_TX_SPENDABLE_AGE-1, cryptonote::feature::ENFORCE_MIN_AGE, rct_config, false, NULL, NULL); } diff --git a/tests/core_tests/rct.h b/tests/core_tests/rct.h index d1b08856173..77152bfd59b 100755 --- a/tests/core_tests/rct.h +++ b/tests/core_tests/rct.h @@ -70,7 +70,7 @@ struct gen_rct_tx_validation_base : public test_chain_unit_base } bool generate_with_full(std::vector& events, const int *out_idx, int mixin, - uint64_t amount_paid, size_t second_rewind, uint8_t last_version, const rct::RCTConfig &rct_config, bool valid, + uint64_t amount_paid, size_t second_rewind, cryptonote::hf last_version, const rct::RCTConfig &rct_config, bool valid, const std::function &sources, std::vector &destinations)> &pre_tx, const std::function &post_tx) const; bool generate_with(std::vector& events, const int *out_idx, int mixin, @@ -85,7 +85,7 @@ struct gen_rct_tx_validation_base : public test_chain_unit_base template<> struct get_test_options { - const std::vector hard_forks = {{1,0,0,0}, {2,0,1,0}, {4,0,65,0}}; + const std::vector hard_forks = {{cryptonote::hf::hf7,0,0,0}, {cryptonote::hf::hf8,0,1,0}, {cryptonote::hf::hf9_master_nodes,0,65,0}}; const cryptonote::test_options test_options = { hard_forks, 0 }; @@ -271,7 +271,7 @@ struct gen_rct_tx_uses_output_too_early : public gen_rct_tx_validation_base bool generate(std::vector& events) const; }; template<> struct get_test_options { - const std::vector hard_forks = {{1,0,0,0}, {2,0,1,0}, {4,0,65,0}, {12,0,69,0}, {0,0,0,0}}; + const std::vector hard_forks = {{cryptonote::hf::hf7,0,0,0}, {cryptonote::hf::hf8,0,1,0}, {cryptonote::hf::hf9_master_nodes,0,65,0}, {cryptonote::hf::hf12_security_signature,0,69,0}, {cryptonote::hf::none,0,0,0}}; const cryptonote::test_options test_options = { hard_forks, 0 }; diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp index 5fadc6096f9..bd0c6acf376 100755 --- a/tests/core_tests/ring_signature_1.cpp +++ b/tests/core_tests/ring_signature_1.cpp @@ -187,7 +187,7 @@ bool gen_ring_signature_2::check_balances_1(cryptonote::core& c, size_t ev_index m_alice_account = var::get(events[2]); std::vector blocks; - bool r = c.get_blocks(0, 100 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + bool r = c.get_blocks(0, 100 + 3 * cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); std::vector chain; @@ -205,7 +205,7 @@ bool gen_ring_signature_2::check_balances_2(cryptonote::core& c, size_t ev_index DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_2::check_balances_2"); std::vector blocks; - bool r = c.get_blocks(0, 100 + 3 * CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + bool r = c.get_blocks(0, 100 + 3 * cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); std::vector chain; @@ -241,7 +241,7 @@ bool gen_ring_signature_big::generate(std::vector& events) con { std::vector accounts(m_test_size); std::vector blocks; - blocks.reserve(m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW); + blocks.reserve(m_test_size + cryptonote::MINED_MONEY_UNLOCK_WINDOW); uint64_t ts_start = 1338224400; GENERATE_ACCOUNT(miner_account); @@ -265,7 +265,7 @@ bool gen_ring_signature_big::generate(std::vector& events) con for (size_t i = 0; i < m_test_size; ++i) { - block blk_with_unlocked_out = blocks[blocks.size() - 1 - CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW]; + block blk_with_unlocked_out = blocks[blocks.size() - 1 - cryptonote::MINED_MONEY_UNLOCK_WINDOW]; MAKE_TX_LIST_START(events, txs_blk_i, miner_account, accounts[i], m_tx_amount, blk_with_unlocked_out); for (size_t j = 0; j <= i; ++j) { @@ -297,7 +297,7 @@ bool gen_ring_signature_big::check_balances_1(cryptonote::core& c, size_t ev_ind m_alice_account = var::get(events[1 + m_test_size]); std::vector blocks; - bool r = c.get_blocks(0, 2 * m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + bool r = c.get_blocks(0, 2 * m_test_size + cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); std::vector chain; @@ -322,7 +322,7 @@ bool gen_ring_signature_big::check_balances_2(cryptonote::core& c, size_t ev_ind DEFINE_TESTS_ERROR_CONTEXT("gen_ring_signature_big::check_balances_2"); std::vector blocks; - bool r = c.get_blocks(0, 2 * m_test_size + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, blocks); + bool r = c.get_blocks(0, 2 * m_test_size + cryptonote::MINED_MONEY_UNLOCK_WINDOW, blocks); CHECK_TEST_CONDITION(r); std::vector chain; diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index ed376a905b8..7cc0b0d8c75 100755 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -194,43 +194,43 @@ bool gen_tx_unlock_time::generate(std::vector& events) const transaction tx = {}; uint64_t unlock_time = 0; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); tx = {}; unlock_time = get_block_height(blk_money_unlocked) - 1; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); tx = {}; unlock_time = get_block_height(blk_money_unlocked); - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); tx = {}; unlock_time = get_block_height(blk_money_unlocked) + 1; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); tx = {}; unlock_time = get_block_height(blk_money_unlocked) + 2; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); tx = {}; unlock_time = ts_start - 1; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); tx = {}; unlock_time = time(0) + 60 * 60; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_unlock_time(unlock_time).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_unlock_time(unlock_time).build(); events.push_back(tx); txs_0.push_back(tx); @@ -301,7 +301,7 @@ bool gen_tx_no_inputs_has_outputs::generate(std::vector& event REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); tx.vin.clear(); DO_CALLBACK(events, "mark_invalid_tx"); @@ -319,7 +319,7 @@ bool gen_tx_has_inputs_no_outputs::generate(std::vector& event REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); tx.vout.clear(); DO_CALLBACK(events, "mark_invalid_tx"); // NOTE(beldex): This used to be valid in Monero pre RCT, but not anymore with our transactions because we start with RCT type TXs @@ -339,7 +339,7 @@ bool gen_tx_invalid_input_amount::generate(std::vector& events std::vector sources; std::vector destinations; uint64_t change_amount; - fill_tx_sources_and_destinations(events, blk_money_unlocked, miner_account, get_address(miner_account), MK_COINS(1), TESTS_DEFAULT_FEE, CRYPTONOTE_DEFAULT_TX_MIXIN, sources, destinations, &change_amount); + fill_tx_sources_and_destinations(events, blk_money_unlocked, miner_account, get_address(miner_account), MK_COINS(1), TESTS_DEFAULT_FEE, cryptonote::TX_OUTPUT_DECOYS, sources, destinations, &change_amount); sources.front().amount++; transaction tx = {}; @@ -362,10 +362,10 @@ bool gen_tx_input_wo_key_offsets::generate(std::vector& events std::vector sources; std::vector destinations; - fill_tx_sources_and_destinations(events, blk_money_unlocked, miner_account, get_address(miner_account), MK_COINS(1), TESTS_DEFAULT_FEE, CRYPTONOTE_DEFAULT_TX_MIXIN, sources, destinations); + fill_tx_sources_and_destinations(events, blk_money_unlocked, miner_account, get_address(miner_account), MK_COINS(1), TESTS_DEFAULT_FEE, cryptonote::TX_OUTPUT_DECOYS, sources, destinations); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); txin_to_key& in_to_key = var::get(tx.vin.front()); while (!in_to_key.key_offsets.empty()) in_to_key.key_offsets.pop_back(); @@ -392,11 +392,11 @@ bool gen_tx_key_offset_points_to_foreign_key::generate(std::vector sources_alice; std::vector destinations_alice; - fill_tx_sources_and_destinations(events, blk_money_unlocked, alice_account, get_address(miner_account), MK_COINS(15) + 1 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, CRYPTONOTE_DEFAULT_TX_MIXIN, sources_alice, destinations_alice); + fill_tx_sources_and_destinations(events, blk_money_unlocked, alice_account, get_address(miner_account), MK_COINS(15) + 1 - TESTS_DEFAULT_FEE, TESTS_DEFAULT_FEE, cryptonote::TX_OUTPUT_DECOYS, sources_alice, destinations_alice); txin_to_key& bob_in_to_key = var::get(bob_tx.vin.front()); bob_in_to_key.key_offsets.front() = sources_alice.front().outputs.back().first; @@ -423,7 +423,7 @@ bool gen_tx_sender_key_offset_not_exist::generate(std::vector& REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); txin_to_key& in_to_key = var::get(tx.vin.front()); in_to_key.key_offsets.front() = std::numeric_limits::max(); @@ -489,12 +489,12 @@ bool gen_tx_mixed_key_offset_not_exist::generate(std::vector& std::vector sources; std::vector destinations; uint64_t change_amount; - fill_tx_sources_and_destinations(events, blk_money_unlocked, bob_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, CRYPTONOTE_DEFAULT_TX_MIXIN, sources, destinations, &change_amount); + fill_tx_sources_and_destinations(events, blk_money_unlocked, bob_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, cryptonote::TX_OUTPUT_DECOYS, sources, destinations, &change_amount); sources.front().outputs[(sources.front().real_output + 1) % 2].first = std::numeric_limits::max(); transaction tx = {}; cryptonote::tx_destination_entry change_addr{ change_amount, miner_account.get_keys().m_account_address, false /*is_subaddress*/ }; - assert(cryptonote::construct_tx(miner_account.get_keys(), sources, destinations, change_addr, {} /*tx_extra*/, tx, 0 /*unlock_time*/, cryptonote::network_version_7)); + assert(cryptonote::construct_tx(miner_account.get_keys(), sources, destinations, change_addr, {} /*tx_extra*/, tx, 0 /*unlock_time*/, cryptonote::hf::hf7)); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx); @@ -511,7 +511,7 @@ bool gen_tx_key_image_not_derive_from_tx_key::generate(std::vector(tx.vin.front()); // Use fake key image @@ -539,7 +539,7 @@ bool gen_tx_key_image_is_invalid::generate(std::vector& events REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); txin_to_key& in_to_key = var::get(tx.vin.front()); in_to_key.k_image = generate_invalid_key_image(); @@ -623,7 +623,7 @@ bool gen_tx_txout_to_key_has_invalid_key::generate(std::vector REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); txout_to_key& out_to_key = var::get(tx.vout.front().target); out_to_key.key = generate_invalid_pub_key(); @@ -648,7 +648,7 @@ bool gen_tx_output_with_zero_amount::generate(std::vector& eve std::vector sources; std::vector destinations; uint64_t change_amount; - fill_tx_sources_and_destinations(events, blk_money_unlocked, miner_account, get_address(miner_account), MK_COINS(1), TESTS_DEFAULT_FEE, CRYPTONOTE_DEFAULT_TX_MIXIN, sources, destinations, &change_amount); + fill_tx_sources_and_destinations(events, blk_money_unlocked, miner_account, get_address(miner_account), MK_COINS(1), TESTS_DEFAULT_FEE, cryptonote::TX_OUTPUT_DECOYS, sources, destinations, &change_amount); for (tx_destination_entry &entry : destinations) entry.amount = 0; @@ -659,7 +659,7 @@ bool gen_tx_output_with_zero_amount::generate(std::vector& eve #else transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); tx.vout.front().amount = 0; #endif @@ -678,14 +678,14 @@ bool gen_tx_output_is_not_txout_to_key::generate(std::vector& REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); tx.vout.back().target = txout_to_script(); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx); tx = {}; - beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).build(); + beldex_tx_builder(events, tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).build(); tx.vout.back().target = txout_to_scripthash(); DO_CALLBACK(events, "mark_invalid_tx"); @@ -710,7 +710,7 @@ bool gen_tx_signatures_are_invalid::generate(std::vector& even REWIND_BLOCKS (events, blk_head, blk_money_unlocked, miner_account); transaction miner_tx = {}; - beldex_tx_builder(events, miner_tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(60), cryptonote::network_version_7).with_fee(TESTS_DEFAULT_FEE).build(); + beldex_tx_builder(events, miner_tx, blk_money_unlocked, miner_account, miner_account.get_keys().m_account_address, MK_COINS(60), cryptonote::hf::hf7).with_fee(TESTS_DEFAULT_FEE).build(); // TX without signatures DO_CALLBACK(events, "mark_invalid_tx"); @@ -730,7 +730,7 @@ bool gen_tx_signatures_are_invalid::generate(std::vector& even events.push_back(serialized_transaction(sr_tx)); transaction bob_tx = {}; - beldex_tx_builder(events, bob_tx, blk_money_unlocked, bob_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::network_version_7).with_fee(TESTS_DEFAULT_FEE).build(); + beldex_tx_builder(events, bob_tx, blk_money_unlocked, bob_account, miner_account.get_keys().m_account_address, MK_COINS(1), cryptonote::hf::hf7).with_fee(TESTS_DEFAULT_FEE).build(); // TX without signatures DO_CALLBACK(events, "mark_invalid_tx"); diff --git a/tests/core_tests/v2_tests.cpp b/tests/core_tests/v2_tests.cpp index b6472e69931..3480ae531f4 100755 --- a/tests/core_tests/v2_tests.cpp +++ b/tests/core_tests/v2_tests.cpp @@ -54,7 +54,7 @@ bool gen_v2_tx_validation_base::generate_with(std::vector& eve miner_accounts[n].generate(); CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n], test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp, - 2, 2, prev_block->timestamp + tools::to_seconds(TARGET_BLOCK_TIME_OLD) * 2, // v2 has blocks twice as long + cryptonote::hf::hf1, 2, prev_block->timestamp + tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector(), 0), false, "Failed to generate block"); events.push_back(blocks[n]); @@ -65,12 +65,12 @@ bool gen_v2_tx_validation_base::generate_with(std::vector& eve cryptonote::block blk_r; { cryptonote::block blk_last = blocks[3]; - for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i) + for (size_t i = 0; i < cryptonote::MINED_MONEY_UNLOCK_WINDOW; ++i) { cryptonote::block blk; CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_account, test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp, - 2, 2, blk_last.timestamp + tools::to_seconds(TARGET_BLOCK_TIME_OLD) * 2, // v2 has blocks twice as long + cryptonote::hf::hf1, 2, blk_last.timestamp + tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12) * 2, // v2 has blocks twice as long crypto::hash(), 0, transaction(), std::vector(), 0), false, "Failed to generate block"); events.push_back(blk); diff --git a/tests/core_tests/v2_tests.h b/tests/core_tests/v2_tests.h index 14f75923a3f..bbdb42898e1 100755 --- a/tests/core_tests/v2_tests.h +++ b/tests/core_tests/v2_tests.h @@ -79,7 +79,7 @@ struct gen_v2_tx_validation_base : public test_chain_unit_base template<> struct get_test_options { - const std::vector hard_forks = {{1,0,0,0}, {2,0,0,0}}; + const std::vector hard_forks = {{cryptonote::hf::hf7,0,0,0}, {cryptonote::hf::hf8,0,0,0}}; const cryptonote::test_options test_options = { hard_forks, 0 }; diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index 74437d60e3e..4e27ae9cc22 100755 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -105,7 +105,7 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector cur_height) + if (td.m_block_height + cryptonote::MINED_MONEY_UNLOCK_WINDOW > cur_height) continue; if (selected_idx.find((size_t)i) != selected_idx.end()){ MERROR("Should not happen (selected_idx not found): " << i); diff --git a/tests/difficulty/difficulty.cpp b/tests/difficulty/difficulty.cpp index 2726938777a..72b35201512 100755 --- a/tests/difficulty/difficulty.cpp +++ b/tests/difficulty/difficulty.cpp @@ -58,17 +58,17 @@ int main(int argc, char *argv[]) { size_t n = 0; while (data >> timestamp >> difficulty) { size_t begin, end; - if (n < (DIFFICULTY_WINDOW + 1) + DIFFICULTY_LAG) { + if (n < ( cryptonote::old::DIFFICULTY_WINDOW + 1) + DIFFICULTY_LAG) { begin = 0; - end = std::min(n, (size_t) (DIFFICULTY_WINDOW + 1)); + end = std::min(n, (size_t) ( cryptonote::old::DIFFICULTY_WINDOW + 1)); } else { end = n - DIFFICULTY_LAG; - begin = end - (DIFFICULTY_WINDOW + 1); + begin = end - ( cryptonote::old::DIFFICULTY_WINDOW + 1); } uint64_t res = cryptonote::next_difficulty_v2( std::vector(timestamps.begin() + begin, timestamps.begin() + end), std::vector(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), - tools::to_seconds(TARGET_BLOCK_TIME_OLD), //use old Blocktime + tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12), //use old Blocktime cryptonote::difficulty_calc_mode::normal); if (res != difficulty) { std::cerr << "Wrong difficulty for block " << n diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 06c37bd65a4..837a6d5a144 100755 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -268,12 +268,12 @@ bool transactions_flow_test(std::string& working_folder, LOG_PRINT_L0( "waiting some new blocks..."); - std::this_thread::sleep_for(TARGET_BLOCK_TIME_OLD*20*1s);//wait two blocks before sync on another wallet on another daemon + std::this_thread::sleep_for(TARGET_BLOCK_TIME_12*20*1s);//wait two blocks before sync on another wallet on another daemon LOG_PRINT_L0( "refreshing..."); bool recvd_money = false; while(w2.refresh(true, blocks_fetched, recvd_money, ok) && ( (blocks_fetched && recvd_money) || !blocks_fetched ) ) { - std::this_thread::sleep_for(TARGET_BLOCK_TIME_OLD*1s);//wait two blocks before sync on another wallet on another daemon + std::this_thread::sleep_for(TARGET_BLOCK_TIME_12*1s);//wait two blocks before sync on another wallet on another daemon } uint64_t money_2 = w2.balance(0, true); diff --git a/tests/performance_tests/check_tx_signature.h b/tests/performance_tests/check_tx_signature.h index d0aeb790dec..b3a20f53b41 100755 --- a/tests/performance_tests/check_tx_signature.h +++ b/tests/performance_tests/check_tx_signature.h @@ -72,7 +72,7 @@ class test_check_tx_signature : private multi_tx_test_base std::unordered_map subaddresses; subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::network_version_count - 1; + tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plusplus; rct::RCTConfig rct_config{range_proof_type, bp_version}; if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector(), m_tx, 0, tx_key, additional_tx_keys, rct_config, nullptr, tx_params)) return false; @@ -136,7 +136,7 @@ class test_check_tx_signature_aggregated_bulletproofs : private multi_tx_test_ba subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::network_version_count - 1; + tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plusplus; m_txes.resize(a_num_txes + (extra_outs > 0 ? 1 : 0)); for (size_t n = 0; n < a_num_txes; ++n) { diff --git a/tests/performance_tests/construct_tx.h b/tests/performance_tests/construct_tx.h index 93a7668bbe0..6df8c4015a3 100755 --- a/tests/performance_tests/construct_tx.h +++ b/tests/performance_tests/construct_tx.h @@ -75,7 +75,7 @@ class test_construct_tx : private multi_tx_test_base subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct::RCTConfig rct_config{range_proof_type, bp_version}; cryptonote::beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::network_version_count - 1; + tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plusplus; return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::tx_destination_entry{}, std::vector(), m_tx, 0, tx_key, additional_tx_keys, rct_config, nullptr, tx_params); } diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index f63fd48c5de..df010dd7ef6 100755 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -741,7 +741,7 @@ bool gen_trezor_base::generate(std::vector& events) const crypto::hash prev_id = get_block_hash(blk_gen); const uint64_t already_generated_coins = generator.get_already_generated_coins(prev_id); block_weights.clear(); - generator.get_last_n_block_weights(block_weights, prev_id, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + generator.get_last_n_block_weights(block_weights, prev_id, cryptonote::REWARD_BLOCKS_WINDOW); generator.construct_block(blk_0, 1, prev_id, m_miner_account, m_ts_start, already_generated_coins, block_weights, tx_list); } diff --git a/tests/unit_tests/base58.cpp b/tests/unit_tests/base58.cpp index 88d47fa21ee..10de07b15c4 100755 --- a/tests/unit_tests/base58.cpp +++ b/tests/unit_tests/base58.cpp @@ -474,14 +474,14 @@ TEST(get_account_address_as_str, works_correctly) { cryptonote::account_public_address addr; ASSERT_NO_THROW(serialization::parse_binary(test_serialized_keys, addr)); - std::string addr_str = cryptonote::get_account_address_as_str(cryptonote::MAINNET, false, addr); + std::string addr_str = cryptonote::get_account_address_as_str(cryptonote::network_type::MAINNET, false, addr); ASSERT_EQ(addr_str, test_keys_addr_str); } TEST(get_account_address_from_str, handles_valid_address) { cryptonote::address_parse_info info; - ASSERT_TRUE(cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, test_keys_addr_str)); + ASSERT_TRUE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, test_keys_addr_str)); std::string blob; ASSERT_NO_THROW(blob = serialization::dump_binary(info.address)); @@ -494,7 +494,7 @@ TEST(get_account_address_from_str, fails_on_invalid_address_format) std::string addr_str = test_keys_addr_str; addr_str[0] = '0'; - ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, addr_str)); + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_prefix) @@ -502,33 +502,33 @@ TEST(get_account_address_from_str, fails_on_invalid_address_prefix) std::string addr_str = base58::encode_addr(0, test_serialized_keys); cryptonote::address_parse_info info; - ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, addr_str)); + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_content) { - std::string addr_str = base58::encode_addr(config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, test_serialized_keys.substr(1)); + std::string addr_str = base58::encode_addr(cryptonote::config::PUBLIC_ADDRESS_BASE58_PREFIX, test_serialized_keys.substr(1)); cryptonote::address_parse_info info; - ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, addr_str)); + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_spend_key) { std::string serialized_keys_copy = test_serialized_keys; serialized_keys_copy[0] = '\0'; - std::string addr_str = base58::encode_addr(config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); + std::string addr_str = base58::encode_addr(cryptonote::config::PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); cryptonote::address_parse_info info; - ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, addr_str)); + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, addr_str)); } TEST(get_account_address_from_str, fails_on_invalid_address_view_key) { std::string serialized_keys_copy = test_serialized_keys; serialized_keys_copy.back() = '\0'; - std::string addr_str = base58::encode_addr(config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); + std::string addr_str = base58::encode_addr(cryptonote::config::PUBLIC_ADDRESS_BASE58_PREFIX, serialized_keys_copy); cryptonote::address_parse_info info; - ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::MAINNET, addr_str)); + ASSERT_FALSE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, addr_str)); } diff --git a/tests/unit_tests/block_reward.cpp b/tests/unit_tests/block_reward.cpp index 796225ec8f2..73968243e58 100755 --- a/tests/unit_tests/block_reward.cpp +++ b/tests/unit_tests/block_reward.cpp @@ -41,7 +41,7 @@ namespace class block_reward_and_already_generated_coins : public ::testing::Test { protected: - static const size_t current_block_weight = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2; + static const size_t current_block_weight = cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2; union { @@ -53,12 +53,12 @@ namespace }; #define TEST_ALREADY_GENERATED_COINS(already_generated_coins, expected_reward) \ - m_block_not_too_big = get_base_block_reward(0, current_block_weight, already_generated_coins, m_block_reward, m_block_reward_unpenalized, 7,0); \ + m_block_not_too_big = get_base_block_reward(0, current_block_weight, already_generated_coins, m_block_reward, m_block_reward_unpenalized, cryptonote::hf::hf7, 0); \ ASSERT_TRUE(m_block_not_too_big); \ ASSERT_EQ(m_block_reward, expected_reward); #define TEST_ALREADY_GENERATED_COINS_V2(already_generated_coins, expected_reward, h) \ - m_block_not_too_big = get_base_block_reward(0, current_block_weight, already_generated_coins, m_block_reward, m_block_reward_unpenalized, 8,h); \ + m_block_not_too_big = get_base_block_reward(0, current_block_weight, already_generated_coins, m_block_reward, m_block_reward_unpenalized, cryptonote::hf::hf8, h); \ ASSERT_TRUE(m_block_not_too_big); \ ASSERT_EQ(m_block_reward, expected_reward); @@ -87,9 +87,9 @@ namespace TEST_F(block_reward_and_already_generated_coins, handles_max) { - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((1 << 20) + 1), UINT64_C(0)); - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - (1 << 20) , UINT64_C(0)); - TEST_ALREADY_GENERATED_COINS(MONEY_SUPPLY - ((1 << 20) - 1), UINT64_C(0)); + TEST_ALREADY_GENERATED_COINS(beldex::MONEY_SUPPLY - ((1 << 20) + 1), UINT64_C(0)); + TEST_ALREADY_GENERATED_COINS(beldex::MONEY_SUPPLY - (1 << 20) , UINT64_C(0)); + TEST_ALREADY_GENERATED_COINS(beldex::MONEY_SUPPLY - ((1 << 20) - 1), UINT64_C(0)); } TEST_F(block_reward_and_already_generated_coins, reward_parity_between_orig_and_beldex_algo) @@ -99,24 +99,24 @@ namespace cryptonote::block_reward_parts reward_parts_v7 = {}; cryptonote::beldex_block_reward_context reward_context_v7 = {}; - m_block_reward_calc_success &= get_beldex_block_reward(0, current_block_weight, already_generated_coins, 7, reward_parts_v7, reward_context_v7); + m_block_reward_calc_success &= get_beldex_block_reward(0, current_block_weight, already_generated_coins, cryptonote::hf::hf7, reward_parts_v7, reward_context_v7); cryptonote::block_reward_parts reward_parts_v8 = {}; cryptonote::beldex_block_reward_context reward_context_v8 = {}; - m_block_reward_calc_success &= get_beldex_block_reward(0, current_block_weight, already_generated_coins, 8, reward_parts_v8, reward_context_v8); + m_block_reward_calc_success &= get_beldex_block_reward(0, current_block_weight, already_generated_coins, cryptonote::hf::hf8, reward_parts_v8, reward_context_v8); cryptonote::block_reward_parts reward_parts_v9 = {}; cryptonote::beldex_block_reward_context reward_context_v9 = {}; - m_block_reward_calc_success &= get_beldex_block_reward(0, current_block_weight, already_generated_coins, 9, reward_parts_v9, reward_context_v9); + m_block_reward_calc_success &= get_beldex_block_reward(0, current_block_weight, already_generated_coins, cryptonote::hf::hf9_master_nodes, reward_parts_v9, reward_context_v9); uint64_t reward_v7_orig = 0, reward_unpenalized_unused_; - m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v7_orig, reward_unpenalized_unused_, 7, 0); + m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v7_orig, reward_unpenalized_unused_, cryptonote::hf::hf7, 0); uint64_t reward_v8_orig = 0; - m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v8_orig, reward_unpenalized_unused_, 8, 0); + m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v8_orig, reward_unpenalized_unused_, cryptonote::hf::hf8, 0); uint64_t reward_v9_orig = 0; - m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v9_orig, reward_unpenalized_unused_, 9, 0); + m_block_reward_calc_success &= cryptonote::get_base_block_reward(0, current_block_weight, already_generated_coins, reward_v9_orig, reward_unpenalized_unused_, cryptonote::hf::hf9_master_nodes, 0); ASSERT_TRUE(m_block_reward_calc_success); \ ASSERT_EQ(reward_parts_v7.original_base_reward, reward_v7_orig); @@ -125,5 +125,5 @@ namespace } //-------------------------------------------------------------------------------------------------------------------- - // TODO(doyle): The following tests, test using CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 which is not relevant to us. + // TODO(doyle): The following tests, test using cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V1 which is not relevant to us. } diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index a50525cc474..a0be561cd31 100755 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -111,7 +111,7 @@ static uint32_t lcg() #define PREFIX_WINDOW(hf_version,window) \ blockchain_objects_t bc_objects = {}; \ const std::vector hard_forks{ \ - {7,0,0,0}, {hf_version,0,1,0}}; \ + {cryptonote::hf::hf7,0,0,0}, {hf_version,0,1,0}}; \ const cryptonote::test_options test_options = { \ hard_forks, \ window, \ @@ -124,21 +124,21 @@ static uint32_t lcg() TEST(long_term_block_weight, empty_short) { - PREFIX(9); + PREFIX(cryptonote::hf::hf9_master_nodes); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); - ASSERT_EQ(bc->get_current_cumulative_block_weight_median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5); - ASSERT_EQ(bc->get_current_cumulative_block_weight_limit(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 * 2); + ASSERT_EQ(bc->get_current_cumulative_block_weight_median(), cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5); + ASSERT_EQ(bc->get_current_cumulative_block_weight_limit(), cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 * 2); } TEST(long_term_block_weight, identical_before_fork) { - PREFIX(9); + PREFIX(cryptonote::hf::hf9_master_nodes); for (uint64_t h = 1; h < 10 * TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h) { - size_t w = h < CRYPTONOTE_REWARD_BLOCKS_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = h < cryptonote::REWARD_BLOCKS_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -151,11 +151,11 @@ TEST(long_term_block_weight, identical_before_fork) TEST(long_term_block_weight, identical_after_fork_before_long_term_window) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); for (uint64_t h = 1; h <= TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h) { - size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -168,11 +168,11 @@ TEST(long_term_block_weight, identical_after_fork_before_long_term_window) TEST(long_term_block_weight, ceiling_at_30000000) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); for (uint64_t h = 0; h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW + TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW / 2 - 1; ++h) { - size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -183,11 +183,11 @@ TEST(long_term_block_weight, ceiling_at_30000000) TEST(long_term_block_weight, multi_pop) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); for (uint64_t h = 1; h <= TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW + 20; ++h) { - size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -217,11 +217,11 @@ TEST(long_term_block_weight, multi_pop) TEST(long_term_block_weight, multiple_updates) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); for (uint64_t h = 1; h <= 3 * TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h) { - size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -241,11 +241,11 @@ TEST(long_term_block_weight, multiple_updates) TEST(long_term_block_weight, pop_invariant_max) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); for (uint64_t h = 1; h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW - 10; ++h) { - size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -273,7 +273,7 @@ TEST(long_term_block_weight, pop_invariant_max) } for (int i = 0; i < add; ++i) { - size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); + size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : bc->get_current_cumulative_block_weight_limit(); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, bc->get_db().height(), bc->get_db().height(), {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -289,13 +289,13 @@ TEST(long_term_block_weight, pop_invariant_max) TEST(long_term_block_weight, pop_invariant_random) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); for (uint64_t h = 1; h < 2 * TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW - 10; ++h) { lcg_seed = bc->get_db().height(); uint32_t r = lcg(); - size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : (r % bc->get_current_cumulative_block_weight_limit()); + size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : (r % bc->get_current_cumulative_block_weight_limit()); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -330,7 +330,7 @@ TEST(long_term_block_weight, pop_invariant_random) { lcg_seed = bc->get_db().height(); uint32_t r = lcg(); - size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : (r % bc->get_current_cumulative_block_weight_limit()); + size_t w = bc->get_db().height() < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW ? cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5 : (r % bc->get_current_cumulative_block_weight_limit()); uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, bc->get_db().height(), bc->get_db().height(), {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); @@ -351,14 +351,14 @@ TEST(long_term_block_weight, pop_invariant_random) TEST(long_term_block_weight, long_growth_spike_and_drop) { - PREFIX(HF_VERSION_LONG_TERM_BLOCK_WEIGHT); + PREFIX(cryptonote::feature::LONG_TERM_BLOCK_WEIGHT); uint64_t long_term_effective_median_block_weight; // constant init for (uint64_t h = 0; h < TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h) { - size_t w = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; + size_t w = cryptonote::BLOCK_GRANTED_FULL_REWARD_ZONE_V5; uint64_t ltw = bc->get_next_long_term_block_weight(w); bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); ASSERT_TRUE(bc->update_next_cumulative_weight_limit(&long_term_effective_median_block_weight)); diff --git a/tests/unit_tests/master_nodes.cpp b/tests/unit_tests/master_nodes.cpp index 1b704f2825a..a256cb4c78a 100755 --- a/tests/unit_tests/master_nodes.cpp +++ b/tests/unit_tests/master_nodes.cpp @@ -39,14 +39,14 @@ TEST(master_nodes, staking_requirement) { // NOTE: Thanks for the values @Sonofotis - const uint64_t atomic_epsilon = config::DEFAULT_DUST_THRESHOLD; + const uint64_t atomic_epsilon = cryptonote::config::DEFAULT_DUST_THRESHOLD; // LHS of Equation // Try underflow { uint64_t height = 100; uint64_t mainnet_requirement = master_nodes::get_staking_requirement(height); - ASSERT_EQ(mainnet_requirement, (45000 * COIN)); + ASSERT_EQ(mainnet_requirement, (45000 * beldex::COIN)); } // Starting height for mainnet @@ -56,14 +56,14 @@ TEST(master_nodes, staking_requirement) uint64_t height = 101250; int64_t mainnet_requirement = (int64_t)master_nodes::get_staking_requirement(height); - ASSERT_EQ(mainnet_requirement, (45000 * COIN)); + ASSERT_EQ(mainnet_requirement, (45000 * beldex::COIN)); } // Check the requirements are decreasing { uint64_t height = 209250; int64_t mainnet_requirement = (int64_t)master_nodes::get_staking_requirement(height); - int64_t mainnet_expected = (int64_t)((29643 * COIN) + 670390000); + int64_t mainnet_expected = (int64_t)((29643 * beldex::COIN) + 670390000); int64_t mainnet_delta = std::abs(mainnet_requirement - mainnet_expected); ASSERT_LT(mainnet_delta, atomic_epsilon); } @@ -74,7 +74,7 @@ TEST(master_nodes, staking_requirement) uint64_t height = 230704; int64_t mainnet_requirement = (int64_t)master_nodes::get_staking_requirement(height); - int64_t mainnet_expected = (int64_t)((27164 * COIN) + 648610000); + int64_t mainnet_expected = (int64_t)((27164 * beldex::COIN) + 648610000); int64_t mainnet_delta = std::abs(mainnet_requirement - mainnet_expected); ASSERT_LT(mainnet_delta, atomic_epsilon); } @@ -84,7 +84,7 @@ TEST(master_nodes, staking_requirement) uint64_t height = 373200; int64_t mainnet_requirement = (int64_t)master_nodes::get_staking_requirement(height); - int64_t mainnet_expected = (int64_t)((20839 * COIN) + 644149350); + int64_t mainnet_expected = (int64_t)((20839 * beldex::COIN) + 644149350); ASSERT_EQ(mainnet_requirement, mainnet_expected); } @@ -93,7 +93,7 @@ TEST(master_nodes, staking_requirement) uint64_t height = 450000; uint64_t mainnet_requirement = master_nodes::get_staking_requirement(height); - uint64_t mainnet_expected = (18898 * COIN) + 351896001; + uint64_t mainnet_expected = (18898 * beldex::COIN) + 351896001; ASSERT_EQ(mainnet_requirement, mainnet_expected); } @@ -102,7 +102,7 @@ TEST(master_nodes, staking_requirement) uint64_t height = 999999; uint64_t mainnet_requirement = master_nodes::get_staking_requirement(height); - uint64_t mainnet_expected = (15000 * COIN) + 3122689; + uint64_t mainnet_expected = (15000 * beldex::COIN) + 3122689; ASSERT_EQ(mainnet_requirement, mainnet_expected); } @@ -111,7 +111,7 @@ TEST(master_nodes, staking_requirement) uint64_t height = 1000000; uint64_t mainnet_requirement = master_nodes::get_staking_requirement(height); - uint64_t mainnet_expected = 15000 * COIN; + uint64_t mainnet_expected = 15000 * beldex::COIN; ASSERT_EQ(mainnet_requirement, mainnet_expected); } } @@ -121,8 +121,8 @@ static bool verify_vote(master_nodes::quorum_vote_t const &vote, cryptonote::vote_verification_context &vvc, master_nodes::quorum const &quorum) { - bool result = master_nodes::verify_vote_age(vote, latest_height,vvc, cryptonote::network_version_17_POS ); - result &= master_nodes::verify_vote_signature(cryptonote::network_version_count - 1, vote, vvc, quorum); + bool result = master_nodes::verify_vote_age(vote, latest_height,vvc, cryptonote::hf::hf17_POS); + result &= master_nodes::verify_vote_signature(cryptonote::hf::hf20_bulletproof_plusplus, vote, vvc, quorum); return result; } @@ -230,7 +230,7 @@ TEST(master_nodes, tx_extra_state_change_validation) // Valid state_change cryptonote::tx_extra_master_node_state_change valid_state_change = {}; - uint8_t hf_version = cryptonote::network_version_11_infinite_staking; + cryptonote::hf hf_version = cryptonote::hf::hf11_infinite_staking; const uint64_t HEIGHT = 100; { valid_state_change.block_height = HEIGHT - 1; @@ -322,114 +322,82 @@ TEST(master_nodes, tx_extra_state_change_validation) TEST(master_nodes, min_portions) { - uint8_t hf_version = cryptonote::network_version_9_master_nodes; + cryptonote::hf hf_version = cryptonote::hf::hf9_master_nodes; // Test new contributors can *NOT* stake to a registration with under 25% of the total stake if there is more than 25% available. { - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {0, STAKING_PORTIONS})); + ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {0, cryptonote::old::STAKING_PORTIONS})); } - - { - const auto small = MIN_PORTIONS - 1; - const auto rest = STAKING_PORTIONS - small; - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {small, rest})); - } - - { - /// TODO: fix this test - const auto small = MIN_PORTIONS - 1; - const auto rest = STAKING_PORTIONS - small - STAKING_PORTIONS / 2; - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {STAKING_PORTIONS / 2, small, rest})); - } - - { - const auto small = MIN_PORTIONS - 1; - const auto rest = STAKING_PORTIONS - small - 2 * MIN_PORTIONS; - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {MIN_PORTIONS, MIN_PORTIONS, small, rest})); - } - // Test new contributors *CAN* stake as the last person with under 25% if there is less than 25% available. // Two contributers { - const auto large = 4 * (STAKING_PORTIONS / 5); - const auto rest = STAKING_PORTIONS - large; + const auto large = 4 * (cryptonote::old::STAKING_PORTIONS / 5); + const auto rest = cryptonote::old::STAKING_PORTIONS - large; bool result = master_nodes::check_master_node_portions(hf_version, {large, rest}); ASSERT_TRUE(result); } // Three contributers { - const auto half = STAKING_PORTIONS / 2 - 1; - const auto rest = STAKING_PORTIONS - 2 * half; + const auto half = cryptonote::old::STAKING_PORTIONS / 2 - 1; + const auto rest = cryptonote::old::STAKING_PORTIONS - 2 * half; bool result = master_nodes::check_master_node_portions(hf_version, {half, half, rest}); ASSERT_TRUE(result); } // Four contributers { - const auto third = STAKING_PORTIONS / 3 - 1; - const auto rest = STAKING_PORTIONS - 3 * third; + const auto third = cryptonote::old::STAKING_PORTIONS / 3 - 1; + const auto rest = cryptonote::old::STAKING_PORTIONS - 3 * third; bool result = master_nodes::check_master_node_portions(hf_version, {third, third, third, rest}); ASSERT_TRUE(result); } // ===== After hard fork v11 ===== - hf_version = cryptonote::network_version_11_infinite_staking; + hf_version = cryptonote::hf::hf11_infinite_staking; { - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {0, STAKING_PORTIONS})); + ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {0, cryptonote::old::STAKING_PORTIONS})); } { - const auto small = MIN_PORTIONS - 1; - const auto rest = STAKING_PORTIONS - small; - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {small, rest})); - } - - { - const auto small = STAKING_PORTIONS / 8; - const auto rest = STAKING_PORTIONS - small - STAKING_PORTIONS / 2; - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {STAKING_PORTIONS / 2, small, rest})); - } - - { - const auto small = MIN_PORTIONS - 1; - const auto rest = STAKING_PORTIONS - small - 2 * MIN_PORTIONS; - ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {MIN_PORTIONS, MIN_PORTIONS, small, rest})); + const auto small = cryptonote::old::STAKING_PORTIONS / 8; + const auto rest = cryptonote::old::STAKING_PORTIONS - small - cryptonote::old::STAKING_PORTIONS / 2; + ASSERT_FALSE(master_nodes::check_master_node_portions(hf_version, {cryptonote::old::STAKING_PORTIONS / 2, small, rest})); } // Test new contributors *CAN* stake as the last person with under 25% if there is less than 25% available. // Two contributers { - const auto large = 4 * (STAKING_PORTIONS / 5); - const auto rest = STAKING_PORTIONS - large; + const auto large = 4 * (cryptonote::old::STAKING_PORTIONS / 5); + const auto rest = cryptonote::old::STAKING_PORTIONS - large; bool result = master_nodes::check_master_node_portions(hf_version, {large, rest}); ASSERT_TRUE(result); } // Three contributers { - const auto half = STAKING_PORTIONS / 2 - 1; - const auto rest = STAKING_PORTIONS - 2 * half; + const auto half = cryptonote::old::STAKING_PORTIONS / 2 - 1; + const auto rest = cryptonote::old::STAKING_PORTIONS - 2 * half; bool result = master_nodes::check_master_node_portions(hf_version, {half, half, rest}); ASSERT_TRUE(result); } // Four contributers { - const auto third = STAKING_PORTIONS / 3 - 1; - const auto rest = STAKING_PORTIONS - 3 * third; + const auto third = cryptonote::old::STAKING_PORTIONS / 3 - 1; + const auto rest = cryptonote::old::STAKING_PORTIONS - 3 * third; bool result = master_nodes::check_master_node_portions(hf_version, {third, third, third, rest}); ASSERT_TRUE(result); } // New test for hf_v11: allow less than 25% stake in the presence of large existing contributions { - const auto large = STAKING_PORTIONS / 2; - const auto small_1 = STAKING_PORTIONS / 6; - const auto small_2 = STAKING_PORTIONS / 6; - const auto rest = STAKING_PORTIONS - large - small_1 - small_2; + const auto large = cryptonote::old::STAKING_PORTIONS / 2; + const auto small_1 = cryptonote::old::STAKING_PORTIONS / 6; + const auto small_2 = cryptonote::old::STAKING_PORTIONS / 6; + const auto rest = cryptonote::old::STAKING_PORTIONS - large - small_1 - small_2; bool result = master_nodes::check_master_node_portions(hf_version, {large, small_1, small_2, rest}); ASSERT_TRUE(result); } @@ -442,7 +410,7 @@ TEST(master_nodes, min_stake_amount) { /// pre v11 uint64_t height = 101250; - uint8_t hf_version = cryptonote::network_version_9_master_nodes; + cryptonote::hf hf_version = cryptonote::hf::hf9_master_nodes; uint64_t stake_requirement = master_nodes::get_staking_requirement(height); { const uint64_t reserved = stake_requirement / 2; @@ -457,7 +425,7 @@ TEST(master_nodes, min_stake_amount) } /// post v11 - hf_version = cryptonote::network_version_11_infinite_staking; + hf_version = cryptonote::hf::hf11_infinite_staking; stake_requirement = master_nodes::get_staking_requirement(height); { // 50% reserved, with 1 contribution, max of 4- the minimum stake should be (50% / 3) @@ -490,14 +458,8 @@ TEST(master_nodes, min_stake_amount) TEST(master_nodes, master_node_rewards_proportional_to_portions) { { - const auto reward_a = cryptonote::get_portion_of_reward(MIN_PORTIONS, COIN); - const auto reward_b = cryptonote::get_portion_of_reward(3 * MIN_PORTIONS, COIN); - ASSERT_TRUE(3 * reward_a == reward_b); - } - - { - const auto reward_a = cryptonote::get_portion_of_reward(STAKING_PORTIONS/2, COIN); - const auto reward_b = cryptonote::get_portion_of_reward(STAKING_PORTIONS, COIN); + const auto reward_a = cryptonote::get_portion_of_reward(cryptonote::old::STAKING_PORTIONS/2, beldex::COIN); + const auto reward_b = cryptonote::get_portion_of_reward(cryptonote::old::STAKING_PORTIONS, beldex::COIN); ASSERT_TRUE(2 * reward_a == reward_b); } @@ -505,32 +467,32 @@ TEST(master_nodes, master_node_rewards_proportional_to_portions) TEST(master_nodes, master_node_get_locked_key_image_unlock_height) { - uint64_t lock_duration = master_nodes::staking_num_lock_blocks(cryptonote::MAINNET, cryptonote::network_version_17_POS) / 2; + uint64_t lock_duration = master_nodes::staking_num_lock_blocks(cryptonote::MAINNET, cryptonote::hf::hf17_POS) / 2; { uint64_t curr_height = 100; uint64_t expected = curr_height + lock_duration; - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, curr_height, cryptonote::network_version_17_POS); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, curr_height, cryptonote::hf::hf17_POS); ASSERT_EQ(unlock_height, expected); } { uint64_t curr_height = lock_duration - 1; uint64_t expected = curr_height + lock_duration; - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, curr_height, cryptonote::network_version_17_POS); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, curr_height, cryptonote::hf::hf17_POS); ASSERT_EQ(unlock_height, expected); } { uint64_t curr_height = lock_duration + 100; uint64_t expected = curr_height + lock_duration; - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, curr_height, cryptonote::network_version_17_POS); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, curr_height, cryptonote::hf::hf17_POS); ASSERT_EQ(unlock_height, expected); } { uint64_t expected = lock_duration + lock_duration; - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, lock_duration, cryptonote::network_version_17_POS); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET, lock_duration, cryptonote::hf::hf17_POS); ASSERT_EQ(unlock_height, expected); } @@ -538,7 +500,7 @@ TEST(master_nodes, master_node_get_locked_key_image_unlock_height) uint64_t register_height = lock_duration + 1; uint64_t curr_height = register_height + 2; uint64_t expected = curr_height + lock_duration; - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET,curr_height, cryptonote::network_version_17_POS); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(cryptonote::MAINNET,curr_height, cryptonote::hf::hf17_POS); ASSERT_EQ(unlock_height, expected); } } diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index bd504cc1adf..61c5d0c47fe 100755 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -76,7 +76,7 @@ class test_core bool prepare_handle_incoming_blocks(const std::vector &blocks_entry, std::vector &blocks) { return true; } bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } - size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } + size_t get_block_sync_size(uint64_t height) const { return cryptonote::BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } virtual crypto::hash on_transaction_relayed(const cryptonote::blobdata& tx) { return crypto::null_hash; } cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; } bool get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const { return false; } @@ -85,7 +85,7 @@ class test_core uint8_t get_ideal_hard_fork_version() const { return 0; } uint8_t get_ideal_hard_fork_version(uint64_t height) const { return 0; } uint8_t get_hard_fork_version(uint64_t height) const { return 0; } - uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return 0; } + uint64_t get_earliest_ideal_height_for_version(cryptonote::hf version) const { return 0; } cryptonote::difficulty_type get_block_cumulative_difficulty(uint64_t height) const { return 0; } uint64_t prevalidate_block_hashes(uint64_t height, const std::vector &hashes) { return 0; } bool pad_transactions() { return false; } diff --git a/tests/unit_tests/output_distribution.cpp b/tests/unit_tests/output_distribution.cpp index a0d38ddb739..60d2ccc628d 100755 --- a/tests/unit_tests/output_distribution.cpp +++ b/tests/unit_tests/output_distribution.cpp @@ -83,7 +83,7 @@ bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64 { blockchain_objects_t bc = {}; struct get_test_options { - const std::vector hard_forks{{1,0,0,0}}; + const std::vector hard_forks{{cryptonote::hf::hf1,0,0,0}}; const cryptonote::test_options test_options = { hard_forks }; diff --git a/tests/unit_tests/pruning.cpp b/tests/unit_tests/pruning.cpp index 0a007cb8ee8..f69bf88c967 100755 --- a/tests/unit_tests/pruning.cpp +++ b/tests/unit_tests/pruning.cpp @@ -76,49 +76,49 @@ TEST(pruning, fits) TEST(pruning, tip) { - static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + 1000; - static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS, "H must be >= CRYPTONOTE_PRUNING_TIP_BLOCKS"); - for (uint64_t h = H - CRYPTONOTE_PRUNING_TIP_BLOCKS; h < H; ++h) + static constexpr uint64_t H = cryptonote::PRUNING_TIP_BLOCKS + 1000; + static_assert(H >= cryptonote::PRUNING_TIP_BLOCKS, "H must be >= cryptonote::PRUNING_TIP_BLOCKS"); + for (uint64_t h = H - cryptonote::PRUNING_TIP_BLOCKS; h < H; ++h) { - uint32_t pruning_seed = tools::get_pruning_seed(h, H, CRYPTONOTE_PRUNING_LOG_STRIPES); + uint32_t pruning_seed = tools::get_pruning_seed(h, H, cryptonote::PRUNING_LOG_STRIPES); ASSERT_EQ(pruning_seed, 0); - for (pruning_seed = 0; pruning_seed <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++pruning_seed) + for (pruning_seed = 0; pruning_seed <= (1 << cryptonote::PRUNING_LOG_STRIPES); ++pruning_seed) ASSERT_TRUE(tools::has_unpruned_block(h, H, pruning_seed)); } } TEST(pruning, seed) { - const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE; - const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES; + const uint64_t SS = cryptonote::PRUNING_STRIPE_SIZE; + const uint64_t NS = 1 << cryptonote::PRUNING_LOG_STRIPES; const uint64_t TB = NS * SS; for (uint64_t cycle = 0; cycle < 10; ++cycle) { const uint64_t O = TB * cycle; - ASSERT_EQ(tools::get_pruning_stripe(O + 0, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1); - ASSERT_EQ(tools::get_pruning_stripe(O + 1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1); - ASSERT_EQ(tools::get_pruning_stripe(O + SS-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1); - ASSERT_EQ(tools::get_pruning_stripe(O + SS, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 2); - ASSERT_EQ(tools::get_pruning_stripe(O + SS*2-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 2); - ASSERT_EQ(tools::get_pruning_stripe(O + SS*2, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 3); - ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), NS); - ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1); + ASSERT_EQ(tools::get_pruning_stripe(O + 0, 10000000, cryptonote::PRUNING_LOG_STRIPES), 1); + ASSERT_EQ(tools::get_pruning_stripe(O + 1, 10000000, cryptonote::PRUNING_LOG_STRIPES), 1); + ASSERT_EQ(tools::get_pruning_stripe(O + SS-1, 10000000, cryptonote::PRUNING_LOG_STRIPES), 1); + ASSERT_EQ(tools::get_pruning_stripe(O + SS, 10000000, cryptonote::PRUNING_LOG_STRIPES), 2); + ASSERT_EQ(tools::get_pruning_stripe(O + SS*2-1, 10000000, cryptonote::PRUNING_LOG_STRIPES), 2); + ASSERT_EQ(tools::get_pruning_stripe(O + SS*2, 10000000, cryptonote::PRUNING_LOG_STRIPES), 3); + ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS-1, 10000000, cryptonote::PRUNING_LOG_STRIPES), NS); + ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS, 10000000, cryptonote::PRUNING_LOG_STRIPES), 1); } } TEST(pruning, match) { - static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + 1000; - static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS, "H must be >= CRYPTONOTE_PRUNING_TIP_BLOCKS"); - for (uint64_t h = 0; h < H - CRYPTONOTE_PRUNING_TIP_BLOCKS; ++h) + static constexpr uint64_t H = cryptonote::PRUNING_TIP_BLOCKS + 1000; + static_assert(H >= cryptonote::PRUNING_TIP_BLOCKS, "H must be >= cryptonote::PRUNING_TIP_BLOCKS"); + for (uint64_t h = 0; h < H - cryptonote::PRUNING_TIP_BLOCKS; ++h) { - uint32_t pruning_seed = tools::get_pruning_seed(h, H, CRYPTONOTE_PRUNING_LOG_STRIPES); + uint32_t pruning_seed = tools::get_pruning_seed(h, H, cryptonote::PRUNING_LOG_STRIPES); uint32_t pruning_stripe = tools::get_pruning_stripe(pruning_seed); - ASSERT_TRUE(pruning_stripe > 0 && pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES)); - for (uint32_t other_pruning_stripe = 1; other_pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++other_pruning_stripe) + ASSERT_TRUE(pruning_stripe > 0 && pruning_stripe <= (1 << cryptonote::PRUNING_LOG_STRIPES)); + for (uint32_t other_pruning_stripe = 1; other_pruning_stripe <= (1 << cryptonote::PRUNING_LOG_STRIPES); ++other_pruning_stripe) { - uint32_t other_pruning_seed = tools::make_pruning_seed(other_pruning_stripe, CRYPTONOTE_PRUNING_LOG_STRIPES); + uint32_t other_pruning_seed = tools::make_pruning_seed(other_pruning_stripe, cryptonote::PRUNING_LOG_STRIPES); ASSERT_TRUE(tools::has_unpruned_block(h, H, other_pruning_seed) == (other_pruning_seed == pruning_seed)); } } @@ -126,13 +126,13 @@ TEST(pruning, match) TEST(pruning, stripe_size) { - static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + CRYPTONOTE_PRUNING_STRIPE_SIZE * (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) + 1000; - static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS + CRYPTONOTE_PRUNING_STRIPE_SIZE * (1 << CRYPTONOTE_PRUNING_LOG_STRIPES), "H must be >= that stuff in front"); - for (uint32_t pruning_stripe = 1; pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++pruning_stripe) + static constexpr uint64_t H = cryptonote::PRUNING_TIP_BLOCKS + cryptonote::PRUNING_STRIPE_SIZE * (1 << cryptonote::PRUNING_LOG_STRIPES) + 1000; + static_assert(H >= cryptonote::PRUNING_TIP_BLOCKS + cryptonote::PRUNING_STRIPE_SIZE * (1 << cryptonote::PRUNING_LOG_STRIPES), "H must be >= that stuff in front"); + for (uint32_t pruning_stripe = 1; pruning_stripe <= (1 << cryptonote::PRUNING_LOG_STRIPES); ++pruning_stripe) { - uint32_t pruning_seed = tools::make_pruning_seed(pruning_stripe, CRYPTONOTE_PRUNING_LOG_STRIPES); + uint32_t pruning_seed = tools::make_pruning_seed(pruning_stripe, cryptonote::PRUNING_LOG_STRIPES); unsigned int current_run = 0, best_run = 0; - for (uint64_t h = 0; h < H - CRYPTONOTE_PRUNING_TIP_BLOCKS; ++h) + for (uint64_t h = 0; h < H - cryptonote::PRUNING_TIP_BLOCKS; ++h) { if (tools::has_unpruned_block(h, H, pruning_seed)) { @@ -140,31 +140,31 @@ TEST(pruning, stripe_size) } else if (current_run) { - ASSERT_EQ(current_run, CRYPTONOTE_PRUNING_STRIPE_SIZE); + ASSERT_EQ(current_run, cryptonote::PRUNING_STRIPE_SIZE); best_run = std::max(best_run, current_run); current_run = 0; } } - ASSERT_EQ(best_run, CRYPTONOTE_PRUNING_STRIPE_SIZE); + ASSERT_EQ(best_run, cryptonote::PRUNING_STRIPE_SIZE); } } TEST(pruning, next_unpruned) { - static_assert((1 << CRYPTONOTE_PRUNING_LOG_STRIPES) >= 4, "CRYPTONOTE_PRUNING_LOG_STRIPES too low"); + static_assert((1 << cryptonote::PRUNING_LOG_STRIPES) >= 4, "cryptonote::PRUNING_LOG_STRIPES too low"); - const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE; - const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES; + const uint64_t SS = cryptonote::PRUNING_STRIPE_SIZE; + const uint64_t NS = 1 << cryptonote::PRUNING_LOG_STRIPES; const uint64_t TB = NS * SS; for (uint64_t h = 0; h < 100; ++h) ASSERT_EQ(tools::get_next_unpruned_block_height(h, 10000000, 0), h); - const uint32_t seed1 = tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seed2 = tools::make_pruning_seed(2, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seed3 = tools::make_pruning_seed(3, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seed4 = tools::make_pruning_seed(4, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seedNS = tools::make_pruning_seed(NS, CRYPTONOTE_PRUNING_LOG_STRIPES); + const uint32_t seed1 = tools::make_pruning_seed(1, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seed2 = tools::make_pruning_seed(2, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seed3 = tools::make_pruning_seed(3, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seed4 = tools::make_pruning_seed(4, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seedNS = tools::make_pruning_seed(NS, cryptonote::PRUNING_LOG_STRIPES); ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed1), 0); ASSERT_EQ(tools::get_next_unpruned_block_height(1, 10000000, seed1), 1); @@ -201,20 +201,20 @@ TEST(pruning, next_unpruned) TEST(pruning, next_pruned) { - static_assert((1 << CRYPTONOTE_PRUNING_LOG_STRIPES) >= 4, "CRYPTONOTE_PRUNING_LOG_STRIPES too low"); + static_assert((1 << cryptonote::PRUNING_LOG_STRIPES) >= 4, "cryptonote::PRUNING_LOG_STRIPES too low"); - const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE; - const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES; + const uint64_t SS = cryptonote::PRUNING_STRIPE_SIZE; + const uint64_t NS = 1 << cryptonote::PRUNING_LOG_STRIPES; const uint64_t TB = NS * SS; - const uint32_t seed1 = tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seed2 = tools::make_pruning_seed(2, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seedNS_1 = tools::make_pruning_seed(NS-1, CRYPTONOTE_PRUNING_LOG_STRIPES); - const uint32_t seedNS = tools::make_pruning_seed(NS, CRYPTONOTE_PRUNING_LOG_STRIPES); + const uint32_t seed1 = tools::make_pruning_seed(1, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seed2 = tools::make_pruning_seed(2, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seedNS_1 = tools::make_pruning_seed(NS-1, cryptonote::PRUNING_LOG_STRIPES); + const uint32_t seedNS = tools::make_pruning_seed(NS, cryptonote::PRUNING_LOG_STRIPES); for (uint64_t h = 0; h < 100; ++h) ASSERT_EQ(tools::get_next_pruned_block_height(h, 10000000, 0), 10000000); - for (uint64_t h = 10000000 - 1 - CRYPTONOTE_PRUNING_TIP_BLOCKS; h < 10000000; ++h) + for (uint64_t h = 10000000 - 1 - cryptonote::PRUNING_TIP_BLOCKS; h < 10000000; ++h) ASSERT_EQ(tools::get_next_pruned_block_height(h, 10000000, 0), 10000000); ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seed1), SS); From f59a92a81c1e017c452a717f968fb97136355f2b Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 24 Mar 2025 11:55:37 +0530 Subject: [PATCH 003/182] Bump oxen-encoding to latest Resolves a gcc12+ compilation issue for a missing include. --- external/CMakeLists.txt | 2 +- external/oxen-encoding | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a1f33241456..8f0731d8305 100755 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -35,7 +35,7 @@ if(NOT STATIC AND NOT BUILD_STATIC_DEPS) find_package(PkgConfig REQUIRED) - pkg_check_modules(OXENC liboxenc>=1.0.1 IMPORTED_TARGET) + pkg_check_modules(OXENC liboxenc>=1.0.10 IMPORTED_TARGET) pkg_check_modules(OXENMQ liboxenmq>=1.2.3 IMPORTED_TARGET) endif() diff --git a/external/oxen-encoding b/external/oxen-encoding index 085bbff7741..9dd7fb39d49 160000 --- a/external/oxen-encoding +++ b/external/oxen-encoding @@ -1 +1 @@ -Subproject commit 085bbff774106dfd41c4848ef03e4de259accf4d +Subproject commit 9dd7fb39d49666c20f62c6b63399a711fe3ac7f7 From 633fd500720a613d024958658b20ece1b65a8968 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 24 Mar 2025 12:25:26 +0530 Subject: [PATCH 004/182] Add swarm as hex version of swarm_id in service node info --- src/rpc/core_rpc_server.cpp | 5 +++++ src/rpc/core_rpc_server_commands_defs.cpp | 2 ++ src/rpc/core_rpc_server_commands_defs.h | 2 ++ 3 files changed, 9 insertions(+) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index df17f8dc43b..ec41815c531 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/tx_extra.h" @@ -3142,6 +3143,10 @@ namespace cryptonote { namespace rpc { entry.portions_for_operator = info.portions_for_operator; entry.operator_address = cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*is_subaddress*/, info.operator_address); entry.swarm_id = info.swarm_id; + std::string raw; + raw.resize(sizeof(info.swarm_id)); + oxenc::write_host_as_big(info.swarm_id, raw.data()); + entry.swarm = oxenc::to_hex(raw); entry.registration_hf_version = info.registration_hf_version; } diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 7f9f5194e10..23f4172f0a2 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1106,6 +1106,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::requested_fields_t) KV_SERIALIZE(staking_requirement) KV_SERIALIZE(portions_for_operator) KV_SERIALIZE(swarm_id) + KV_SERIALIZE(swarm) KV_SERIALIZE(operator_address) KV_SERIALIZE(public_ip) KV_SERIALIZE(storage_port) @@ -1172,6 +1173,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::response::entry) KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(staking_requirement); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(portions_for_operator); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(swarm_id); + KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(swarm); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(operator_address); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(public_ip); KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_port); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 98b637b952e..b9c554a9b8b 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2044,6 +2044,7 @@ namespace rpc { bool staking_requirement; bool portions_for_operator; bool swarm_id; + bool swarm; bool operator_address; bool public_ip; bool storage_port; @@ -2113,6 +2114,7 @@ namespace rpc { uint64_t staking_requirement; // The staking requirement in atomic units that is required to be contributed to become a Master Node. uint64_t portions_for_operator; // The operator percentage cut to take from each reward expressed in portions, see cryptonote_config.h's STAKING_PORTIONS. uint64_t swarm_id; // The identifier of the Master Node's current swarm. + std::string swarm; // The identifier of the Master Node's current swarm, as a 16-character hex string. std::string operator_address; // The wallet address of the operator to which the operator cut of the staking reward is sent to. std::string public_ip; // The public ip address of the service node uint16_t storage_port; // The port number associated with the storage server From 9ee18235b8a67075ca2acabc664229fa8cdf6151 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 24 Mar 2025 17:21:25 +0530 Subject: [PATCH 005/182] boost 1.84 compatibility fix --- cmake/StaticBuild.cmake | 5 ----- contrib/epee/include/epee/storages/portable_storage_base.h | 1 + contrib/epee/src/wipeable_string.cpp | 1 + src/serialization/boost_std_variant.h | 5 +++++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 395ea4194db..c7e14581419 100755 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -479,11 +479,6 @@ endif() build_external(sodium) add_static_target(sodium sodium_external libsodium.a) - -if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) - set(zmq_patch PATCH_COMMAND patch -p1 -i ${PROJECT_SOURCE_DIR}/utils/build_scripts/libzmq-mingw-closesocket.patch) -endif() - set(zmq_cross_host "${cross_host}") if(IOS AND cross_host MATCHES "-ios$") # zmq doesn't like "-ios" for the host, so replace it with -darwin diff --git a/contrib/epee/include/epee/storages/portable_storage_base.h b/contrib/epee/include/epee/storages/portable_storage_base.h index deb0aae5097..df511f09551 100755 --- a/contrib/epee/include/epee/storages/portable_storage_base.h +++ b/contrib/epee/include/epee/storages/portable_storage_base.h @@ -33,6 +33,7 @@ #include #include #include +#include #include "../misc_log_ex.h" #include "../int-util.h" diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp index 7034db8a7f2..e69ba079b69 100755 --- a/contrib/epee/src/wipeable_string.cpp +++ b/contrib/epee/src/wipeable_string.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/serialization/boost_std_variant.h b/src/serialization/boost_std_variant.h index 13b95fbddd5..072cea03bb3 100755 --- a/src/serialization/boost_std_variant.h +++ b/src/serialization/boost_std_variant.h @@ -1,5 +1,8 @@ #pragma once +#include + +#if BOOST_VERSION < 108400 // Adapts boost::serialization to support std::variant, serializing just as a boost::variant would // be serialized (so that the serialized boost::variant and std::variant values are // interchangeable). @@ -50,3 +53,5 @@ inline void serialize(Archive& ar, std::variant& v, const unsigned int fil } } // namespace boost::serialization + +#endif From 547607d0ea8dc4f3dfeeb98c5c9a38c3cb0fa7f2 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 24 Mar 2025 17:44:38 +0530 Subject: [PATCH 006/182] Bump oxenmq to latest version --- external/loki-mq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/loki-mq b/external/loki-mq index b8bb10eac5b..5b8597d308c 160000 --- a/external/loki-mq +++ b/external/loki-mq @@ -1 +1 @@ -Subproject commit b8bb10eac5b45846f25f30fe6f26fd2bc671f9c2 +Subproject commit 5b8597d308cd2c5b1c092365ce2984bb72ea80b8 From cc5655ab9915a0eb1a58a8628d43e092e98508fc Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 24 Mar 2025 17:57:41 +0530 Subject: [PATCH 007/182] Bump Apple deployment target Targetting <10.13 will not work with std::get, which the updated boost code requires --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ca507de6fb..f7b665c8c2f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ cmake_minimum_required(VERSION 3.10) message(STATUS "CMake version ${CMAKE_VERSION}") # Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)") +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "macOS deployment target (Apple clang only)") project(beldex VERSION 6.0.0 From ca7bdfdc01c9cba28444c6882da9ab09a123a399 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 24 Mar 2025 18:01:04 +0530 Subject: [PATCH 008/182] Windows compilation fixes --- cmake/StaticBuild.cmake | 2 +- contrib/epee/include/epee/console_handler.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index c7e14581419..46a231fdffc 100755 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -587,7 +587,7 @@ endif() add_static_target(CURL::libcurl curl_external libcurl.a) set(libcurl_link_libs zlib) if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) - list(APPEND libcurl_link_libs ws2_32) + list(APPEND libcurl_link_libs ws2_32;bcrypt) elseif(APPLE) list(APPEND libcurl_link_libs "-framework SystemConfiguration") endif() diff --git a/contrib/epee/include/epee/console_handler.h b/contrib/epee/include/epee/console_handler.h index 0cab99621f6..50354d844c4 100755 --- a/contrib/epee/include/epee/console_handler.h +++ b/contrib/epee/include/epee/console_handler.h @@ -194,7 +194,7 @@ namespace epee if (m_read_status == state_cancelled) return false; - int retval = ::WaitForSingleObject(::GetStdHandle(STD_INPUT_HANDLE), 100); + auto retval = ::WaitForSingleObject(::GetStdHandle(STD_INPUT_HANDLE), 100); switch (retval) { case WAIT_FAILED: From 59a4339af36022ed8e19df9033f50f51f1210474 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 28 Mar 2025 14:25:56 +0530 Subject: [PATCH 009/182] Fix invalid mempool selection with conflicting BNS buys --- external/loki-mq | 2 +- src/cryptonote_core/tx_pool.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/external/loki-mq b/external/loki-mq index 5b8597d308c..923a0a80bc5 160000 --- a/external/loki-mq +++ b/external/loki-mq @@ -1 +1 @@ -Subproject commit 5b8597d308cd2c5b1c092365ce2984bb72ea80b8 +Subproject commit 923a0a80bc533e0d63ae32e2f9f5d4f7f53a258e diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 64234ac1ed1..8e3b2df38ca 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1826,6 +1826,11 @@ namespace cryptonote size_t const max_total_weight = 2 * median_weight - COINBASE_BLOB_RESERVED_SIZE; std::unordered_set k_images; + // Track BNS buys because we can't put more than one for the same BNS name into the same block + // (otherwise the *block* will fail but validation won't, because validation here won't see the + // earlier tx has having taken effect, but the block addition will). + std::unordered_set bns_buys; + LOG_PRINT_L2("Filling block template, median weight " << median_weight << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); LockedTXN lock(m_blockchain); @@ -1921,6 +1926,24 @@ namespace cryptonote LOG_PRINT_L2(" key images already seen"); continue; } + if (tx.type == txtype::beldex_name_system) { + // TX validation above has checked that this isn't an BNS buy for a name that is already + // registered, but it can't check that we don't create such a conflict from trying to + // put two conflicting registrations in the same block: when actually processing such a + // block the second one *would* be invalid because processing the first one created it. + // + // We only filter buys based on name_hash here which means technically we might + // over-filter (e.g. if there is both a bchat + wallet BNS) but that's not a big deal + // (one of the two will just get delayed for a block), and perfectly figuring out + // whether two might conflict is complicated enough that it's not worth doing here. + cryptonote::tx_extra_beldex_name_system bns; + if (cryptonote::get_field_from_tx_extra(tx.extra, bns) && bns.is_buying() && + !bns_buys.emplace(bns.name_hash).second) { + + LOG_PRINT_L2(" conflicting BNS buy in mempool"); + continue; + } + } bl.tx_hashes.push_back(sorted_it.second); total_weight += meta.weight; From 50568db8fb03d9fbbdc48043087788b281017e9e Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 28 Mar 2025 14:48:25 +0530 Subject: [PATCH 010/182] add missing oxenc dep --- src/common/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 803876f661d..de2da321c39 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -62,6 +62,7 @@ endif() target_link_libraries(common PUBLIC cncrypto + oxenc::oxenc oxenmq::oxenmq filesystem fmt::fmt From dcf1532e542d461a80d9111ac88174675acddddd Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 28 Mar 2025 16:08:20 +0530 Subject: [PATCH 011/182] Fix MN sort_and_filter being able to sort by shared_ptr contents --- src/cryptonote_core/master_node_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index 7aa4cd534a2..684760c3a3f 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -123,7 +123,7 @@ namespace master_nodes std::sort(result.begin(), result.end(), [](const pubkey_and_mninfo &a, const pubkey_and_mninfo &b) { - return memcmp(reinterpret_cast(&a), reinterpret_cast(&b), sizeof(a)) < 0; + return a.first < b.first; }); return result; } From 38c2e8fbc3f4b457be6f7692b703335317248c9a Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 28 Mar 2025 16:37:21 +0530 Subject: [PATCH 012/182] BNS check for nullptr --- src/cryptonote_core/beldex_name_system.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cryptonote_core/beldex_name_system.cpp b/src/cryptonote_core/beldex_name_system.cpp index 46a0dbc8b5b..d19e73fcfd3 100755 --- a/src/cryptonote_core/beldex_name_system.cpp +++ b/src/cryptonote_core/beldex_name_system.cpp @@ -2333,11 +2333,19 @@ bool name_system_db::save_settings(uint64_t top_height, crypto::hash const &top_ bool name_system_db::prune_db(uint64_t height) { - if (!bind_and_run(bns_sql_type::pruning, prune_mappings_sql, nullptr, height)) return false; - if (!sql_run_statement(bns_sql_type::pruning, prune_owners_sql, nullptr)) return false; + bool result = false; + if (db) { + if (bind_and_run(bns_sql_type::pruning, prune_mappings_sql, nullptr, height)) + if (sql_run_statement(bns_sql_type::pruning, prune_owners_sql, nullptr)) + result = true; - this->last_processed_height = (height - 1); - return true; + MDEBUG("Detach request for BNS (last processed is" << this->last_processed_height << "), " << (result ? "detached" : "failed to detach") << " to " << height); + + if (result) + this->last_processed_height = (height - 1); + } + + return result; } owner_record name_system_db::get_owner_by_key(bns::generic_owner const &owner) From ad2e124e68b4ae7e3752ba7eb716dfc73a79ff92 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 2 Apr 2025 14:27:21 +0530 Subject: [PATCH 013/182] Add wrapper around blockchain detach to detect errors --- src/cryptonote_core/blockchain.cpp | 70 +++++++++++++++++++----------- src/cryptonote_core/blockchain.h | 4 +- src/rpc/core_rpc_server.cpp | 2 +- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 4aba3c60b7d..3badb713cf2 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -405,6 +405,24 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() return true; } + +static bool exec_detach_hooks( + Blockchain& blockchain, + uint64_t detach_height, + std::vector hooks, // have to change + bool by_pop_blocks, + bool load_missing_blocks = true) { + + detached_info hook_data{detach_height, by_pop_blocks}; + for (const auto& hook : hooks) + hook(hook_data); + + bool result = true; + if (load_missing_blocks) + result = blockchain.load_missing_blocks_into_beldex_subsystems(); + return result; +} + //------------------------------------------------------------------ //FIXME: possibly move this into the constructor, to avoid accidentally // dereferencing a null BlockchainDB pointer @@ -566,10 +584,18 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett for (const auto& hook : m_init_hooks) hook(); - if (!m_db->is_read_only() && !load_missing_blocks_into_beldex_subsystems()) + if (!m_db->is_read_only()) { - MERROR("Failed to load blocks into beldex subsystems"); - return false; + if (!exec_detach_hooks( + *this, + m_db->height(), + m_blockchain_detached_hooks, + /*by_pop_blocks*/ false, + /*load_missing_blocks_into_beldex_subsystems*/ true)) + { + MERROR("Failed to load blocks into beldex subsystems"); + return false; + } } return true; @@ -680,11 +706,7 @@ void Blockchain::pop_blocks(uint64_t nblocks) return; } - detached_info hook_data{m_db->height(), /*by_pop_blocks=*/true}; - for (const auto& hook : m_blockchain_detached_hooks) - hook(hook_data); - load_missing_blocks_into_beldex_subsystems(); - + exec_detach_hooks(*this, m_db->height(), m_blockchain_detached_hooks, /*by_pop_blocks=*/true); if (stop_batch) m_db->batch_stop(); } @@ -998,11 +1020,7 @@ bool Blockchain::rollback_blockchain_switching(const std::listheight(); - detached_info split_hook_data{split_height, /*by_pop_blocks=*/false}; - for (const auto& hook : m_blockchain_detached_hooks) - hook(split_hook_data); - load_missing_blocks_into_beldex_subsystems(); + exec_detach_hooks(*this, split_height, m_blockchain_detached_hooks, /*by_pop_blocks=*/false); //connecting new alternative chain for(auto alt_ch_iter = alt_chain.begin(); alt_ch_iter != alt_chain.end(); alt_ch_iter++) @@ -4128,7 +4143,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block } // this is a cheap test - // HF19 TODO: remove the requirement that minor_version must be >= network version + // HF20 TODO: remove the requirement that minor_version must be >= network version if (auto v = get_network_version(blk_height); (v > hf::none) && (blk.major_version != v || (blk.major_version < hf::hf20_bulletproof_plusplus && blk.minor_version < static_cast(v)))) { LOG_PRINT_L1("Block with id: " << blk_hash << ", has invalid version " << static_cast(blk.major_version) << "." << +blk.minor_version << @@ -4170,8 +4185,11 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block } } - // HF19 TODO: remove the requirement that minor_version must be >= network version - if (required_major_version > hf::none && blk.major_version != required_major_version || (blk.major_version < hf::hf20_bulletproof_plusplus && blk.minor_version < static_cast(required_major_version))) + // HF20 TODO: remove the requirement that minor_version must be >= network version + if ((required_major_version > hf::none) && + (blk.major_version != required_major_version || + (blk.major_version < hf::hf20_bulletproof_plusplus && + blk.minor_version < static_cast(required_major_version)))) { MGINFO_RED("Block with id: " << blk_hash << ", has invalid version " << static_cast(blk.major_version) << "." << +blk.minor_version << "; current: " << static_cast(required_major_version) << "." << static_cast(required_major_version) << " for height " << blk_height); @@ -4452,9 +4470,12 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& auto abort_block = beldex::defer([&]() { pop_block_from_blockchain(); - detached_info hook_data{m_db->height(), false /*by_pop_blocks*/}; - for (const auto& hook : m_blockchain_detached_hooks) - hook(hook_data); + exec_detach_hooks( + *this, + m_db->height(), + m_blockchain_detached_hooks, + /*by_pop_blocks=*/false, + /*load_missing_blocks=*/false); }); // TODO(beldex): Not nice, making the hook take in a vector of pair key_images_container; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ec41815c531..e7d046edf2f 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2517,7 +2517,7 @@ namespace cryptonote { namespace rpc { if (!tools::hex_to_type(txid_hex, txid)) { if (!res.status.empty()) res.status += ", "; - res.status += "invalid transaction id: " + txid; + res.status += "invalid transaction id: " + txid_hex; continue; } cryptonote::blobdata txblob; From cb9c04a1f9bb3d8015e76a30b69d278c73f55d28 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 10 Apr 2025 19:11:47 +0530 Subject: [PATCH 014/182] Replace boost::endian conversion with oxenc --- .../epee/storages/portable_storage_from_bin.h | 5 +- .../epee/storages/portable_storage_to_bin.h | 9 ++- contrib/epee/src/CMakeLists.txt | 1 + src/blockchain_db/lmdb/db_lmdb.cpp | 63 +++++++++---------- src/common/util.h | 4 +- src/cryptonote_basic/cryptonote_basic.cpp | 1 + src/cryptonote_core/blockchain.cpp | 6 +- src/cryptonote_core/cryptonote_core.cpp | 1 - src/cryptonote_core/master_node_list.cpp | 6 +- .../master_node_quorum_cop.cpp | 4 +- src/cryptonote_core/master_node_rules.cpp | 4 +- src/device/device_ledger.cpp | 8 +-- src/device/io_ledger_tcp.cpp | 6 +- src/device_trezor/trezor/protocol.cpp | 1 - src/device_trezor/trezor/transport.cpp | 10 +-- src/net/socks.cpp | 11 ++-- src/p2p/net_node.cpp | 4 +- src/rpc/core_rpc_server.cpp | 1 - src/rpc/http_server.cpp | 1 - src/rpc/http_server_base.cpp | 3 +- src/serialization/binary_archive.h | 6 +- src/wallet/wallet2.cpp | 12 ++-- tests/unit_tests/epee_utils.cpp | 14 ++--- tests/unit_tests/net.cpp | 1 - 24 files changed, 87 insertions(+), 95 deletions(-) diff --git a/contrib/epee/include/epee/storages/portable_storage_from_bin.h b/contrib/epee/include/epee/storages/portable_storage_from_bin.h index 5d795a95ebd..26f39e663c2 100755 --- a/contrib/epee/include/epee/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/epee/storages/portable_storage_from_bin.h @@ -29,7 +29,8 @@ #pragma once #include "portable_storage_base.h" -#include +#include +#include namespace epee { @@ -110,7 +111,7 @@ namespace epee static_assert(std::is_integral_v); read(&v, sizeof(T)); if constexpr (sizeof(T) > 1) - boost::endian::little_to_native(v); + oxenc::little_to_host(v); } template diff --git a/contrib/epee/include/epee/storages/portable_storage_to_bin.h b/contrib/epee/include/epee/storages/portable_storage_to_bin.h index c37317f8aa0..2fbd396058e 100755 --- a/contrib/epee/include/epee/storages/portable_storage_to_bin.h +++ b/contrib/epee/include/epee/storages/portable_storage_to_bin.h @@ -30,7 +30,7 @@ #include "../pragma_comp_defs.h" #include "portable_storage_base.h" -#include +#include #include namespace epee @@ -71,16 +71,15 @@ namespace epee void pack_entry_to_buff(std::ostream& strm, T v) { if constexpr (sizeof(T) > 1) - boost::endian::native_to_little_inplace(v); + oxenc::host_to_little_inplace(v); strm.write(reinterpret_cast(&v), sizeof(v)); } inline void pack_entry_to_buff(std::ostream& strm, double v) { - static_assert(std::numeric_limits::is_iec559 && sizeof(double) == 8 && - (boost::endian::order::native == boost::endian::order::big || boost::endian::order::native == boost::endian::order::little)); + static_assert(std::numeric_limits::is_iec559 && sizeof(double) == 8 && (oxenc::little_endian || oxenc::big_endian)); char* buff = reinterpret_cast(&v); - if constexpr (boost::endian::order::native == boost::endian::order::big) { + if constexpr (oxenc::big_endian) { size_t i = 8; while (i) strm.put(buff[--i]); } else { diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 8c957dd32c5..7770bcc7896 100755 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -65,6 +65,7 @@ target_link_libraries(epee PUBLIC easylogging oxenmq::oxenmq + oxenc::oxenc PRIVATE filesystem Boost::thread diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 6b1477f3167..fb854026b54 100755 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include #include @@ -57,7 +57,6 @@ using namespace crypto; -using namespace boost::endian; enum struct lmdb_version { @@ -2413,14 +2412,14 @@ static_assert(sizeof(blob_header) == 8, "blob_header layout is unexpected, possi static blob_header write_little_endian_blob_header(blob_type type, uint32_t size) { blob_header result = {type, size}; - native_to_little_inplace(result.size); + oxenc::host_to_little_inplace(result.size); return result; } -static blob_header native_endian_blob_header(const blob_header *header) +static blob_header host_endian_blob_header(const blob_header *header) { blob_header result = {header->type, header->size}; - little_to_native_inplace(result.size); + oxenc::little_to_host_inplace(result.size); return result; } @@ -2439,7 +2438,7 @@ static bool read_alt_block_data_from_mdb_val(MDB_val const v, alt_block_data_t * src = reinterpret_cast(alt_data + 1); while (src < end) { - blob_header header = native_endian_blob_header(reinterpret_cast(src)); + blob_header header = host_endian_blob_header(reinterpret_cast(src)); src += sizeof(header); if (header.type == blob_type::block) { @@ -3977,8 +3976,8 @@ static bool convert_checkpoint_into_buffer(checkpoint_t const &checkpoint, check header.block_hash = checkpoint.block_hash; header.num_signatures = checkpoint.signatures.size(); - native_to_little_inplace(header.height); - native_to_little_inplace(header.num_signatures); + oxenc::host_to_little_inplace(header.height); + oxenc::host_to_little_inplace(header.num_signatures); size_t const bytes_for_signatures = sizeof(*checkpoint.signatures.data()) * checkpoint.signatures.size(); result.len = sizeof(header) + bytes_for_signatures; @@ -4061,8 +4060,8 @@ static checkpoint_t convert_mdb_val_to_checkpoint(MDB_val const value) auto const *signatures = reinterpret_cast(static_cast(value.mv_data) + sizeof(*header)); - auto num_sigs = little_to_native(header->num_signatures); - result.height = little_to_native(header->height); + auto num_sigs = oxenc::little_to_host(header->num_signatures); + result.height = oxenc::little_to_host(header->height); result.type = (num_sigs > 0) ? checkpoint_type::master_node : checkpoint_type::hardcoded; result.block_hash = header->block_hash; result.signatures.insert(result.signatures.end(), signatures, signatures + num_sigs); @@ -5939,12 +5938,12 @@ void BlockchainLMDB::migrate_5_6() // unexpected padding auto const *header = static_cast(val.mv_data); - auto num_sigs = little_to_native(header->num_signatures); + auto num_sigs = oxenc::little_to_host(header->num_signatures); auto const *aligned_signatures = reinterpret_cast(static_cast(val.mv_data) + sizeof(*header)); if (num_sigs == 0) continue; // NOTE: Hardcoded checkpoints checkpoint_t checkpoint = {}; - checkpoint.height = little_to_native(header->height); + checkpoint.height = oxenc::little_to_host(header->height); checkpoint.type = (num_sigs > 0) ? checkpoint_type::master_node : checkpoint_type::hardcoded; checkpoint.block_hash = header->block_hash; @@ -6160,15 +6159,15 @@ void BlockchainLMDB::clear_master_node_data() } template -C native_to_little_container(const C& c) { +C host_to_little_container(const C& c) { C result{c}; - for (auto& x : result) native_to_little_inplace(x); + for (auto& x : result) oxenc::host_to_little_inplace(x); return result; } template -C little_to_native_container(const C& c) { +C little_to_host_container(const C& c) { C result{c}; - for (auto& x : result) little_to_native_inplace(x); + for (auto& x : result) oxenc::little_to_host_inplace(x); return result; } @@ -6176,25 +6175,25 @@ struct master_node_proof_serialized_old { master_node_proof_serialized_old() = default; master_node_proof_serialized_old(const master_nodes::proof_info &info) - : timestamp{native_to_little(info.timestamp)}, - ip{native_to_little(info.proof->public_ip)}, - storage_https_port{native_to_little(info.proof->storage_https_port)}, - storage_omq_port{native_to_little(info.proof->storage_omq_port)}, - quorumnet_port{native_to_little(info.proof->qnet_port)}, - version{native_to_little_container(info.proof->version)}, + : timestamp{oxenc::host_to_little(info.timestamp)}, + ip{oxenc::host_to_little(info.proof->public_ip)}, + storage_https_port{oxenc::host_to_little(info.proof->storage_https_port)}, + storage_omq_port{oxenc::host_to_little(info.proof->storage_omq_port)}, + quorumnet_port{oxenc::host_to_little(info.proof->qnet_port)}, + version{host_to_little_container(info.proof->version)}, pubkey_ed25519{info.proof->pubkey_ed25519} {} void update(master_nodes::proof_info &info) const { - info.timestamp = little_to_native(timestamp); + info.timestamp = oxenc::little_to_host(timestamp); if (info.timestamp > info.effective_timestamp) info.effective_timestamp = info.timestamp; - info.proof->public_ip = little_to_native(ip); - info.proof->storage_https_port = little_to_native(storage_https_port); - info.proof->storage_omq_port = little_to_native(storage_omq_port); - info.proof->qnet_port = little_to_native(quorumnet_port); - info.proof->version = little_to_native_container(version); + info.proof->public_ip = oxenc::little_to_host(ip); + info.proof->storage_https_port = oxenc::little_to_host(storage_https_port); + info.proof->storage_omq_port = oxenc::little_to_host(storage_omq_port); + info.proof->qnet_port = oxenc::little_to_host(quorumnet_port); + info.proof->version = little_to_host_container(version); info.proof->storage_server_version = {0, 0, 0}; info.proof->belnet_version = {0, 0, 0}; info.update_pubkey(pubkey_ed25519); @@ -6221,8 +6220,8 @@ struct master_node_proof_serialized : master_node_proof_serialized_old { master_node_proof_serialized() = default; master_node_proof_serialized(const master_nodes::proof_info &info) : master_node_proof_serialized_old{info}, - storage_server_version{native_to_little_container(info.proof->storage_server_version)}, - belnet_version{native_to_little_container(info.proof->belnet_version)} + storage_server_version{host_to_little_container(info.proof->storage_server_version)}, + belnet_version{host_to_little_container(info.proof->belnet_version)} {} std::array storage_server_version{}; std::array belnet_version{}; @@ -6231,8 +6230,8 @@ struct master_node_proof_serialized : master_node_proof_serialized_old { void update(master_nodes::proof_info& info) const { if (!info.proof) info.proof = std::unique_ptr(new uptime_proof::Proof()); master_node_proof_serialized_old::update(info); - info.proof->storage_server_version = little_to_native_container(storage_server_version); - info.proof->belnet_version = little_to_native_container(belnet_version); + info.proof->storage_server_version = little_to_host_container(storage_server_version); + info.proof->belnet_version = little_to_host_container(belnet_version); } operator master_nodes::proof_info() const diff --git a/src/common/util.h b/src/common/util.h index 3b28013b7b3..c010b8d6072 100755 --- a/src/common/util.h +++ b/src/common/util.h @@ -30,7 +30,7 @@ #pragma once -#include +#include #include #include #include @@ -85,7 +85,7 @@ namespace tools // Copy an integer type, swapping to little-endian if needed template ::value, int> = 0> void memcpy_one(char*& dest, T t) { - boost::endian::native_to_little_inplace(t); + oxenc::host_to_little_inplace(t); std::memcpy(dest, &t, sizeof(T)); dest += sizeof(T); } diff --git a/src/cryptonote_basic/cryptonote_basic.cpp b/src/cryptonote_basic/cryptonote_basic.cpp index b8f8492ef8d..00e6e26ead0 100644 --- a/src/cryptonote_basic/cryptonote_basic.cpp +++ b/src/cryptonote_basic/cryptonote_basic.cpp @@ -1,4 +1,5 @@ #include "cryptonote_basic.h" +#include namespace cryptonote { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 3badb713cf2..400a4b5945d 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include "common/rules.h" #include "common/hex.h" @@ -5642,9 +5642,7 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get if (checkpoints.size() > 4) { - uint32_t nblocks; - std::memcpy(&nblocks, checkpoints.data(), 4); - boost::endian::little_to_native_inplace(nblocks); + auto nblocks = oxenc::load_little_to_host(checkpoints.data()); if (nblocks > (std::numeric_limits::max() - 4) / sizeof(hash)) { MERROR("Block hash data is too large"); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index e4bf0b69849..b61d0b373df 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -30,7 +30,6 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include -#include #include "epee/string_tools.h" diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index 684760c3a3f..c226c1aa788 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include extern "C" { #include @@ -1648,7 +1648,7 @@ namespace master_nodes { std::array src = {static_cast(type)}; std::memcpy(&src[1], &hash, sizeof(hash)); - for (uint32_t &val : src) boost::endian::little_to_native_inplace(val); + for (uint32_t &val : src) oxenc::little_to_host_inplace(val); std::seed_seq sequence(src.begin(), src.end()); result.seed(sequence); } @@ -1656,7 +1656,7 @@ namespace master_nodes { uint64_t seed = 0; std::memcpy(&seed, hash.data, sizeof(seed)); - boost::endian::little_to_native_inplace(seed); + oxenc::little_to_host_inplace(seed); seed += static_cast(type); result.seed(seed); } diff --git a/src/cryptonote_core/master_node_quorum_cop.cpp b/src/cryptonote_core/master_node_quorum_cop.cpp index 14f07730e70..f7b2cb1e0be 100755 --- a/src/cryptonote_core/master_node_quorum_cop.cpp +++ b/src/cryptonote_core/master_node_quorum_cop.cpp @@ -37,7 +37,7 @@ #include "common/beldex.h" #include "common/util.h" #include "epee/net/local_ip.h" -#include +#include #include "common/beldex_integration_test_hooks.h" @@ -858,7 +858,7 @@ namespace master_nodes std::memcpy(local.data(), pkdata + offset, prewrap); std::memcpy(local.data() + prewrap, pkdata, sizeof(uint64_t) - prewrap); } - sum += boost::endian::little_to_native(*reinterpret_cast(local.data())); + sum += oxenc::little_to_host(*reinterpret_cast(local.data())); ++offset; } return sum; diff --git a/src/cryptonote_core/master_node_rules.cpp b/src/cryptonote_core/master_node_rules.cpp index cf15e2a3712..13825cfff0d 100755 --- a/src/cryptonote_core/master_node_rules.cpp +++ b/src/cryptonote_core/master_node_rules.cpp @@ -1,7 +1,7 @@ #include "cryptonote_config.h" #include "common/beldex.h" #include "epee/int-util.h" -#include +#include #include #include #include @@ -48,7 +48,7 @@ crypto::hash generate_request_stake_unlock_hash(uint32_t nonce) { static_assert(sizeof(crypto::hash) == 8 * sizeof(uint32_t) && alignof(crypto::hash) >= alignof(uint32_t)); crypto::hash result; - boost::endian::native_to_little_inplace(nonce); + oxenc::host_to_little_inplace(nonce); for (size_t i = 0; i < 8; i++) reinterpret_cast(result.data)[i] = nonce; return result; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 65f6e59f9ed..7b8cdbda2d6 100755 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -39,7 +39,7 @@ #include "common/lock.h" #include "common/varint.h" #include -#include +#include #ifdef DEBUG_HWDEVICE #include @@ -433,19 +433,19 @@ } void device_ledger::send_u32(uint32_t x, int& offset) { - boost::endian::native_to_big_inplace(x); + oxenc::host_to_big_inplace(x); send_bytes(&x, 4, offset); } void device_ledger::send_u16(uint16_t x, int& offset) { - boost::endian::native_to_big_inplace(x); + oxenc::host_to_big_inplace(x); send_bytes(&x, 2, offset); } uint32_t device_ledger::receive_u32(int& offset) { uint32_t x; receive_bytes(&x, 4, offset); - boost::endian::big_to_native_inplace(x); + oxenc::big_to_host_inplace(x); return x; } uint32_t device_ledger::receive_u32() { diff --git a/src/device/io_ledger_tcp.cpp b/src/device/io_ledger_tcp.cpp index e768908a527..a521a63b874 100644 --- a/src/device/io_ledger_tcp.cpp +++ b/src/device/io_ledger_tcp.cpp @@ -1,7 +1,7 @@ #include "io_ledger_tcp.hpp" #include "common/beldex.h" #include -#include +#include #include #include #include "epee/misc_log_ex.h" @@ -168,7 +168,7 @@ int ledger_tcp::exchange(const unsigned char* command, unsigned int cmd_len, uns throw std::runtime_error{"Unable to exchange data with hardware wallet: not connected"}; // Sending: [SIZE][DATA], where SIZE is a uint32_t in network order - uint32_t size = boost::endian::native_to_big(cmd_len); + uint32_t size = oxenc::host_to_big(cmd_len); const unsigned char* size_bytes = reinterpret_cast(&size); full_write(*sockfd, size_bytes, 4); full_write(*sockfd, command, cmd_len); @@ -178,7 +178,7 @@ int ledger_tcp::exchange(const unsigned char* command, unsigned int cmd_len, uns // bytes of DATA are a 2-byte, u16 status code and... therefore not... included. Good job, Ledger // devs. full_read(*sockfd, reinterpret_cast(&size), 4); - auto data_size = boost::endian::big_to_native(size) + 2; + auto data_size = oxenc::big_to_host(size) + 2; if (data_size > max_resp_len) throw std::runtime_error{"Hardware wallet returned unexpectedly large response: got " + diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index 0d968fa469a..b33f1cc8c0e 100755 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index aae6419dcc9..81bdc4190f5 100755 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -35,7 +35,7 @@ #include #include #include -#include +#include #include #include #include @@ -170,8 +170,8 @@ namespace trezor{ } static void serialize_message_header(void * buff, uint16_t tag, uint32_t len){ - uint16_t wire_tag = boost::endian::native_to_big(static_cast(tag)); - uint32_t wire_len = boost::endian::native_to_big(static_cast(len)); + uint16_t wire_tag = oxenc::host_to_big(static_cast(tag)); + uint32_t wire_len = oxenc::host_to_big(static_cast(len)); memcpy(buff, (void *) &wire_tag, 2); memcpy((uint8_t*)buff + 2, (void *) &wire_len, 4); } @@ -182,8 +182,8 @@ namespace trezor{ memcpy(&wire_tag, buff, 2); memcpy(&wire_len, (uint8_t*)buff + 2, 4); - tag = boost::endian::big_to_native(wire_tag); - len = boost::endian::big_to_native(wire_len); + tag = oxenc::big_to_host(wire_tag); + len = oxenc::big_to_host(wire_len); } static void serialize_message(const google::protobuf::Message &req, size_t msg_size, uint8_t * buff, size_t buff_size) { diff --git a/src/net/socks.cpp b/src/net/socks.cpp index 5dac3385bf3..46179f503ac 100755 --- a/src/net/socks.cpp +++ b/src/net/socks.cpp @@ -32,8 +32,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -58,8 +57,8 @@ namespace socks { std::uint8_t version; std::uint8_t command_code; - boost::endian::big_uint16_t port; - boost::endian::big_uint32_t ip; + std::uint16_t port; + std::uint32_t ip; }; std::size_t write_domain_header(epee::span out, const std::uint8_t command, const std::uint16_t port, std::string_view domain) @@ -72,7 +71,7 @@ namespace socks return 0; // version 4, 1 indicates invalid ip for domain extension - const v4_header temp{4, command, port, std::uint32_t(1)}; + const v4_header temp{4, command, oxenc::host_to_little(port), oxenc::host_to_little(std::uint32_t{1})}; std::memcpy(out.data(), std::addressof(temp), sizeof(temp)); out.remove_prefix(sizeof(temp)); @@ -243,7 +242,7 @@ namespace socks static_assert(0 < sizeof(buffer_), "buffer size too small for null termination"); // version 4 - const v4_header temp{4, v4_connect_command, address.port(), boost::endian::big_to_native(address.ip())}; + const v4_header temp{4, v4_connect_command, oxenc::host_to_big(address.port()), oxenc::host_to_big(address.ip())}; std::memcpy(std::addressof(buffer_), std::addressof(temp), sizeof(temp)); buffer_[sizeof(temp)] = 0; buffer_size_ = sizeof(temp) + 1; diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index cb088ef696e..d9cc0902059 100755 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -28,7 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers -#include +#include #include #include #include @@ -220,7 +220,7 @@ namespace nodetool MERROR("Invalid ipv4:port given for --" << arg_tx_proxy.name); return std::nullopt; } - set_proxy.address = ip::tcp::endpoint{ip::address_v4{boost::endian::native_to_big(ip)}, port}; + set_proxy.address = ip::tcp::endpoint{ip::address_v4{oxenc::host_to_big(ip)}, port}; } return proxies; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e7d046edf2f..711c0222860 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index c332474782a..9fa72ea02c1 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include "common/command_line.h" #include "common/string_util.h" diff --git a/src/rpc/http_server_base.cpp b/src/rpc/http_server_base.cpp index d6a8f19aee4..129afb56679 100755 --- a/src/rpc/http_server_base.cpp +++ b/src/rpc/http_server_base.cpp @@ -2,6 +2,7 @@ #include "http_server_base.h" #include #include +#include #include "common/string_util.h" // epee: @@ -114,7 +115,7 @@ namespace cryptonote::rpc { // for example, localhost becomes `::1` instead of `0:0:0:0:0:0:0:1`). std::array a; std::memcpy(a.data(), addr.data(), 16); - for (auto& x : a) boost::endian::big_to_native_inplace(x); + for (auto& x : a) oxenc::big_to_host_inplace(x); size_t zero_start = 0, zero_end = 0; for (size_t i = 0, start = 0, end = 0; i < a.size(); i++) { diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 4ecc6181126..0b205114d3c 100755 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -41,7 +41,7 @@ #include #include #include -#include +#include #include "base.h" @@ -108,7 +108,7 @@ class binary_unarchiver : public deserializer { stream_.read(reinterpret_cast(&v), sizeof(T)); if constexpr (sizeof(T) > 1) - boost::endian::little_to_native_inplace(v); + oxenc::little_to_host_inplace(v); } /// Serializes binary data of a given size by reading it directly into the given buffer @@ -211,7 +211,7 @@ class binary_archiver : public serializer void serialize_int(T v) { if constexpr (sizeof(T) > 1) - boost::endian::native_to_little_inplace(v); + oxenc::host_to_little_inplace(v); stream_.write(reinterpret_cast(&v), sizeof(T)); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d5a63a3e449..c27510474ba 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "common/password.h" #include "common/string_util.h" #include "cryptonote_basic/tx_extra.h" @@ -13569,10 +13570,9 @@ bool wallet2::export_key_images_to_file(const fs::path& filename, bool requested std::pair>> ski = export_key_images(requested_only); const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; std::string data; - const uint32_t offset = boost::endian::native_to_little(ski.first); - data.reserve(sizeof(offset) + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); - data.resize(sizeof(offset)); - std::memcpy(&data[0], &offset, sizeof(offset)); + data.reserve(sizeof(uint32_t) + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key)); + data.resize(sizeof(uint32_t)); + oxenc::write_host_as_little(ski.first, data.data()); data += tools::view_guts(keys.m_spend_public_key); data += tools::view_guts(keys.m_view_public_key); for (const auto &i: ski.second) @@ -13670,9 +13670,7 @@ uint64_t wallet2::import_key_images_from_file(const fs::path& filename, uint64_t const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename.u8string()); - uint32_t offset; - std::memcpy(&offset, data.data(), sizeof(offset)); - boost::endian::little_to_native_inplace(offset); + uint32_t offset = oxenc::load_little_to_host(data.data()); THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); // Validate embedded spend/view public keys diff --git a/tests/unit_tests/epee_utils.cpp b/tests/unit_tests/epee_utils.cpp index 3387012ce2e..a64f85f1515 100755 --- a/tests/unit_tests/epee_utils.cpp +++ b/tests/unit_tests/epee_utils.cpp @@ -28,7 +28,7 @@ #include #include -#include +#include #include #include #include @@ -460,9 +460,9 @@ TEST(NetUtils, IPv4NetworkAddress) { static_assert(epee::net_utils::ipv4_network_address::get_type_id() == epee::net_utils::address_type::ipv4, "bad ipv4 type id"); - const auto ip1 = boost::endian::native_to_big(0x330012FFu); - const auto ip_loopback = boost::endian::native_to_big(0x7F000001u); - const auto ip_local = boost::endian::native_to_big(0x0A000000u); + const auto ip1 = oxenc::host_to_big(0x330012FFu); + const auto ip_loopback = oxenc::host_to_big(0x7F000001u); + const auto ip_local = oxenc::host_to_big(0x0A000000u); epee::net_utils::ipv4_network_address address1{ip1, 65535}; CHECK_EQUAL(address1, address1); @@ -527,9 +527,9 @@ TEST(NetUtils, IPv4NetworkAddress) TEST(NetUtils, NetworkAddress) { - const auto ip1 = boost::endian::native_to_big(0x330012FFu); - const auto ip_loopback = boost::endian::native_to_big(0x7F000001u); - const auto ip_local = boost::endian::native_to_big(0x0A000000u); + const auto ip1 = oxenc::host_to_big(0x330012FFu); + const auto ip_loopback = oxenc::host_to_big(0x7F000001u); + const auto ip_local = oxenc::host_to_big(0x0A000000u); struct custom_address { constexpr static bool equal(const custom_address&) noexcept { return false; } diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index c7aba2aa516..f8fbdce5225 100755 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include From 0767d9083101936ab4c8ca4b9bbf6886708af8e9 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 15 Apr 2025 09:23:00 +0530 Subject: [PATCH 015/182] Add nlohmann-json submodule --- .gitmodules | 3 +++ external/CMakeLists.txt | 4 ++++ external/nlohmann-json | 1 + src/cryptonote_core/CMakeLists.txt | 1 + src/rpc/CMakeLists.txt | 3 +++ 5 files changed, 12 insertions(+) create mode 160000 external/nlohmann-json diff --git a/.gitmodules b/.gitmodules index 06f0bcc6104..4f5f191779b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "external/oxen-encoding"] path = external/oxen-encoding url = https://github.com/oxen-io/oxen-encoding.git +[submodule "external/nlohmann-json"] + path = external/nlohmann-json + url = https://github.com/nlohmann/json.git diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 8f0731d8305..a681d868efd 100755 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -63,6 +63,10 @@ add_subdirectory(db_drivers) add_subdirectory(easylogging++ easyloggingpp) add_subdirectory(randomx EXCLUDE_FROM_ALL) add_subdirectory(fmt) + +set(JSON_MultipleHeaders ON CACHE BOOL "") # Allows multi-header nlohmann use +add_subdirectory(nlohmann-json) + # uSockets doesn't really have a proper build system (just a very simple Makefile) so build it # ourselves. if (NOT CMAKE_VERSION VERSION_LESS 3.12) diff --git a/external/nlohmann-json b/external/nlohmann-json new file mode 160000 index 00000000000..db78ac1d771 --- /dev/null +++ b/external/nlohmann-json @@ -0,0 +1 @@ +Subproject commit db78ac1d7716f56fc9f1b030b715f872f93964e4 diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index c4f892cccca..d4246df6a3f 100755 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(cryptonote_core device checkpoints sqlite3 + nlohmann_json::nlohmann_json PRIVATE Boost::program_options systemd diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index cefd00b8c6c..cc8717ec305 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -53,6 +53,7 @@ target_link_libraries(rpc_commands PUBLIC common cpr::cpr + nlohmann_json::nlohmann_json PRIVATE cryptonote_protocol extra) @@ -61,6 +62,7 @@ target_link_libraries(rpc_server_base PUBLIC common uWebSockets + nlohmann_json::nlohmann_json PRIVATE extra) @@ -88,5 +90,6 @@ target_link_libraries(rpc_http_client PUBLIC common cpr::cpr + nlohmann_json::nlohmann_json PRIVATE extra) From 9a59c31fb3859193922b50772766117d700e0f1b Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 16 Apr 2025 10:55:03 +0530 Subject: [PATCH 016/182] nlohmann submodule version update --- external/nlohmann-json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/nlohmann-json b/external/nlohmann-json index db78ac1d771..55f93686c01 160000 --- a/external/nlohmann-json +++ b/external/nlohmann-json @@ -1 +1 @@ -Subproject commit db78ac1d7716f56fc9f1b030b715f872f93964e4 +Subproject commit 55f93686c01528224f448c19128836e7df245f72 From c5512c9530970705cbf576aa6b63ff93bdede489 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 16 Apr 2025 12:34:00 +0530 Subject: [PATCH 017/182] Minor api improvement: return pair instead of two output params --- external/CMakeLists.txt | 2 +- src/common/string_util.cpp | 4 +-- src/cryptonote_core/cryptonote_core.cpp | 6 ++-- src/cryptonote_core/cryptonote_core.h | 5 ++-- .../cryptonote_protocol_handler.inl | 2 +- src/rpc/core_rpc_server.cpp | 28 ++++++++----------- 6 files changed, 22 insertions(+), 25 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a681d868efd..9a271534a5f 100755 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -36,7 +36,7 @@ if(NOT STATIC AND NOT BUILD_STATIC_DEPS) find_package(PkgConfig REQUIRED) pkg_check_modules(OXENC liboxenc>=1.0.10 IMPORTED_TARGET) - pkg_check_modules(OXENMQ liboxenmq>=1.2.3 IMPORTED_TARGET) + pkg_check_modules(OXENMQ liboxenmq>=1.2.7 IMPORTED_TARGET) endif() if(NOT OXENC_FOUND) diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index a5caf2b31bd..717de79b039 100755 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -97,8 +97,8 @@ std::string friendly_duration(std::chrono::nanoseconds dur) { dur %= 1min; some = true; } - if (some) { - // If we have >= minutes then don't bother with fractional seconds + if (some || dur == 0s) { + // If we have >= minutes or its exactly 0 seconds then don't bother with fractional seconds os << dur / 1s << 's'; } else { double seconds = std::chrono::duration(dur).count(); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index b61d0b373df..3b347cf7afa 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -431,9 +431,11 @@ namespace cryptonote return m_blockchain_storage.get_current_blockchain_height(); } //----------------------------------------------------------------------------------------------- - void core::get_blockchain_top(uint64_t& height, crypto::hash& top_id) const + std::pair core::get_blockchain_top() const { - top_id = m_blockchain_storage.get_tail_id(height); + std::pair result; + result.second = m_blockchain_storage.get_tail_id(result.first); + return result; } //----------------------------------------------------------------------------------------------- bool core::get_blocks(uint64_t start_offset, size_t count, std::vector>& blocks, std::vector& txs) const diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 85cdcf4f6e8..3f6b19172e3 100755 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -481,10 +481,9 @@ namespace cryptonote /** * @brief get the hash and height of the most recent block * - * @param height return-by-reference height of the block - * @param top_id return-by-reference hash of the block + * @return height and hash of the top block on the chain */ - void get_blockchain_top(uint64_t& height, crypto::hash& top_id) const; + std::pair get_blockchain_top() const; /** * @copydoc Blockchain::get_blocks(uint64_t, size_t, std::vector>&, std::vector&) const diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 7bcae0661d1..34e72a9ba94 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -554,7 +554,7 @@ namespace cryptonote template bool t_cryptonote_protocol_handler::get_payload_sync_data(CORE_SYNC_DATA& hshd) { - m_core.get_blockchain_top(hshd.current_height, hshd.top_id); + std::tie(hshd.current_height, hshd.top_id) = m_core.get_blockchain_top(); hshd.top_version = get_network_version(m_core.get_nettype(), hshd.current_height); hshd.cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height); hshd.current_height +=1; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 711c0222860..745fce34c80 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -331,8 +331,8 @@ namespace cryptonote { namespace rpc { if (use_bootstrap_daemon_if_necessary(req, res)) return res; - crypto::hash hash; - m_core.get_blockchain_top(res.height, hash); + auto [height, hash] = m_core.get_blockchain_top(); + res.height = height; ++res.height; // block height to chain height res.hash = tools::type_to_hex(hash); res.status = STATUS_OK; @@ -357,8 +357,8 @@ namespace cryptonote { namespace rpc { { if (context.admin) { - crypto::hash top_hash; - m_core.get_blockchain_top(res.height_without_bootstrap.emplace(), top_hash); + auto [height, top_hash] = m_core.get_blockchain_top(); + res.height_without_bootstrap = height; ++*res.height_without_bootstrap; // turn top block height into blockchain height res.was_bootstrap_ever_used = true; @@ -371,8 +371,8 @@ namespace cryptonote { namespace rpc { return res; } - crypto::hash top_hash; - m_core.get_blockchain_top(res.height, top_hash); + auto [top_height, top_hash] = m_core.get_blockchain_top(); + res.height = top_height; auto prev_ts = m_core.get_blockchain_storage().get_db().get_block_timestamp(res.height); ++res.height; // turn top block height into blockchain height res.top_block_hash = tools::type_to_hex(top_hash); @@ -517,12 +517,10 @@ namespace cryptonote { namespace rpc { if (req.no_miner_tx) res.output_indices.back().indices.push_back(GET_BLOCKS_FAST::tx_output_indices()); res.blocks.back().txs.reserve(bd.second.size()); - for (std::vector>::iterator i = bd.second.begin(); i != bd.second.end(); ++i) + for (auto& [txhash, txdata] : bd.second) { - res.blocks.back().txs.push_back({std::move(i->second), crypto::null_hash}); - i->second.clear(); - i->second.shrink_to_fit(); - size += res.blocks.back().txs.back().size(); + auto& entry = res.blocks.back().txs.emplace_back(std::move(txdata), crypto::null_hash); + size += entry.size(); } const size_t n_txes_to_lookup = bd.second.size() + (req.no_miner_tx ? 0 : 1); @@ -1938,9 +1936,7 @@ namespace cryptonote { namespace rpc { return res; CHECK_CORE_READY(); - uint64_t last_block_height; - crypto::hash last_block_hash; - m_core.get_blockchain_top(last_block_height, last_block_hash); + auto [last_block_height, last_block_hash] = m_core.get_blockchain_top(); block last_block; bool have_last_block = m_core.get_block_by_height(last_block_height, last_block); if (!have_last_block) @@ -2547,8 +2543,8 @@ namespace cryptonote { namespace rpc { PERF_TIMER(on_sync_info); - crypto::hash top_hash; - m_core.get_blockchain_top(res.height, top_hash); + auto [height, top_hash] = m_core.get_blockchain_top(); + res.height = height; ++res.height; // turn top block height into blockchain height res.target_height = m_core.get_target_blockchain_height(); res.next_needed_pruning_seed = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second; From be10014e300ee5727b1f424ea55f0cf9d15cb704 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 16 Apr 2025 22:37:29 +0530 Subject: [PATCH 018/182] Make RPC more flexible & add bt-encoding --- src/beldex_economy.h | 2 + src/cryptonote_core/beldex_name_system.h | 3 + src/daemon/command_parser_executor.cpp | 15 - src/daemon/command_parser_executor.h | 7 - src/daemon/command_server.cpp | 20 -- src/daemon/command_server.h | 16 +- src/daemon/daemon.cpp | 22 +- src/daemon/rpc_command_executor.cpp | 311 +++++++++------- src/daemon/rpc_command_executor.h | 63 +++- src/rpc/CMakeLists.txt | 1 + src/rpc/bootstrap_daemon.cpp | 5 + src/rpc/core_rpc_server.cpp | 394 ++++++++++---------- src/rpc/core_rpc_server.h | 61 ++-- src/rpc/core_rpc_server_command_parser.cpp | 233 ++++++++++++ src/rpc/core_rpc_server_command_parser.h | 14 + src/rpc/core_rpc_server_commands_defs.cpp | 157 ++++---- src/rpc/core_rpc_server_commands_defs.h | 396 ++++++++++++++------- src/rpc/http_client.cpp | 33 +- src/rpc/http_client.h | 20 ++ src/rpc/http_server.cpp | 61 ++-- src/rpc/http_server_base.cpp | 23 +- src/rpc/http_server_base.h | 3 +- src/rpc/lmq_server.cpp | 4 +- utils/lmq-rpc.py | 6 +- 24 files changed, 1198 insertions(+), 672 deletions(-) create mode 100644 src/rpc/core_rpc_server_command_parser.cpp create mode 100644 src/rpc/core_rpc_server_command_parser.h diff --git a/src/beldex_economy.h b/src/beldex_economy.h index da80a78730e..e666592c515 100755 --- a/src/beldex_economy.h +++ b/src/beldex_economy.h @@ -102,6 +102,8 @@ enum struct mapping_years : uint16_t update_record_internal, }; +constexpr bool is_belnet_type(mapping_type t) { return t >= mapping_type::belnet && t <= mapping_type::belnet_10years; } + constexpr bool is_renewal_type(mapping_years y) { return y >= mapping_years::bns_1year && y <= mapping_years::bns_10years; } // How many days we add per "year" of BNS belnet registration. We slightly extend this to the 368 diff --git a/src/cryptonote_core/beldex_name_system.h b/src/cryptonote_core/beldex_name_system.h index 32b5638e0cd..2200e0ae8b8 100755 --- a/src/cryptonote_core/beldex_name_system.h +++ b/src/cryptonote_core/beldex_name_system.h @@ -149,6 +149,9 @@ constexpr uint16_t db_mapping_type(bns::mapping_type type) { return static_cast(type); } constexpr std::string_view db_mapping_value(bns::mapping_type type) { + if(is_belnet_type(type)) + type = mapping_type::belnet; + switch(type) { case mapping_type::bchat: return "encrypted_bchat_value"sv; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 8e51adc3add..4aec97f1db3 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -40,14 +40,6 @@ namespace daemonize { -command_parser_executor::command_parser_executor(std::string daemon_url, const std::optional& login) - : m_executor{std::move(daemon_url), login} -{} - -command_parser_executor::command_parser_executor(cryptonote::rpc::core_rpc_server& rpc_server) - : m_executor{rpc_server} -{} - // Consumes an argument from the given list, if present, parsing it into `var`. // Returns false upon parse failure, true otherwise. template @@ -556,13 +548,6 @@ bool command_parser_executor::stop_daemon(const std::vector& args) return m_executor.stop_daemon(); } -bool command_parser_executor::print_status(const std::vector& args) -{ - if (!args.empty()) return false; - - return m_executor.print_status(); -} - bool command_parser_executor::set_limit(const std::vector& args) { if(args.size()>1) return false; diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index a7762b4c986..d7c6348f777 100755 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -42,11 +42,6 @@ class command_parser_executor final private: rpc_command_executor m_executor; public: - /// Invokes via remote RPC - command_parser_executor(std::string daemon_url, const std::optional& login); - - /// Invokes via local daemon - command_parser_executor(cryptonote::rpc::core_rpc_server& rpc_server); bool print_checkpoints(const std::vector& args); @@ -106,8 +101,6 @@ class command_parser_executor final bool stop_daemon(const std::vector& args); - bool print_status(const std::vector& args); - bool set_limit(const std::vector& args); bool set_limit_up(const std::vector& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 8e9d4369193..5e51b9c32eb 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -44,18 +44,6 @@ namespace daemonize { -command_server::command_server(std::string daemon_url, const std::optional& login) - : m_parser{std::move(daemon_url), login} -{ - init_commands(); -} - -command_server::command_server(cryptonote::rpc::core_rpc_server& rpc) - : m_is_rpc{false}, m_parser{rpc} -{ - init_commands(&rpc); -} - void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) { m_command_lookup.set_handler( @@ -227,11 +215,6 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) , [this](const auto &x) { return m_parser.stop_daemon(x); } , "Stop the daemon." ); - m_command_lookup.set_handler( - "print_status" - , [this](const auto &x) { return m_parser.print_status(x); } - , "Print the current daemon status." - ); m_command_lookup.set_handler( "limit" , [this](const auto &x) { return m_parser.set_limit(x); } @@ -451,7 +434,6 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) bool command_server::start_handling(std::function exit_handler) { - if (m_is_rpc) return false; #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) auto handle_pipe = [&]() @@ -490,8 +472,6 @@ bool command_server::start_handling(std::function exit_handler) void command_server::stop_handling() { - if (m_is_rpc) return; - m_command_lookup.stop_handling(); } diff --git a/src/daemon/command_server.h b/src/daemon/command_server.h index 2a9fa18efa7..67862630309 100755 --- a/src/daemon/command_server.h +++ b/src/daemon/command_server.h @@ -38,21 +38,23 @@ namespace daemonize { class command_server { private: - bool m_is_rpc{true}; command_parser_executor m_parser; epee::console_handlers_binder m_command_lookup; + std::optional m_omq; public: - /// Remote HTTP RPC constructor - command_server(std::string daemon_url, const std::optional& login); - - /// Non-remote constructor - command_server(cryptonote::rpc::core_rpc_server& rpc_server); + /// command_server constructor; forwards to command_parser_executor + template + command_server(T&&... args) + : m_parser{std::forward(args)...} + { + init_commands(); + } template bool process_command_and_log(T&&... args) { return m_command_lookup.process_command_and_log(std::forward(args)...); } - bool start_handling(std::function exit_handler = {}); + bool start_handling(std::function exit_handler = {}); void stop_handling(); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 6b4c31849ca..9761865fca5 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -49,11 +49,10 @@ #include "common/password.h" #include "common/signal_handler.h" -#include "daemon/command_server.h" -#include "daemon/command_line_args.h" #include "net/parse.h" #include "version.h" +#include "command_line_args.h" #include "command_server.h" #include "daemon.h" @@ -320,11 +319,25 @@ bool daemon::run(bool interactive) http_rpc_public->start(); } - std::unique_ptr rpc_commands; + std::optional rpc_commands; if (interactive) { MGINFO("Starting command-line processor"); - rpc_commands = std::make_unique(*rpc); + auto& omq = core->get_omq(); + + std::promise p; + auto conn = omq.connect_inproc( + [&p] (oxenmq::ConnectionID) { p.set_value(); }, + [&p] (oxenmq::ConnectionID, std::string_view err) { + try { + throw std::runtime_error{"Internal beldexd RPC connection failed: " + std::string{err}}; + } catch (...) { + p.set_exception(std::current_exception()); + } + }); + p.get_future().get(); + + rpc_commands.emplace(omq, std::move(conn)); rpc_commands->start_handling([this] { stop(); }); } @@ -341,6 +354,7 @@ bool daemon::run(bool interactive) { MGINFO("Stopping RPC command processor"); rpc_commands->stop_handling(); + rpc_commands.reset(); } if (http_rpc_public) { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 6248fc7062e..69289d9d2b4 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -29,6 +29,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "common/string_util.h" #include "epee/string_tools.h" #include "common/password.h" #include "common/scoped_message_writer.h" @@ -50,6 +51,7 @@ #include #include +#include #include #include #include @@ -232,13 +234,69 @@ namespace { } rpc_command_executor::rpc_command_executor( - std::string remote_url, - const std::optional& login - ) + std::string http_url, + const std::optional& login): m_rpc{std::in_place_type, http_url} { m_rpc_client.emplace(remote_url); if (login) - m_rpc_client->set_auth(login->username, std::string{login->password.password().view()}); + std::get(m_rpc).set_auth(login->username, std::string{login->password.password().view()}); +} + +rpc_command_executor::rpc_command_executor(oxenmq::OxenMQ& omq, oxenmq::ConnectionID conn) + : m_rpc{std::move(conn)}, m_omq{&omq} +{} + +template +static auto try_running(Callback code, std::string_view error_prefix) -> std::optional { + try { + return code(); + } catch (const std::exception& e) { + tools::fail_msg_writer() << error_prefix << ": " << e.what(); + return std::nullopt; + } +} + +nlohmann::json rpc_command_executor::invoke( + std::string_view method, + bool public_method, + std::optional params, + bool check_status_ok) { + + nlohmann::json result; + + if (auto* rpc_client = std::get_if(&m_rpc)) { + result = rpc_client->json_rpc(method, std::move(params)); + } else { + assert(m_omq); + auto conn = std::get(m_rpc); + auto endpoint = (public_method ? "rpc." : "admin.") + std::string{method}; + std::promise result_p; + m_omq->request(conn, endpoint, [&result_p](bool success, auto data) { + try { + if (!success) + throw std::runtime_error{"Request timed out"}; + if (data.size() >= 2 && data[0] == "200") + result_p.set_value(nlohmann::json::parse(data[1])); + else + throw std::runtime_error{"RPC method failed: " + ( + data.empty() ? "empty response" : + tools::join(" ", data))}; + } catch (...) { + result_p.set_exception(std::current_exception()); + } + }, + params ? params->dump() : "{}"); + + result = result_p.get_future().get(); + } + + if (check_status_ok) { + if (auto it = result.find("status"); + it == result.end() || it->get() != cryptonote::rpc::STATUS_OK) + throw std::runtime_error{"Received status " + (it == result.end() ? "(empty)" : it->get_ref()) + " != OK"}; + } + + return result; } bool rpc_command_executor::print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json) @@ -407,15 +465,19 @@ bool rpc_command_executor::hide_hash_rate() { } bool rpc_command_executor::show_difficulty() { - GET_INFO::response res{}; - if (!invoke({}, res, "Failed to get node info")) + auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + if (!maybe_info) return false; - - tools::success_msg_writer() << "BH: " << res.height - << ", TH: " << res.top_block_hash - << ", DIFF: " << res.difficulty - << ", CUM_DIFF: " << res.cumulative_difficulty - << ", HR: " << res.difficulty / res.target << " H/s"; + auto& info = *maybe_info; + auto msg = tools::success_msg_writer(); + msg << "HEIGHT: " << info["height"].get() + << ", HASH: " << info["top_block_hash"].get(); + if (info.value("POS", false)) + msg << ", POS"; + else + msg << ", DIFF: " << info["difficulty"].get() + << ", CUM_DIFF: " << info["cumulative_difficulty"].get() + << ", HR: " << info["difficulty"].get() / info["target"].get() << " H/s"; return true; } @@ -461,7 +523,11 @@ static float get_sync_percentage(const GET_INFO::response &ires) } bool rpc_command_executor::show_status() { - GET_INFO::response ires{}; + auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + if (!maybe_info) + return false; + auto& info = *maybe_info; + HARD_FORK_INFO::request hfreq{}; HARD_FORK_INFO::response hfres{}; MINING_STATUS::response mres{}; @@ -469,11 +535,13 @@ bool rpc_command_executor::show_status() { hfreq.version = 0; bool mining_busy = false; - if (!invoke({}, ires, "Failed to get node info") || - !invoke(std::move(hfreq), hfres, "Failed to retrieve hard fork info")) + if (!invoke(std::move(hfreq), hfres, "Failed to retrieve hard fork info")) return false; - if (ires.start_time) // This will only be non-null if we were recognized as admin (which we need for mining info) + + bool restricted_response = false; + if (auto it = info.find("start_time"); it != info.end() && it->get() > 0) // This will only be non-null if we were recognized as admin (which we need for mining info) { + restricted_response = true; has_mining_info = invoke({}, mres, "Failed to retrieve mining info", false); if (has_mining_info) { if (mres.status == STATUS_BUSY) @@ -490,7 +558,7 @@ bool rpc_command_executor::show_status() { uint64_t my_mn_last_uptime = 0; bool my_mn_registered = false, my_mn_staked = false, my_mn_active = false; uint16_t my_reason_all = 0, my_reason_any = 0; - if (ires.master_node && *ires.master_node) { + if (info["master_node"].get()) { GET_MASTER_KEYS::response res{}; if (!invoke({}, res, "Failed to retrieve master node keys")) @@ -514,25 +582,29 @@ bool rpc_command_executor::show_status() { } } - uint64_t net_height = ires.target_height > ires.height ? ires.target_height : ires.height; + uint64_t height = info["height"].get(); + uint64_t net_height = std::max(info["target_height"].get(), height); std::string bootstrap_msg; std::ostringstream str; - str << "Height: " << ires.height; - if (ires.height != net_height) - str << "/" << net_height << " (" << fmt::format("{:.1f}%)", get_sync_percentage(ires)); + str << "Height: " << height; + if (height != net_height) + str << fmt::format("/{} ({:.1f}%)", net_height, get_sync_percentage(height, net_height)); - if (ires.testnet) str << " ON TESTNET"; - else if (ires.devnet) str << " ON DEVNET"; + auto net = info["nettype"].get(); + if (net == "testnet") str << " ON TESTNET"; + else if (net == "devnet") str << " ON DEVNET"; - if (ires.height < ires.target_height) + if (ires.height < net_height) str << ", syncing"; - if (ires.was_bootstrap_ever_used && *ires.was_bootstrap_ever_used && ires.bootstrap_daemon_address) + if (info.value("was_bootstrap_ever_used", false)) { - str << ", bootstrap " << *ires.bootstrap_daemon_address; - if (ires.untrusted) - str << fmt::format(", local height: {} ({:.1f}%)", *ires.height_without_bootstrap, get_sync_percentage(*ires.height_without_bootstrap, net_height)); + str << ", bootstrap " << info["bootstrap_daemon_address"].get(); + if (info.value("untrusted", false)){ + auto hwb = info["height_without_bootstrap"].get(); + str << fmt::format(", local height: {} ({:.1f}%)", hwb, get_sync_percentage(hwb, net_height)); + } else str << " was used"; } @@ -543,25 +615,22 @@ bool rpc_command_executor::show_status() { str << ", mining at " << get_mining_speed(mres.speed); if (hfres.version < cryptonote::feature::POS) - str << ", net hash " << get_mining_speed(ires.difficulty / ires.target); + str << ", net hash " << get_mining_speed(info["difficulty"].get() / info["target"].get()); - str << ", v" << (ires.version.empty() ? "?.?.?" : ires.version); + str << ", v" << info["version"].get(); str << "(net v" << static_cast(hfres.version) << ')'; if (hfres.earliest_height) - print_fork_extra_info(str, *hfres.earliest_height, net_height, ires.target); + print_fork_extra_info(str, *hfres.earliest_height, net_height, 1s * info["target"].get()); std::time_t now = std::time(nullptr); // restricted RPC does not disclose these: - if (ires.outgoing_connections_count && ires.incoming_connections_count && ires.start_time) + if (restricted_response) { - std::time_t uptime = now - *ires.start_time; - str << ", " << *ires.outgoing_connections_count << "(out)+" << *ires.incoming_connections_count << "(in) connections" + std::chrono::seconds uptime{now - info["start_time"].get()}; + str << ", " << info["outgoing_connections_count"].get() << "(out)+" << info["incoming_connections_count"].get() << "(in) connections" << ", uptime " - << (uptime / (24*60*60)) << 'd' - << (uptime / (60*60)) % 24 << 'h' - << (uptime / 60) % 60 << 'm' - << uptime % 60 << 's'; + << tools::friendly_duration(uptime); } tools::success_msg_writer() << str.str(); @@ -575,14 +644,14 @@ bool rpc_command_executor::show_status() { str << (!my_mn_staked ? "awaiting" : my_mn_active ? "active" : "DECOMMISSIONED (" + std::to_string(my_decomm_remaining) + " blocks credit)") << ", proof: " << (my_mn_last_uptime ? get_human_time_ago(my_mn_last_uptime, now) : "(never)"); str << ", last pings: "; - if (*ires.last_storage_server_ping > 0) - str << get_human_time_ago(*ires.last_storage_server_ping, now, true /*abbreviate*/); + if (auto last_ss_ping = info["last_storage_server_ping"].get(); last_ss_ping > 0) + str << get_human_time_ago(last_ss_ping, now, true /*abbreviate*/); else str << "NOT RECEIVED"; str << " (storage), "; - if (*ires.last_belnet_ping > 0) - str << get_human_time_ago(*ires.last_belnet_ping, now, true /*abbreviate*/); + if (auto last_belnet_ping = info["last_belnet_ping"].get(); last_belnet_ping > 0) + str << get_human_time_ago(last_belnet_ping, now, true /*abbreviate*/); else str << "NOT RECEIVED"; str << " (belnet)"; @@ -735,18 +804,19 @@ bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint // negative: relative to the end if (start_block_index < 0) { - GET_INFO::response ires; - if (!invoke(GET_INFO::request{}, ires, "Failed to query daemon info")) - return false; + auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + if (!maybe_info) + return false; + auto& info = *maybe_info; - if (start_block_index < 0 && (uint64_t)-start_block_index >= ires.height) + if (start_block_index < 0 && -start_block_index >= info["height"].get()) { tools::fail_msg_writer() << "start offset is larger than blockchain height"; return false; } - start_block_index = ires.height + start_block_index; - end_block_index = start_block_index + end_block_index - 1; + start_block_index += info["height"].get(); + end_block_index += start_block_index - 1; } req.start_height = start_block_index; @@ -823,14 +893,13 @@ bool rpc_command_executor::set_log_categories(std::string categories) { } bool rpc_command_executor::print_height() { - GET_HEIGHT::response res{}; - - if (!invoke({}, res, "Failed to retrieve height")) - return false; - - tools::success_msg_writer() << res.height; - - return true; + if (auto height = try_running([this] { + return invoke(std::nullopt).at("height").get(); + }, "Failed to retrieve height")) { + tools::success_msg_writer() << *height; + return true; + } + return false; } bool rpc_command_executor::print_block(GET_BLOCK::request&& req, bool include_hex) { @@ -1039,10 +1108,13 @@ bool rpc_command_executor::print_transaction_pool_short() { bool rpc_command_executor::print_transaction_pool_stats() { GET_TRANSACTION_POOL_STATS::response res{}; - GET_INFO::response ires{}; + auto full_reward_zone = try_running([this] { + return invoke(std::nullopt).at("block_size_limit").get() / 2; + }, "Failed to retrieve node info"); + if (!full_reward_zone) + return false; - if (!invoke({}, res, "Failed to retreive transaction pool statistics") || - !invoke({}, ires, "Failed to retrieve node info")) + if (!invoke({}, res, "Failed to retreive transaction pool statistics")) return false; size_t n_transactions = res.pool_stats.txs_total; @@ -1050,14 +1122,13 @@ bool rpc_command_executor::print_transaction_pool_stats() { size_t avg_bytes = n_transactions ? res.pool_stats.bytes_total / n_transactions : 0; std::string backlog_message; - const uint64_t full_reward_zone = ires.block_weight_limit / 2; - if (res.pool_stats.bytes_total <= full_reward_zone) + if (res.pool_stats.bytes_total <= *full_reward_zone) { backlog_message = "no backlog"; } else { - uint64_t backlog = (res.pool_stats.bytes_total + full_reward_zone - 1) / full_reward_zone; + uint64_t backlog = (res.pool_stats.bytes_total + *full_reward_zone - 1) / *full_reward_zone; backlog_message = fmt::format("estimated {} block ({} minutes ) backlog", backlog, backlog * cryptonote::TARGET_BLOCK_TIME / 1min); } @@ -1138,23 +1209,6 @@ bool rpc_command_executor::stop_daemon() return true; } -bool rpc_command_executor::print_status() -{ - if (!m_rpc_client) - { - tools::fail_msg_writer() << "print_status makes no sense in interactive mode"; - return false; - } - - // Make a request to get_height because it is public and relatively simple - GET_HEIGHT::response res; - if (invoke({}, res, "beldexd is NOT running")) { - tools::success_msg_writer() << "beldexd is running (height: " << res.height << ")"; - return true; - } - return false; -} - bool rpc_command_executor::get_limit(bool up, bool down) { GET_LIMIT::response res{}; @@ -1330,11 +1384,15 @@ bool rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t count bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, uint64_t last_blocks) { - GET_INFO::response ires{}; + auto height = try_running([this] { + return invoke(std::nullopt).at("height").get(); + }, "Failed to retrieve node info"); + if (!height) + return false; + GET_ALTERNATE_CHAINS::response res{}; - if (!invoke({}, ires, "Failed to retrieve node info") || - !invoke({}, res, "Failed to retrieve alt chain data")) + if (!invoke({}, res, "Failed to retrieve alt chain data")) return false; if (tip.empty()) @@ -1348,7 +1406,7 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, if (chain.length <= above) continue; const uint64_t start_height = (chain.height - chain.length + 1); - if (last_blocks > 0 && ires.height - 1 - start_height >= last_blocks) + if (last_blocks > 0 && *height - 1 - start_height >= last_blocks) continue; display.push_back(i); } @@ -1357,7 +1415,7 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, { const auto &chain = chains[idx]; const uint64_t start_height = (chain.height - chain.length + 1); - tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) + tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (*height - start_height - 1) << " deep), diff " << chain.difficulty << ": " << chain.block_hash; } } @@ -1370,7 +1428,7 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, const auto &chain = *i; tools::success_msg_writer() << "Found alternate chain with tip " << tip; uint64_t start_height = (chain.height - chain.length + 1); - tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) + tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (*height - start_height - 1) << " deep), diff " << chain.difficulty << ":"; for (const std::string &block_id: chain.block_hashes) tools::msg_writer() << " " << block_id; @@ -1415,29 +1473,32 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) { - GET_INFO::response ires{}; + auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + if (!maybe_info) + return false; + auto& info = *maybe_info; + GET_BASE_FEE_ESTIMATE::response feres{}; HARD_FORK_INFO::response hfres{}; - if (!invoke({}, ires, "Failed to retrieve node info") || - !invoke({}, feres, "Failed to retrieve current fee info") || + if (!invoke({}, feres, "Failed to retrieve current fee info") || !invoke({static_cast(cryptonote::feature::PER_BYTE_FEE)}, hfres, "Failed to retrieve hard fork info")) return false; - tools::msg_writer() << "Height: " << ires.height << ", diff " << ires.difficulty << ", cum. diff " << ires.cumulative_difficulty - << ", target " << ires.target << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee_per_byte) << "/" << (hfres.enabled ? "byte" : "kB") - << " + " << cryptonote::print_money(feres.fee_per_output) << "/out"; + auto height = info["height"].get(); + tools::msg_writer() << "Height: " << height << ", diff " << info["difficulty"].get() << ", cum. diff " << info["cumulative_difficulty"].get() + << ", target " << info["target"].get() << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee_per_byte) << "/" << (hfres.enabled ? "byte" : "kB") if (nblocks > 0) { - if (nblocks > ires.height) - nblocks = ires.height; + if (nblocks > height) + nblocks = height; GET_BLOCK_HEADERS_RANGE::request bhreq{}; GET_BLOCK_HEADERS_RANGE::response bhres{}; - bhreq.start_height = ires.height - nblocks; - bhreq.end_height = ires.height - 1; + bhreq.start_height = height - nblocks; + bhreq.end_height = height - 1; bhreq.fill_pow_hash = false; if (!invoke(std::move(bhreq), bhres, "Failed to retrieve block headers")) return false; @@ -1796,18 +1857,20 @@ bool rpc_command_executor::print_mn(const std::vector &args) req.master_node_pubkeys.push_back(arg); } - GET_INFO::response get_info_res{}; + auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + if (!maybe_info) + return false; + auto& info = *maybe_info; - if (!invoke({}, get_info_res, "Failed to retrieve node info") || - !invoke(std::move(req), res, "Failed to retrieve master node data")) + if (!invoke(std::move(req), res, "Failed to retrieve master node data")) return false; cryptonote::network_type nettype = - get_info_res.mainnet ? cryptonote::network_type::MAINNET : - get_info_res.devnet ? cryptonote::network_type::DEVNET : - get_info_res.testnet ? cryptonote::network_type::TESTNET : + info.value("mainnet", false) ? cryptonote::network_type::MAINNET : + info.value("devnet", false) ? cryptonote::network_type::DEVNET : + info.value("testnet", false) ? cryptonote::network_type::TESTNET : cryptonote::network_type::UNDEFINED; - uint64_t curr_height = get_info_res.height; + uint64_t curr_height = info["height"].get(); std::vector unregistered; std::vector registered; @@ -1983,45 +2046,47 @@ bool rpc_command_executor::prepare_registration(bool force_registration) auto scoped_log_cats = std::unique_ptr(new clear_log_categories()); // Check if the daemon was started in master Node or not - GET_INFO::response res{}; + auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + if (!maybe_info) + return false; + auto& info = *maybe_info; + GET_MASTER_KEYS::response kres{}; HARD_FORK_INFO::response hf_res{}; - if (!invoke({}, res, "Failed to get node info") || - !invoke({}, hf_res, "Failed to retrieve hard fork info") || + if (!invoke({}, hf_res, "Failed to retrieve hard fork info") || !invoke({}, kres, "Failed to retrieve master node keys")) return false; - if (!res.master_node) + if (!info.value("master_node", false)) { tools::fail_msg_writer() << "Unable to prepare registration: this daemon is not running in --master-node mode"; return false; } - else if (auto last_belnet_ping = static_cast(res.last_belnet_ping.value_or(0)); + else if (auto last_belnet_ping = info.value("last_belnet_ping", 0); last_belnet_ping < (time(nullptr) - 60) && !force_registration) { tools::fail_msg_writer() << "Unable to prepare registration: this daemon has not received a ping from belnet " - << (res.last_belnet_ping == 0 ? "yet" : "since " + get_human_time_ago(last_belnet_ping, std::time(nullptr))); + << (last_belnet_ping == 0 ? "yet" : "since " + get_human_time_ago(last_belnet_ping, std::time(nullptr))); return false; } - else if (auto last_storage_server_ping = static_cast(res.last_storage_server_ping.value_or(0)); + else if (auto last_storage_server_ping = info.value("last_storage_server_ping", 0); last_storage_server_ping < (time(nullptr) - 60) && !force_registration) { tools::fail_msg_writer() << "Unable to prepare registration: this daemon has not received a ping from the storage server " - << (res.last_storage_server_ping == 0 ? "yet" : "since " + get_human_time_ago(last_storage_server_ping, std::time(nullptr))); + << (last_storage_server_ping == 0 ? "yet" : "since " + get_human_time_ago(last_storage_server_ping, std::time(nullptr))); return false; } - uint64_t block_height = std::max(res.height, res.target_height); + uint64_t block_height = std::max(info["height"].get(), info["target_height"].get()); auto hf_version = hf_res.version; #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) cryptonote::network_type const nettype = cryptonote::FAKECHAIN; #else - cryptonote::network_type const nettype = - res.mainnet ? cryptonote::network_type::MAINNET : - res.devnet ? cryptonote::network_type::DEVNET : - res.testnet ? cryptonote::network_type::TESTNET : - res.nettype == "fakechain" ? cryptonote::network_type::FAKECHAIN : - cryptonote::network_type::UNDEFINED; + cryptonote::network_type nettype = + info.value("mainnet", false) ? cryptonote::network_type::MAINNET : + info.value("devnet", false) ? cryptonote::network_type::DEVNET : + info.value("testnet", false) ? cryptonote::network_type::TESTNET : + cryptonote::UNDEFINED; #endif // Query the latest block we've synced and check that the timestamp is sensible, issue a warning if not @@ -2555,11 +2620,13 @@ bool rpc_command_executor::set_bootstrap_daemon( bool rpc_command_executor::version() { - GET_INFO::response response{}; - if (!invoke(GET_INFO::request{}, response, "Failed to query daemon info")) - return false; - tools::success_msg_writer() << response.version; - return true; + auto version = try_running([this] { + return invoke(std::nullopt).at("version").get(); + }, "Failed to retrieve node info"); + if (!version) + return false; + tools::success_msg_writer() << *version; + return true; } bool rpc_command_executor::test_trigger_uptime_proof() diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 648ca2a316d..6b2f9a1baa6 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -39,10 +39,12 @@ #pragma once +#include #include #include "common/common_fwd.h" #include "common/scoped_message_writer.h" +#include "rpc/core_rpc_server_commands_defs.h" #include "rpc/http_client.h" #include "cryptonote_basic/cryptonote_basic.h" #include "rpc/core_rpc_server.h" @@ -54,36 +56,35 @@ namespace daemonize { class rpc_command_executor final { private: - std::optional m_rpc_client; - cryptonote::rpc::core_rpc_server* m_rpc_server = nullptr; - const cryptonote::rpc::rpc_context m_server_context{true}; + std::variant m_rpc; + oxenmq::OxenMQ* m_omq = nullptr; public: - /// Executor for remote connection RPC + /// Executor for HTTP remote connection RPC rpc_command_executor( - std::string remote_url, + std::string http_url, const std::optional& user ); - /// Executor for local daemon RPC - rpc_command_executor(cryptonote::rpc::core_rpc_server& rpc_server) - : m_rpc_server{&rpc_server} {} + /// Executor for OMQ RPC, either local (inproc) or remote. + rpc_command_executor(oxenmq::OxenMQ& omq, oxenmq::ConnectionID conn); + /// FIXME: remove this! + /// /// Runs some RPC command either via json_rpc or a direct core rpc call. /// /// @param req the request object (rvalue reference) - /// @param res the response object (lvalue reference) /// @param error print this (and, on exception, the exception message) on failure. If empty then /// nothing is printed on failure. /// @param check_status_ok whether we require res.status == STATUS_OK to consider the request /// successful - template + template && cryptonote::rpc::FIXME_has_nested_response_v, int> = 0> bool invoke(typename RPC::request&& req, typename RPC::response& res, const std::string& error, bool check_status_ok = true) { try { - if (m_rpc_client) { - res = m_rpc_client->json_rpc(RPC::names()[0], req); + if (auto* rpc_client = std::get_if(&m_rpc)) { + res = rpc_client->json_rpc(RPC::names()[0], req); } else { - res = m_rpc_server->invoke(std::move(req), m_server_context); + throw std::runtime_error{"fixme"}; } if (!check_status_ok || res.status == cryptonote::rpc::STATUS_OK) return true; @@ -97,6 +98,40 @@ class rpc_command_executor final { return false; } + /// Runs some RPC command either via json_rpc or an internal rpc call. Returns nlohmann::json + /// results on success, throws on failure. + /// + /// Note that for a json_rpc request this is the "result" value inside the json_rpc wrapper, not + /// the wrapper itself. + /// + /// This is the low-level implementing method for `invoke(...)`. + /// + /// @param method the method name, typically `SOMERPC::names()[0]` + /// @param public_method true if this is a public rpc request; this is used, in particular, to + /// decide whether "rpc." or "admin." should be prefixed if this goes through OMQ RPC. + /// @param params the "params" field for the request. Can be nullopt to pass no "params". + /// @param check_status_ok whether we require the result to have a "status" key set to STATUS_OK + /// to consider the request successful. Note that this defaults to *false* if this is called + /// directly, unlike the RPC-type-templated version, below. + nlohmann::json invoke( + std::string_view method, + bool public_method, + std::optional params, + bool check_status_ok = false); + + /// Runs some RPC command either via json_rpc or an internal rpc call. Returns nlohmann::json + /// results on success, throws on failure. + /// + /// @tparam RPC the rpc type class + /// @param params the "params" value to pass to json_rpc, or std::nullopt to omit it + /// @param check_status_ok whether we require the result to have a "status" key set to STATUS_OK + /// to consider the request successful + template && !cryptonote::rpc::FIXME_has_nested_response_v, int> = 0> + nlohmann::json invoke(std::optional params, bool check_status_ok = true) + { + return invoke(RPC::names()[0], std::is_base_of_v, std::move(params), check_status_ok); + } + bool print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json); bool print_mn_state_changes(uint64_t start_height, uint64_t end_height); @@ -153,8 +188,6 @@ class rpc_command_executor final { bool stop_daemon(); - bool print_status(); - bool get_limit(bool up = true, bool down = true); bool set_limit(int64_t limit_down, int64_t limit_up); diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index cc8717ec305..0595966753d 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -29,6 +29,7 @@ add_library(rpc_commands core_rpc_server_commands_defs.cpp + core_rpc_server_command_parser.cpp ) add_library(rpc_server_base diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index 8b7755d4b5b..ae03ab39d1c 100755 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -34,6 +34,10 @@ namespace cryptonote std::optional bootstrap_daemon::get_height() { + // FIXME + throw std::runtime_error{"FIXME"}; + + /* // query bootstrap daemon's height rpc::GET_HEIGHT::response res{}; if (!invoke({}, res)) @@ -47,6 +51,7 @@ namespace cryptonote } return res.height; + */ } bool bootstrap_daemon::set_server(std::string url, const std::optional> &credentials /* = std::nullopt */) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 745fce34c80..a17aab05069 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -39,6 +39,7 @@ #include #include #include +#include "common/string_util.h" #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/tx_extra.h" @@ -48,6 +49,9 @@ #include "beldex_economy.h" #include "epee/string_tools.h" #include "core_rpc_server.h" +#include "core_rpc_server_command_parser.h" +#include "rpc_args.h" +#include "core_rpc_server_error_codes.h" #include "common/command_line.h" #include "common/beldex.h" #include "common/sha256sum.h" @@ -60,8 +64,6 @@ #include "cryptonote_core/uptime_proof.h" #include "net/parse.h" #include "crypto/hash.h" -#include "rpc/rpc_args.h" -#include "core_rpc_server_error_codes.h" #include "p2p/net_node.h" #include "version.h" #include @@ -70,112 +72,98 @@ #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc" -namespace cryptonote { namespace rpc { +namespace cryptonote::rpc { namespace { - // Helper loaders for RPC registration; this lets us reduce the amount of compiled code by - // avoiding the need to instantiate {JSON,binary} loading code for {binary,JSON} commands. - // This first one is for JSON, the specialization below is for binary. - template - struct reg_helper { - using Request = typename RPC::request; - - Request load(rpc_request& request) { - Request req{}; - if (auto body = request.body_view()) { - if (!epee::serialization::load_t_from_json(req, *body)) - throw parse_error{"Failed to parse JSON parameters"}; - } else { - // This is nasty. TODO: get rid of epee's horrible serialization code. - auto& epee_stuff = var::get(request.body); - auto& storage_entry = epee_stuff.second; - // Epee nomenclature translactions: - // - // - "storage_entry" is a variant over values (ints, doubles, string, storage_entries, or - // array_entry). - // - // - "array_entry" is a variant over vectors of all of those values. - // - // Epee's json serialization also has a metric ton of limitations: for example it can't - // properly deserialize signed integer (unless *all* values are negative), or doubles - // (unless *all* values do not look like ints), and for both serialization and - // deserialization doesn't support lists of lists, and any mixed types in lists (for - // example '[bool, 1, "hi"]`). - // - // Conclusion: it needs to go. - if (auto* section = std::get_if(&storage_entry)) - req.load(epee_stuff.first, section); - else - throw std::runtime_error{"only top-level JSON object values are currently supported"}; - } - return req; - } - - // store_t_to_json can't store a string. Go epee. - template ::value, int> = 0> - std::string serialize(std::string&& res) { - std::ostringstream o; - epee::serialization::dump_as_json(o, std::move(res), 0 /*indent*/, false /*newlines*/); - return o.str(); - } - template ::value, int> = 0> - std::string serialize(typename RPC::response&& res) { - std::string response; - epee::serialization::store_t_to_json(res, response, 0 /*indent*/, false /*newlines*/); - return response; - } - }; - - // binary command specialization - template - struct reg_helper::value>> { - using Request = typename RPC::request; - Request load(rpc_request& request) { - LOG_PRINT_L2("reg_helper load" << __func__); - Request req{}; - std::string_view data; - if (auto body = request.body_view()) - data = *body; - else - throw std::runtime_error{"Internal error: can't load binary a RPC command with non-string body"}; - if (sizeof(data)>0){ - if (!epee::serialization::load_t_from_binary(req, data)) - throw parse_error{"Failed to parse binary data parameterS"}; + oxenmq::bt_value json_to_bt(nlohmann::json&& j) { + using namespace oxenmq; + if (j.is_object()) { + bt_dict res; + for (auto& [k, v] : j.items()) { + if (v.is_null()) + continue; // skip k-v pairs with a null v (for other nulls we fail). + res[k] = json_to_bt(std::move(v)); } - else{ - - } - - return req; + return res; } - - std::string serialize(typename RPC::response&& res) { - std::string response; - epee::serialization::store_t_to_binary(res, response); - return response; + if (j.is_array()) { + bt_list res; + for (auto& v : j) + res.push_back(json_to_bt(std::move(v))); + return res; } - }; - + if (j.is_string()) { + return std::move(j.get_ref()); + } + if (j.is_boolean()) + return j.get() ? 1 : 0; + if (j.is_number_unsigned()) + return j.get(); + if (j.is_number_integer()) + return j.get(); + throw std::domain_error{"internal error: encountered some unhandled/invalid type in json-to-bt translation"}; + } + template , int> = 0> void register_rpc_command(std::unordered_map>& regs) { - using Request = typename RPC::request; - using Response = typename RPC::response; - /// check that core_rpc_server.invoke(Request, rpc_context) returns a Response; the code below - /// will fail anyway if this isn't satisfied, but that compilation failure might be more cryptic. - using invoke_return_type = decltype(std::declval().invoke(std::declval(), rpc_context{})); - static_assert(std::is_same::value, - "Unable to register RPC command: core_rpc_server::invoke(Request) is not defined or does not return a Response"); auto cmd = std::make_shared(); cmd->is_public = std::is_base_of_v; cmd->is_binary = std::is_base_of_v; cmd->is_legacy = std::is_base_of_v; - cmd->invoke = [](rpc_request&& request, core_rpc_server& server) { - reg_helper helper; - Response res = server.invoke(helper.load(request), std::move(request.context)); - return helper.serialize(std::move(res)); - }; + if constexpr (!std::is_base_of_v) { + static_assert(!FIXME_has_nested_response_v); + cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> std::string { + RPC rpc; + try { + if (auto body = request.body_view()) { + if (body->front() == 'd') { // Looks like a bt dict + rpc.set_bt(); + parse_request(rpc, oxenc::bt_dict_consumer{*body}); + } + else + parse_request(rpc, nlohmann::json::parse(*body)); + } else if (auto* json = std::get_if(&request.body)) { + parse_request(rpc, std::move(*json)); + } else { + assert(std::holds_alternative(request.body)); + parse_request(rpc, std::monostate{}); + } + } catch (const std::exception& e) { + throw parse_error{"Failed to parse request parameters: "s + e.what()}; + } + + server.invoke(rpc, std::move(request.context)); + + if (rpc.response.is_null()) + rpc.response = nlohmann::json::object(); + + if (rpc.is_bt()) + return bt_serialize(json_to_bt(std::move(rpc.response))); + else + return rpc.response.dump(); + }; + } else { + // Legacy binary request; these still use epee serialization, and should be considered + // deprecated (tentatively to be removed in Oxen 11). + cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> std::string { + typename RPC::request req{}; + std::string_view data; + if (auto body = request.body_view()) + data = *body; + else + throw std::runtime_error{"Internal error: can't load binary a RPC command with non-string body"}; + if (!epee::serialization::load_t_from_binary(req, data)) + throw parse_error{"Failed to parse binary data parameters"}; + + auto res = server.invoke(std::move(req), std::move(request.context)); + + std::string response; + epee::serialization::store_t_to_binary(res, response); + return response; + }; + } for (const auto& name : RPC::names()) regs.emplace(name, cmd); @@ -323,36 +311,32 @@ namespace cryptonote { namespace rpc { #define CHECK_CORE_READY() do { if(!check_core_ready()){ res.status = STATUS_BUSY; return res; } } while(0) //------------------------------------------------------------------------------------------------------------------------------ - GET_HEIGHT::response core_rpc_server::invoke(GET_HEIGHT::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_HEIGHT& get_height, rpc_context context) { - GET_HEIGHT::response res{}; - PERF_TIMER(on_get_height); + /* FIXME if (use_bootstrap_daemon_if_necessary(req, res)) return res; - + */ auto [height, hash] = m_core.get_blockchain_top(); - res.height = height; - ++res.height; // block height to chain height - res.hash = tools::type_to_hex(hash); - res.status = STATUS_OK; + ++height; // block height to chain height + get_height.response["status"] = STATUS_OK; + get_height.response["height"] = height; + get_height.response_hex["hash"] = hash; - res.immutable_height = 0; + uint64_t immutable_height = 0; cryptonote::checkpoint_t checkpoint; - if (m_core.get_blockchain_storage().get_db().get_immutable_checkpoint(&checkpoint, res.height - 1)) + if (m_core.get_blockchain_storage().get_db().get_immutable_checkpoint(&checkpoint, height - 1)) { - res.immutable_height = checkpoint.height; - res.immutable_hash = tools::type_to_hex(checkpoint.block_hash); + get_height.response["immutable_height"] = checkpoint.height; + get_height.response_hex["immutable_hash"] = checkpoint.block_hash; } - - return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_INFO::response core_rpc_server::invoke(GET_INFO::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_INFO& info, rpc_context context) { - GET_INFO::response res{}; - PERF_TIMER(on_get_info); + /* FIXME if (use_bootstrap_daemon_if_necessary(req, res)) { if (context.admin) @@ -370,91 +354,106 @@ namespace cryptonote { namespace rpc { } return res; } - + */ auto [top_height, top_hash] = m_core.get_blockchain_top(); - res.height = top_height; - auto prev_ts = m_core.get_blockchain_storage().get_db().get_block_timestamp(res.height); - ++res.height; // turn top block height into blockchain height - res.top_block_hash = tools::type_to_hex(top_hash); - res.target_height = m_core.get_target_blockchain_height(); + + auto& bs = m_core.get_blockchain_storage(); + auto& db = bs.get_db(); + + auto prev_ts = db.get_block_timestamp(top_height); + auto height = top_height + 1; // turn top block height into blockchain height + + info.response["height"] = height; + info.response_hex["top_block_hash"] = top_hash; + info.response["target_height"] = m_core.get_target_blockchain_height(); bool next_block_is_POS = false; - if (POS::timings t; POS::get_round_timings(m_core.get_blockchain_storage(), res.height, prev_ts, t)) { - res.POS_ideal_timestamp = tools::to_seconds(t.ideal_timestamp.time_since_epoch()); - res.POS_target_timestamp = tools::to_seconds(t.r0_timestamp.time_since_epoch()); + if (POS::timings t; + POS::get_round_timings(bs, height, prev_ts, t)) { + info.response["POS_ideal_timestamp"] = tools::to_seconds(t.ideal_timestamp.time_since_epoch()); + info.response["POS_target_timestamp"] = tools::to_seconds(t.r0_timestamp.time_since_epoch()); next_block_is_POS = POS::clock::now() < t.miner_fallback_timestamp; } - res.immutable_height = 0; - cryptonote::checkpoint_t checkpoint; - if (m_core.get_blockchain_storage().get_db().get_immutable_checkpoint(&checkpoint, res.height - 1)) + if (cryptonote::checkpoint_t checkpoint; + db.get_immutable_checkpoint(&checkpoint, top_height)) { - res.immutable_height = checkpoint.height; - res.immutable_block_hash = tools::type_to_hex(checkpoint.block_hash); + info.response["immutable_height"] = checkpoint.height; + info.response_hex["immutable_block_hash"] = checkpoint.block_hash; } - res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(next_block_is_POS); - res.target = tools::to_seconds((next_block_is_POS ? TARGET_BLOCK_TIME : old::TARGET_BLOCK_TIME_12)); - res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase - res.tx_pool_size = m_core.get_pool().get_transactions_count(); + if (next_block_is_POS) + info.response["POS"] = true; + else + info.response["difficulty"] = bs.get_difficulty_for_next_block(next_block_is_POS); + + info.response["target"] = tools::to_seconds((next_block_is_POS ? TARGET_BLOCK_TIME : old::TARGET_BLOCK_TIME_12)); + // This count seems broken: blocks with no outputs (after batching) shouldn't be subtracted, and + // 0-output txes (SN state changes) arguably shouldn't be, either. + info.response["tx_count"] = m_core.get_blockchain_storage().get_total_transactions() - height; //without coinbase + info.response["tx_pool_size"] = m_core.get_pool().get_transactions_count(); + if (context.admin) { - res.alt_blocks_count = m_core.get_blockchain_storage().get_alternative_blocks_count(); - uint64_t total_conn = m_p2p.get_public_connections_count(); - res.outgoing_connections_count = m_p2p.get_public_outgoing_connections_count(); - res.incoming_connections_count = (total_conn - *res.outgoing_connections_count); - res.white_peerlist_size = m_p2p.get_public_white_peers_count(); - res.grey_peerlist_size = m_p2p.get_public_gray_peers_count(); + info.response["alt_blocks_count"] = bs.get_alternative_blocks_count(); + auto total_conn = m_p2p.get_public_connections_count(); + auto outgoing_conns = m_p2p.get_public_outgoing_connections_count(); + info.response["outgoing_connections_count"] = outgoing_conns; + info.response["incoming_connections_count"] = total_conn - outgoing_conns; + info.response["white_peerlist_size"] = m_p2p.get_public_white_peers_count(); + info.response["grey_peerlist_size"] = m_p2p.get_public_gray_peers_count(); } cryptonote::network_type nettype = m_core.get_nettype(); - res.mainnet = nettype == network_type::MAINNET; - res.testnet = nettype == network_type::TESTNET; - res.devnet = nettype == network_type::DEVNET; - res.nettype = nettype == network_type::MAINNET ? "mainnet" : nettype == network_type::TESTNET ? "testnet" : nettype == network_type::DEVNET ? "devnet" : "fakechain"; + info.response["mainnet"] = nettype == network_type::MAINNET; + if (nettype == network_type::TESTNET) info.response["testnet"] = true; + else if (nettype == network_type::DEVNET) info.response["devnet"] = true; + else if (nettype != network_type::MAINNET) info.response["fakechain"] = true; + info.response["nettype"] = nettype == network_type::MAINNET ? "mainnet" : nettype == network_type::TESTNET ? "testnet" : nettype == network_type::DEVNET ? "devnet" : "fakechain"; try { - res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1); + auto cd = db.get_block_cumulative_difficulty(top_height); + info.response["cumulative_difficulty"] = cd; } catch(std::exception const &e) { - res.status = "Error retrieving cumulative difficulty at height " + std::to_string(res.height - 1); - return res; + info.response["status"] = "Error retrieving cumulative difficulty at height " + std::to_string(top_height); + return; } - res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); - res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); + info.response["block_size_limit"] = bs.get_current_cumulative_block_weight_limit(); + info.response["block_size_median"] = bs.get_current_cumulative_block_weight_median(); - auto bns_counts = m_core.get_blockchain_storage().name_system_db().get_mapping_counts(res.height); - res.bns_counts = bns_counts; + info.response["bns_counts"] = bs.name_system_db().get_mapping_counts(height); if (context.admin) { - res.master_node = m_core.master_node(); - res.start_time = (uint64_t)m_core.get_start_time(); - res.last_storage_server_ping = (uint64_t)m_core.m_last_storage_server_ping; - res.last_belnet_ping = (uint64_t)m_core.m_last_belnet_ping; - res.free_space = m_core.get_free_space(); - res.height_without_bootstrap = res.height; - std::shared_lock lock{m_bootstrap_daemon_mutex}; - if (m_bootstrap_daemon) - { - res.bootstrap_daemon_address = m_bootstrap_daemon->address(); + bool mn = m_core.master_node(); + info.response["master_node"] = sn; + info.response["start_time"] = m_core.get_start_time(); + if (mn) { + info.response["last_storage_server_ping"] = m_core.m_last_storage_server_ping.load(); + info.response["last_belnet_ping"] = m_core.m_last_belnet_ping.load(); + } + info.response["free_space"] = m_core.get_free_space(); + + if (std::shared_lock lock{m_bootstrap_daemon_mutex}; m_bootstrap_daemon) { + info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); + info.response["height_without_bootstrap"] = height; + info.response["was_bootstrap_ever_used"] = m_was_bootstrap_ever_used; } - res.was_bootstrap_ever_used = m_was_bootstrap_ever_used; } - res.offline = m_core.offline(); - res.database_size = m_core.get_blockchain_storage().get_db().get_database_size(); - if (!context.admin) - res.database_size = round_up(res.database_size, 1'000'000'000); - res.version = context.admin ? BELDEX_VERSION_FULL : std::to_string(BELDEX_VERSION[0]); - res.status_line = context.admin ? m_core.get_status_string() : + if (m_core.offline()) + info.response["offline"] = true; + auto db_size = db.get_database_size(); + info.response["database_size"] = context.admin ? db_size : round_up(db_size, 1'000'000'000); + info.response["version"] = context.admin ? BELDEX_VERSION_FULL : std::to_string(BELDEX_VERSION[0]); + info.response["status_line"] = context.admin ? m_core.get_status_string() : "v" + std::to_string(BELDEX_VERSION[0]) + "; Height: " + std::to_string(res.height); - res.status = STATUS_OK; - return res; + info.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ GET_NET_STATS::response core_rpc_server::invoke(GET_NET_STATS::request&& req, rpc_context context) @@ -487,17 +486,17 @@ namespace cryptonote { namespace rpc { }; } //------------------------------------------------------------------------------------------------------------------------------ - GET_BLOCKS_FAST::response core_rpc_server::invoke(GET_BLOCKS_FAST::request&& req, rpc_context context) + GET_BLOCKS_BIN::response core_rpc_server::invoke(GET_BLOCKS_BIN::request&& req, rpc_context context) { - GET_BLOCKS_FAST::response res{}; + GET_BLOCKS_BIN::response res{}; PERF_TIMER(on_get_blocks); - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary(req, res)) return res; std::vector, std::vector > > > bs; - if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, GET_BLOCKS_FAST::MAX_COUNT)) + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, GET_BLOCKS_BIN::MAX_COUNT)) { res.status = "Failed"; return res; @@ -511,11 +510,11 @@ namespace cryptonote { namespace rpc { res.blocks.resize(res.blocks.size()+1); res.blocks.back().block = bd.first.first; size += bd.first.first.size(); - res.output_indices.push_back(GET_BLOCKS_FAST::block_output_indices()); + res.output_indices.push_back(GET_BLOCKS_BIN::block_output_indices()); ntxes += bd.second.size(); res.output_indices.back().indices.reserve(1 + bd.second.size()); if (req.no_miner_tx) - res.output_indices.back().indices.push_back(GET_BLOCKS_FAST::tx_output_indices()); + res.output_indices.back().indices.push_back(GET_BLOCKS_BIN::tx_output_indices()); res.blocks.back().txs.reserve(bd.second.size()); for (auto& [txhash, txdata] : bd.second) { @@ -542,12 +541,12 @@ namespace cryptonote { namespace rpc { res.status = STATUS_OK; return res; } - GET_ALT_BLOCKS_HASHES::response core_rpc_server::invoke(GET_ALT_BLOCKS_HASHES::request&& req, rpc_context context) + GET_ALT_BLOCKS_HASHES_BIN::response core_rpc_server::invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context) { - GET_ALT_BLOCKS_HASHES::response res{}; + GET_ALT_BLOCKS_HASHES_BIN::response res{}; PERF_TIMER(on_get_alt_blocks_hashes); - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary(req, res)) return res; std::vector blks; @@ -570,12 +569,12 @@ namespace cryptonote { namespace rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_BLOCKS_BY_HEIGHT::response core_rpc_server::invoke(GET_BLOCKS_BY_HEIGHT::request&& req, rpc_context context) + GET_BLOCKS_BY_HEIGHT_BIN::response core_rpc_server::invoke(GET_BLOCKS_BY_HEIGHT_BIN::request&& req, rpc_context context) { - GET_BLOCKS_BY_HEIGHT::response res{}; + GET_BLOCKS_BY_HEIGHT_BIN::response res{}; PERF_TIMER(on_get_blocks_by_height); - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary(req, res)) return res; res.status = "Failed"; @@ -605,12 +604,12 @@ namespace cryptonote { namespace rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_HASHES_FAST::response core_rpc_server::invoke(GET_HASHES_FAST::request&& req, rpc_context context) + GET_HASHES_BIN::response core_rpc_server::invoke(GET_HASHES_BIN::request&& req, rpc_context context) { - GET_HASHES_FAST::response res{}; + GET_HASHES_BIN::response res{}; PERF_TIMER(on_get_hashes); - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary(req, res)) return res; res.start_height = req.start_height; @@ -681,12 +680,12 @@ namespace cryptonote { namespace rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_TX_GLOBAL_OUTPUTS_INDEXES::response core_rpc_server::invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES::request&& req, rpc_context context) + GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response core_rpc_server::invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context) { - GET_TX_GLOBAL_OUTPUTS_INDEXES::response res{}; + GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response res{}; PERF_TIMER(on_get_indexes); - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary(req, res)) return res; bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); @@ -696,7 +695,7 @@ namespace cryptonote { namespace rpc { return res; } res.status = STATUS_OK; - LOG_PRINT_L2("GET_TX_GLOBAL_OUTPUTS_INDEXES: [" << res.o_indexes.size() << "]"); + LOG_PRINT_L2("GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN: [" << res.o_indexes.size() << "]"); return res; } @@ -1559,13 +1558,13 @@ namespace cryptonote { namespace rpc { // // Beldex // - GET_OUTPUT_BLACKLIST::response core_rpc_server::invoke(GET_OUTPUT_BLACKLIST::request&& req, rpc_context context) + GET_OUTPUT_BLACKLIST_BIN::response core_rpc_server::invoke(GET_OUTPUT_BLACKLIST_BIN::request&& req, rpc_context context) { - GET_OUTPUT_BLACKLIST::response res{}; + GET_OUTPUT_BLACKLIST_BIN::response res{}; PERF_TIMER(on_get_output_blacklist_bin); - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary(req, res)) return res; try @@ -2543,9 +2542,8 @@ namespace cryptonote { namespace rpc { PERF_TIMER(on_sync_info); - auto [height, top_hash] = m_core.get_blockchain_top(); - res.height = height; - ++res.height; // turn top block height into blockchain height + auto [top_height, top_hash] = m_core.get_blockchain_top(); + res.height = top_height + 1; // turn top block height into blockchain height res.target_height = m_core.get_target_blockchain_height(); res.next_needed_pruning_seed = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second; @@ -3656,11 +3654,10 @@ namespace cryptonote { namespace rpc { } //------------------------------------------------------------------------------------------------------------------------------ - BNS_RESOLVE::response core_rpc_server::invoke(BNS_RESOLVE::request&& req, rpc_context context) + void core_rpc_server::invoke(BNS_RESOLVE& resolve, rpc_context context) { - BNS_RESOLVE::response res{}; - - if (req.type >= static_cast>(bns::mapping_type::belnet_2years)) + auto& req = resolve.request; + if (req.type < 0 || req.type >= tools::enum_count) throw rpc_error{ERROR_WRONG_PARAM, "Unable to resolve BNS address: 'type' parameter not specified"}; auto name_hash = bns::name_hash_input_to_base64(req.name_hash); @@ -3675,11 +3672,10 @@ namespace cryptonote { namespace rpc { type, *name_hash, m_core.get_current_blockchain_height())) { auto [val, nonce] = mapping->value_nonce(type); - res.encrypted_value = oxenc::to_hex(val); + resolve.response_hex["encrypted_value"] = val; if (val.size() < mapping->to_view().size()) - res.nonce = oxenc::to_hex(nonce); + resolve.response_hex["nonce"] = nonce; } - return res; } //------------------------------------------------------------------------------------------------------------------------------ BNS_VALUE_DECRYPT::response core_rpc_server::invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context) @@ -3731,4 +3727,4 @@ namespace cryptonote { namespace rpc { res.value = value.to_readable_value(nettype(), type); return res; } -} } // namespace cryptonote +} // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 26a8a3ef824..549c0d44eb9 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -56,6 +56,12 @@ class variables_map; } namespace cryptonote::rpc { + // FIXME: temporary shim for converting RPC methods + template + struct FIXME_has_nested_response : std::false_type {}; + template + struct FIXME_has_nested_response> : std::true_type {}; + template constexpr bool FIXME_has_nested_response_v = FIXME_has_nested_response::value; /// Exception when trying to invoke an RPC command that indicate a parameter parse failure (will /// give an invalid params error for JSON-RPC, for example). @@ -86,6 +92,7 @@ namespace cryptonote::rpc { std::string message; }; + /// FIXME: kill this. /// Junk that epee makes us deal with to pass in a generically parsed json value using jsonrpc_params = std::pair; @@ -101,7 +108,7 @@ namespace cryptonote::rpc { // first place if attempted by a public requestor). bool admin = false; - // The RPC engine source of the request, i.e. internal, HTTP, LMQ + // The RPC engine source of the request, i.e. internal, HTTP, OMQ rpc_source source = rpc_source::internal; // A free-form identifier (meant for humans) identifiying the remote address of the request; @@ -110,20 +117,20 @@ namespace cryptonote::rpc { }; struct rpc_request { - // The request body; for a non-HTTP-JSON-RPC request the string or string_view will be populated - // with the unparsed request body (though may be empty, e.g. for GET requests). For HTTP - // JSON-RPC request, if the request has a "params" value then the epee storage pair will be set - // to the portable_storage entry and the storage entry containing "params". If "params" is - // omitted entirely (or, for LMQ, there is no data part) then the string will be set in the - // variant (and empty). + // The request body: + // - for an HTTP, non-JSONRPC POST request the string or string_view will be populated with the + // unparsed request body. + // - for an HTTP JSONRPC request with a "params" value the nlohmann::json will be set to the + // parsed "params" value of the request. + // - for OMQ requests with a data part the string or string_view will be set to the provided value + // - for all other requests (i.e. JSONRPC with no params; HTTP GET requests; no-data OMQ + // requests) the variant will contain a std::monostate. // - // The returned value in either case is the serialized value to return. - // - // If sometimes goes wrong, throw. - std::variant body; + // If something goes wrong, throw. + std::variant body; // Returns a string_view of the body, if the body is a string or string_view. Returns - // std::nullopt if the body is a jsonrpc_params. + // std::nullopt if the body is empty (std::monostate) or parsed jsonrpc params. std::optional body_view() const; // Values to pass through to the invoke() call @@ -190,21 +197,29 @@ namespace cryptonote::rpc { network_type nettype() const { return m_core.get_nettype(); } - GET_HEIGHT::response invoke(GET_HEIGHT::request&& req, rpc_context context); - GET_BLOCKS_FAST::response invoke(GET_BLOCKS_FAST::request&& req, rpc_context context); - GET_ALT_BLOCKS_HASHES::response invoke(GET_ALT_BLOCKS_HASHES::request&& req, rpc_context context); - GET_BLOCKS_BY_HEIGHT::response invoke(GET_BLOCKS_BY_HEIGHT::request&& req, rpc_context context); - GET_HASHES_FAST::response invoke(GET_HASHES_FAST::request&& req, rpc_context context); - GET_TRANSACTIONS::response invoke(GET_TRANSACTIONS::request&& req, rpc_context context); + // JSON & bt-encoded RPC endpoints + void invoke(GET_HEIGHT& req, rpc_context context); + void invoke(GET_INFO& info, rpc_context context); + void invoke(BNS_RESOLVE& resolve, rpc_context context); + + // Deprecated Monero NIH binary endpoints: + GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); + GET_BLOCKS_BIN::response invoke(GET_BLOCKS_BIN::request&& req, rpc_context context); + GET_BLOCKS_BY_HEIGHT_BIN::response invoke(GET_BLOCKS_BY_HEIGHT_BIN::request&& req, rpc_context context); + GET_HASHES_BIN::response invoke(GET_HASHES_BIN::request&& req, rpc_context context); + GET_OUTPUT_BLACKLIST_BIN::response invoke(GET_OUTPUT_BLACKLIST_BIN::request&& req, rpc_context context); + GET_OUTPUT_DISTRIBUTION_BIN::response invoke(GET_OUTPUT_DISTRIBUTION_BIN::request&& req, rpc_context context); + GET_OUTPUTS_BIN::response invoke(GET_OUTPUTS_BIN::request&& req, rpc_context context); + GET_TRANSACTION_POOL_HASHES_BIN::response invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context); + GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); + + // FIXME: unconverted JSON RPC endpoints: IS_KEY_IMAGE_SPENT::response invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context); - GET_TX_GLOBAL_OUTPUTS_INDEXES::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES::request&& req, rpc_context context); SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); START_MINING::response invoke(START_MINING::request&& req, rpc_context context); STOP_MINING::response invoke(STOP_MINING::request&& req, rpc_context context); MINING_STATUS::response invoke(MINING_STATUS::request&& req, rpc_context context); - GET_OUTPUTS_BIN::response invoke(GET_OUTPUTS_BIN::request&& req, rpc_context context); GET_OUTPUTS::response invoke(GET_OUTPUTS::request&& req, rpc_context context); - GET_INFO::response invoke(GET_INFO::request&& req, rpc_context context); GET_NET_STATS::response invoke(GET_NET_STATS::request&& req, rpc_context context); SAVE_BC::response invoke(SAVE_BC::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); @@ -213,7 +228,6 @@ namespace cryptonote::rpc { SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); GET_TRANSACTION_POOL::response invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context); - GET_TRANSACTION_POOL_HASHES_BIN::response invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context); GET_TRANSACTION_POOL_HASHES::response invoke(GET_TRANSACTION_POOL_HASHES::request&& req, rpc_context context); GET_TRANSACTION_POOL_STATS::response invoke(GET_TRANSACTION_POOL_STATS::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); @@ -223,7 +237,6 @@ namespace cryptonote::rpc { OUT_PEERS::response invoke(OUT_PEERS::request&& req, rpc_context context); IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - GET_OUTPUT_DISTRIBUTION_BIN::response invoke(GET_OUTPUT_DISTRIBUTION_BIN::request&& req, rpc_context context); POP_BLOCKS::response invoke(POP_BLOCKS::request&& req, rpc_context context); GETBLOCKCOUNT::response invoke(GETBLOCKCOUNT::request&& req, rpc_context context); GETBLOCKHASH::response invoke(GETBLOCKHASH::request&& req, rpc_context context); @@ -250,7 +263,6 @@ namespace cryptonote::rpc { SYNC_INFO::response invoke(SYNC_INFO::request&& req, rpc_context context); GET_TRANSACTION_POOL_BACKLOG::response invoke(GET_TRANSACTION_POOL_BACKLOG::request&& req, rpc_context context); PRUNE_BLOCKCHAIN::response invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context); - GET_OUTPUT_BLACKLIST::response invoke(GET_OUTPUT_BLACKLIST::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); @@ -270,7 +282,6 @@ namespace cryptonote::rpc { BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); - BNS_RESOLVE::response invoke(BNS_RESOLVE::request&& req, rpc_context context); BNS_VALUE_DECRYPT::response invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context); FLUSH_CACHE::response invoke(FLUSH_CACHE::request&& req, rpc_context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp new file mode 100644 index 00000000000..089fcc7f76b --- /dev/null +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -0,0 +1,233 @@ +#include "core_rpc_server_command_parser.h" + +#include +#include +#include +#include + +namespace cryptonote::rpc { + + // Binary types that we support as input parameters. For json, these must be specified as hex or + // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. + template + inline constexpr bool is_binary_parameter = false; + template <> + inline constexpr bool is_binary_parameter = true; + + // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw + // bytes. + template >> + void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) { + constexpr size_t raw_size = sizeof(T); + constexpr size_t hex_size = raw_size * 2; + constexpr size_t b64_padded = (raw_size + 2) / 3 * 4; + constexpr size_t b64_padding = raw_size % 3 == 1 ? 2 : raw_size % 3 == 2 ? 1 : 0; + constexpr size_t b64_unpadded = b64_padded - b64_padding; + constexpr std::string_view b64_padding_string = b64_padding == 2 ? "=="sv : b64_padding == 1 ? "="sv : ""sv; + if (allow_raw && bytes.size() == raw_size) { + std::memcpy(&val, bytes.data(), bytes.size()); + return; + } else if (bytes.size() == hex_size) { + if (oxenc::is_hex(bytes)){ + oxenc::from_hex(bytes.begin(), bytes.end(), reinterpret_cast(&val)); + return; + } + } else if (bytes.size() == b64_unpadded || + (b64_padding > 0 && bytes.size() == b64_padded && bytes.substr(b64_unpadded) == b64_padding_string)) { + if (oxenc::is_base64(bytes)){ + oxenc::from_base64(bytes.begin(), bytes.end(), reinterpret_cast(&val)); + return; + } + } + + throw std::runtime_error{"Invalid binary value: unexpected size and/or encoding"}; + } + +} + +// Specializations of binary types for deserialization; when receiving these from json we expect +// them encoded in hex or base64. These may *not* be used for serialization, and will throw if so +// invoked; for serialization you need to use RPC_COMMAND::response_binary instead. +namespace nlohmann { + template + struct adl_serializer>> { + static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); + + static void to_json(json& j, const T&) { + throw std::logic_error{"Internal error: binary types are not directly serialization"}; + } + static void from_json(const json& j, T& val) { + load_binary_parameter(j.get(), false /*no raw*/, val); + } + }; +} + + +namespace cryptonote::rpc { + + namespace { + + // Checks that key names are given in ascending order + template + void check_ascending_names(std::string_view name1, std::string_view name2, const Ignore&...) { + if (!(name2 > name1)) + throw std::runtime_error{"Internal error: request values must be retrieved in ascending order"}; + } + + // Wrapper around a reference for get_values that is used to indicate that the value is + // required, in which case an exception will be raised if the value is not found. Usage: + // + // int a_optional = 0, b_required; + // get_values(input, + // "a", a_optional, + // "b", required{b_required}, + // // ... + // ); + template + struct required { + T& value; + required(T& ref) : value{ref} {} + }; + template + constexpr bool is_required_wrapper = false; + template + constexpr bool is_required_wrapper> = true; + + using oxenc::bt_dict_consumer; + + using json_range = std::pair; + + // Advances the dict consumer to the first element >= the given name. Returns true if found, + // false if it advanced beyond the requested name. This is exactly the same as + // `d.skip_until(name)`, but is here so we can also overload an equivalent function for json + // iteration. + bool skip_until(oxenc::bt_dict_consumer& d, std::string_view name) { + return d.skip_until(name); + } + // Equivalent to the above but for a json object iterator. + bool skip_until(json_range& it_range, std::string_view name) { + auto& [it, end] = it_range; + while (it != end && it.key() < name) + ++it; + return it != end && it.key() == name; + } + + // Consumes the next value from the dict consumer into `val` + template + void load_value(oxenc::bt_dict_consumer& d, T& val) { + if constexpr (std::is_integral_v) + val = d.consume_integer(); + else if constexpr (std::is_same_v || std::is_same_v) + val = d.consume_string_view(); + else if constexpr (is_binary_parameter) + load_binary_parameter(d.consume_string_view(), true /*allow raw*/, val); + else if constexpr (std::is_same_v) + val = std::chrono::system_clock::time_point{std::chrono::seconds{d.consume_integer()}}; + else + static_assert(std::is_same_v, "Unsupported load_value type"); + } + // Copies the next value from the json range into `val`, and advances the iterator. Throws + // on unconvertible values. + template + void load_value(json_range& r, T& val) { + auto& key = r.first.key(); + auto& e = *r.first; + if constexpr (std::is_same_v) { + if (e.is_boolean()) + val = e.get(); + else if (e.is_number_unsigned()) { + // Also accept 0 or 1 for bools (mainly to be compatible with bt-encoding which doesn't + // have a distinct bool type). + auto b = e.get(); + if (b <= 1) + val = b; + else + throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; + } else { + throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; + } + } else if constexpr (std::is_unsigned_v) { + if (!e.is_number_unsigned()) + throw std::domain_error{"Invalid value for '" + key + "': non-negative value required"}; + auto i = e.get(); + if (sizeof(T) < sizeof(uint64_t) && i > std::numeric_limits::max()) + throw std::domain_error{"Invalid value for '" + key + "': value too large"}; + val = i; + } else if constexpr (std::is_integral_v) { + if (!e.is_number_integer()) + throw std::domain_error{"Invalid value for '" + key + "': value is not an integer"}; + auto i = e.get(); + if (sizeof(T) < sizeof(int64_t)) { + if (i < std::numeric_limits::lowest()) + throw std::domain_error{"Invalid value for '" + key + "': negative value magnitude is too large"}; + else if (i > std::numeric_limits::max()) + throw std::domain_error{"Invalid value for '" + key + "': value is too large"}; + } + val = i; + } else if constexpr (std::is_same_v || std::is_same_v) { + val = e.get(); + } else if constexpr (is_binary_parameter) { + load_binary_parameter(e.get(), false /*no raw bytes*/, val); + } else if constexpr (std::is_same_v) { + val = std::chrono::system_clock::time_point{std::chrono::seconds{e.get()}}; + } else { + static_assert(std::is_same_v, "Unsupported load type"); + } + ++r.first; + } + + // Gets the next value from a json object iterator or bt_dict_consumer. Leaves the iterator at + // the next value, i.e. found + 1 if found, or the next greater value if not found. (NB: + // nlohmann::json objects are backed by an *ordered* map and so both nlohmann iterators and + // bt_dict_consumer behave analogously here). + template + void get_next_value(In& in, std::string_view name, T& val) { + if constexpr (std::is_same_v) + ; + else if (skip_until(in, name)) { + if constexpr (is_required_wrapper) + return load_value(in, val.value); + else + return load_value(in, val); + } + if constexpr (is_required_wrapper) + throw std::runtime_error{"Required key '" + std::string{name} + "' not found"}; + } + + /// Accessor for simple, flat value retrieval from a json or bt_dict_consumer. In the later + /// case note that the given bt_dict_consumer will be advanced, so you *must* take care to + /// process keys in order, both for the keys passed in here *and* for use before and after this + /// call. + template + void get_values(Input& in, std::string_view name, T&& val, More&&... more) { + if constexpr (std::is_same_v) { + if (auto* json = std::get_if(&in)) { + json_range r{json->cbegin(), json->cend()}; + get_values(r, name, val, std::forward(more)...); + } else if (auto* dict = std::get_if(&in)) { + get_values(*dict, name, val, std::forward(more)...); + } else { + // A monostate indicates that no parameters field was provided at all + get_values(var::get(in), name, val, std::forward(more)...); + } + } else { + static_assert( + std::is_same_v || + std::is_same_v || + std::is_same_v); + get_next_value(in, name, val); + if constexpr (sizeof...(More) > 0) { + check_ascending_names(name, more...); + get_values(in, std::forward(more)...); + } + } + } + } + + void parse_request(BNS_RESOLVE& bns, rpc_input in) { + get_values(in, + "name_hash", required{bns.request.name_hash}, + "type", required{bns.request.type}); + } + +} \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h new file mode 100644 index 00000000000..d346f5cce02 --- /dev/null +++ b/src/rpc/core_rpc_server_command_parser.h @@ -0,0 +1,14 @@ +#pragma once + +#include "core_rpc_server_commands_defs.h" +#include +#include + +namespace cryptonote::rpc { + + using rpc_input = std::variant; + + inline void parse_request(NO_ARGS&, rpc_input) {} + + void parse_request(BNS_RESOLVE& bns, rpc_input); +} \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 23f4172f0a2..55da1acdffa 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1,7 +1,24 @@ #include "core_rpc_server_commands_defs.h" +#include +#include namespace cryptonote::rpc { +nlohmann::json& json_binary_proxy::operator=(std::string_view binary_data) { + switch (format) { + case fmt::bt: return e = binary_data; + case fmt::hex: return e = oxenc::to_hex(binary_data); + case fmt::base64: return e = oxenc::to_base64(binary_data); + } + throw std::runtime_error{"Internal error: invalid binary encoding"}; +} + +void RPC_COMMAND::set_bt() { + bt = true; + response_b64.format = json_binary_proxy::fmt::bt; + response_hex.format = json_binary_proxy::fmt::bt; +} + KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() @@ -11,17 +28,17 @@ KV_SERIALIZE_MAP_CODE_BEGIN(EMPTY) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_HEIGHT::response) - KV_SERIALIZE(height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) - KV_SERIALIZE(hash) - KV_SERIALIZE(immutable_height) - KV_SERIALIZE(immutable_hash) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_HEIGHT::response) +// KV_SERIALIZE(height) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE(hash) +// KV_SERIALIZE(immutable_height) +// KV_SERIALIZE(immutable_hash) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_FAST::request) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::request) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(prune) @@ -29,17 +46,17 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_FAST::request) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_FAST::tx_output_indices) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::tx_output_indices) KV_SERIALIZE(indices) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_FAST::block_output_indices) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::block_output_indices) KV_SERIALIZE(indices) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_FAST::response) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::response) KV_SERIALIZE(blocks) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) @@ -49,32 +66,32 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_FAST::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT::request) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT_BIN::request) KV_SERIALIZE(heights) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT::response) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT_BIN::response) KV_SERIALIZE(blocks) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALT_BLOCKS_HASHES::response) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALT_BLOCKS_HASHES_BIN::response) KV_SERIALIZE(blks_hashes) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_FAST::request) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::request) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_FAST::response) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::response) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) @@ -192,12 +209,12 @@ KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES::request) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request) KV_SERIALIZE_VAL_POD_AS_BLOB(txid) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES::response) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response) KV_SERIALIZE(o_indexes) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) @@ -293,48 +310,48 @@ KV_SERIALIZE_MAP_CODE_BEGIN(MINING_STATUS::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_INFO::response) - KV_SERIALIZE(status) - KV_SERIALIZE(height) - KV_SERIALIZE(target_height) - KV_SERIALIZE(immutable_height) - KV_SERIALIZE(POS_ideal_timestamp) - KV_SERIALIZE(POS_target_timestamp) - KV_SERIALIZE(difficulty) - KV_SERIALIZE(target) - KV_SERIALIZE(tx_count) - KV_SERIALIZE(tx_pool_size) - KV_SERIALIZE(alt_blocks_count) - KV_SERIALIZE(outgoing_connections_count) - KV_SERIALIZE(incoming_connections_count) - KV_SERIALIZE(white_peerlist_size) - KV_SERIALIZE(grey_peerlist_size) - KV_SERIALIZE(mainnet) - KV_SERIALIZE(testnet) - KV_SERIALIZE(devnet) - KV_SERIALIZE(nettype) - KV_SERIALIZE(top_block_hash) - KV_SERIALIZE(immutable_block_hash) - KV_SERIALIZE(cumulative_difficulty) - KV_SERIALIZE(block_size_limit) - KV_SERIALIZE(block_weight_limit) - KV_SERIALIZE(block_size_median) - KV_SERIALIZE(block_weight_median) - KV_SERIALIZE(bns_counts) - KV_SERIALIZE(start_time) - KV_SERIALIZE(master_node) - KV_SERIALIZE(last_storage_server_ping) - KV_SERIALIZE(last_belnet_ping) - KV_SERIALIZE(free_space) - KV_SERIALIZE(offline) - KV_SERIALIZE(untrusted) - KV_SERIALIZE(bootstrap_daemon_address) - KV_SERIALIZE(height_without_bootstrap) - KV_SERIALIZE(was_bootstrap_ever_used) - KV_SERIALIZE(database_size) - KV_SERIALIZE(version) - KV_SERIALIZE(status_line) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_INFO::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(height) +// KV_SERIALIZE(target_height) +// KV_SERIALIZE(immutable_height) +// KV_SERIALIZE(POS_ideal_timestamp) +// KV_SERIALIZE(POS_target_timestamp) +// KV_SERIALIZE(difficulty) +// KV_SERIALIZE(target) +// KV_SERIALIZE(tx_count) +// KV_SERIALIZE(tx_pool_size) +// KV_SERIALIZE(alt_blocks_count) +// KV_SERIALIZE(outgoing_connections_count) +// KV_SERIALIZE(incoming_connections_count) +// KV_SERIALIZE(white_peerlist_size) +// KV_SERIALIZE(grey_peerlist_size) +// KV_SERIALIZE(mainnet) +// KV_SERIALIZE(testnet) +// KV_SERIALIZE(devnet) +// KV_SERIALIZE(nettype) +// KV_SERIALIZE(top_block_hash) +// KV_SERIALIZE(immutable_block_hash) +// KV_SERIALIZE(cumulative_difficulty) +// KV_SERIALIZE(block_size_limit) +// KV_SERIALIZE(block_weight_limit) +// KV_SERIALIZE(block_size_median) +// KV_SERIALIZE(block_weight_median) +// KV_SERIALIZE(bns_counts) +// KV_SERIALIZE(start_time) +// KV_SERIALIZE(master_node) +// KV_SERIALIZE(last_storage_server_ping) +// KV_SERIALIZE(last_belnet_ping) +// KV_SERIALIZE(free_space) +// KV_SERIALIZE(offline) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE(bootstrap_daemon_address) +// KV_SERIALIZE(height_without_bootstrap) +// KV_SERIALIZE(was_bootstrap_ever_used) +// KV_SERIALIZE(database_size) +// KV_SERIALIZE(version) +// KV_SERIALIZE(status_line) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_NET_STATS::response) @@ -1263,7 +1280,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_BLACKLIST::response) +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_BLACKLIST_BIN::response) KV_SERIALIZE(blacklist) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) @@ -1398,16 +1415,16 @@ KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_RESOLVE::request) - KV_SERIALIZE(name_hash) - KV_SERIALIZE_OPT(type, static_cast(-1)) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_RESOLVE::request) +// KV_SERIALIZE(name_hash) +// KV_SERIALIZE_OPT(type, static_cast(-1)) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_RESOLVE::response) - KV_SERIALIZE(encrypted_value) - KV_SERIALIZE(nonce) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_RESOLVE::response) +// KV_SERIALIZE(encrypted_value) +// KV_SERIALIZE(nonce) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(BNS_VALUE_DECRYPT::request) KV_SERIALIZE(name); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index b9c554a9b8b..efe485dfa1c 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -51,6 +51,10 @@ #include "cryptonote_core/master_node_list.h" #include "common/beldex.h" +#include +#include +#include + namespace cryptonote { /// Namespace for core RPC commands. Every RPC commands gets defined here (including its name(s), @@ -98,28 +102,124 @@ namespace rpc { } } - /// Base command that all RPC commands must inherit from (either directly or via one or more of - /// the below tags). Inheriting from this (and no others) gives you a private, json, non-legacy - /// RPC command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC - /// it'll be at `whatever`. - struct RPC_COMMAND {}; + // Wrapper around a nlohmann::json + class json_binary_proxy { + nlohmann::json& e; + enum class fmt { bt, hex, base64 } format; + friend struct RPC_COMMAND; + explicit json_binary_proxy(nlohmann::json& elem, fmt format) + : e{elem}, format{format} {} + json_binary_proxy() = delete; + + public: + json_binary_proxy(const json_binary_proxy&) = default; + json_binary_proxy(json_binary_proxy&&) = default; + + /// Dereferencing a proxy element accesses the underlying nlohmann::json + nlohmann::json& operator*() { return e; } + nlohmann::json* operator->() { return &e; } + + /// Descends into the json object, returning a new binary value proxy around the child element. + template + json_binary_proxy operator[](T&& key) { + return json_binary_proxy{e[std::forward(key)], format}; + } + + /// Assigns binary data from a string_view/string/etc. + nlohmann::json& operator=(std::string_view binary_data); + + /// Assigns binary data from a string_view over a 1-byte, non-char type (e.g. unsigned char or + /// uint8_t). + template , int> = 0> + nlohmann::json& operator=(std::basic_string_view binary_data) { + return *this = std::string_view{reinterpret_cast(binary_data.data()), binary_data.size()}; + } + + /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its + /// contents as the binary value. + template && !std::is_scalar_v && std::is_trivial_v && std::has_unique_object_representations_v, int> = 0> + nlohmann::json& operator=(const T& val) { + return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; + } + }; + + /// Base class that all RPC commands must inherit from (either directly or via one or more of the + /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC + /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be + /// at `whatever`. This base class is also where response objects are stored. + struct RPC_COMMAND { + private: + bool bt = false; + public: + /// Indicates whether this response is to be bt (true) or json (false) encoded. Do not set. + bool is_bt() const { return bt; } + + /// Called early in the request to indicate that this request is a bt-encoded one. + void set_bt(); + + /// The response data. For bt-encoded responses we convert this on the fly, with the + /// following notes: + /// - boolean values become 0 or 1 + /// - key-value pairs with null values are omitted from the object + /// - other null values are not permitted at all: an exception will be raised if the json + /// contains such a value. + /// - double values are not permitted; if a double is absolutely needed then check `is_bt` + /// and, when bt, encode it in some documented, endpoint-specific way. + /// - binary values in strings *are* permitted, but the caller must take care because they + /// will not be permitted for actual json responses (json serialization will fail): the caller + /// is expected to do something like: + /// + /// std::string binary = some_binary_data(); + /// cmd.response["binary_value"] = is_bt ? binary : oxenc::to_hex(binary); + /// + /// or, more conveniently, using one of the shortcut methods: + /// + /// cmd.response_binary(cmd.response["binary_value"], some_binary_data()); + /// cmd.response_binary("binary_value", some_binary_data()); + /// + nlohmann::json response; + + /// Proxy object that is used to set binary data in `response`, encoding it as hex if this + /// data is being returned as json. If this response is to be bt-encoded then the binary + /// value is left as-is (which isn't valid for json, but can be transported inside the json + /// value as we never dump() when going to bt-encoded). + /// + /// Usage: + /// std::string data = "abc"; + /// rpc.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" + json_binary_proxy response_hex{response, json_binary_proxy::fmt::hex}; + + /// Proxy object that encodes binary data as base64 for json, leaving it as binary for + /// bt-encoded responses. + /// + /// Usage: + /// std::string data = "abc"; + /// rpc.response_b64["foo"]["bar"] = data; // json: "YWJj", bt: "abc" + json_binary_proxy response_b64{response, json_binary_proxy::fmt::base64}; + }; /// Tag types that are used (via inheritance) to set rpc endpoint properties /// Specifies that the RPC call is public (i.e. available through restricted rpc). If this is /// *not* inherited from then the command is restricted (i.e. only available to admins). For LMQ, /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). - struct PUBLIC : RPC_COMMAND {}; + struct PUBLIC : virtual RPC_COMMAND {}; - /// Specifies that the RPC call is binary input/ouput. If not given then the command is JSON. - /// For HTTP RPC this also means the command is *not* available via the HTTP JSON RPC. - struct BINARY : RPC_COMMAND {}; + /// Specifies that the RPC call is legacy, deprecated Monero custom binary input/ouput. If not + /// given then the command is JSON/bt-encoded values. For HTTP RPC this also means the command is + /// *not* available via the HTTP JSON RPC. + struct BINARY : virtual RPC_COMMAND {}; + + /// Specifies that the RPC call takes no input arguments. (A dictionary of parameters may still + /// be passed, but will be ignored). + struct NO_ARGS : virtual RPC_COMMAND {}; /// Specifies a "legacy" JSON RPC command, available via HTTP JSON at /whatever (in addition to /// json_rpc as "whatever"). When accessed via legacy mode the result is just the .result element /// of the JSON RPC response. (Only applies to the HTTP RPC interface, and does nothing if BINARY /// if specified). - struct LEGACY : RPC_COMMAND {}; + struct LEGACY : virtual RPC_COMMAND {}; /// (Not a tag). Generic, serializable, no-argument request type, use as `struct request : EMPTY {};` @@ -133,40 +233,41 @@ namespace rpc { KV_MAP_SERIALIZABLE }; - BELDEX_RPC_DOC_INTROSPECT - // Get the node's current height. - struct GET_HEIGHT : PUBLIC, LEGACY + + /// Get the node's current height. + /// + /// Inputs: none. + /// + /// Outputs: + /// + /// height -- The current blockchain height according to the queried daemon. + /// status -- Generic RPC error code. "OK" is the success value. + /// untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// hash -- Hash of the block at the current height + /// immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 SN checkpoints. Omitted if not available. + /// immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. + struct GET_HEIGHT : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_height", "getheight"); } - - struct request : EMPTY {}; - struct response - { - uint64_t height; // The current blockchain height according to the queried daemon. - std::string status; // Generic RPC error code. "OK" is the success value. - bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. - std::string hash; // Hash of the block at the current height - uint64_t immutable_height; // The latest height in the blockchain that can not be reorganized from (backed by atleast 2 Service Node, or 1 hardcoded checkpoint, 0 if N/A). - std::string immutable_hash; // Hash of the highest block in the chain that can not be reorganized. - - KV_MAP_SERIALIZABLE - }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get all blocks info. Binary request. - struct GET_BLOCKS_FAST : PUBLIC, BINARY + /// Get all blocks info. Deprecated, Monero custom binary request. See the (FIXME) RPC endpoint + /// instead. + /// + /// Inputs: + /// block_ids -- descending list of block IDs used to detect reorganizations and network status: + /// the first 10 are the 10 most recent blocks, after which height decreases by a power of 2. + struct GET_BLOCKS_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_blocks.bin", "getblocks.bin"); } static constexpr size_t MAX_COUNT = 1000; - struct request - { - std::list block_ids; // First 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block - uint64_t start_height; // The starting block's height. - bool prune; // Prunes the blockchain, drops off 7/8 off the block iirc. - bool no_miner_tx; // Optional (false by default). + struct request { + std::list block_ids; // Descending list of block IDs used to detect reorganizations and network: the first 10 blocks id are sequential, then height drops by a power of 2 (2, 4, 8, 16, etc.) down to height 1, and then finally the genesis block id. + uint64_t start_height; // The height of the first block to fetch. + bool prune; // Prunes the blockchain, dropping off 7/8ths of the blocks. + bool no_miner_tx; // If specified and true, don't include miner transactions in transaction results. KV_MAP_SERIALIZABLE }; @@ -200,7 +301,7 @@ namespace rpc { BELDEX_RPC_DOC_INTROSPECT // Get blocks by height. Binary request. - struct GET_BLOCKS_BY_HEIGHT : PUBLIC, BINARY + struct GET_BLOCKS_BY_HEIGHT_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_blocks_by_height.bin", "getblocks_by_height.bin"); } @@ -224,7 +325,7 @@ namespace rpc { BELDEX_RPC_DOC_INTROSPECT // Get the known blocks hashes which are not on the main chain. - struct GET_ALT_BLOCKS_HASHES : PUBLIC, BINARY + struct GET_ALT_BLOCKS_HASHES_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_alt_blocks_hashes.bin"); } @@ -241,7 +342,7 @@ namespace rpc { BELDEX_RPC_DOC_INTROSPECT // Get hashes. Binary request. - struct GET_HASHES_FAST : PUBLIC, BINARY + struct GET_HASHES_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_hashes.bin", "gethashes.bin"); } @@ -420,7 +521,7 @@ namespace rpc { BELDEX_RPC_DOC_INTROSPECT // Get global outputs of transactions. Binary request. - struct GET_TX_GLOBAL_OUTPUTS_INDEXES : PUBLIC, BINARY + struct GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_o_indexes.bin"); } @@ -607,60 +708,83 @@ namespace rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Retrieve general information about the state of your node and the network. - // Note that all of the std::optional<> fields here are not included if the request is a public - // (restricted) RPC request. - struct GET_INFO : PUBLIC, LEGACY + /// Retrieve general information about the state of the node and the network. + /// + /// Inputs: none. + /// + /// Output values available from a public RPC endpoint: + /// + /// \arg \c status General RPC status string. `"OK"` means everything looks good. + /// \arg \c height Current length of longest chain known to daemon. + /// \arg \c target_height The height of the next block in the chain. + /// \arg \c immutable_height The latest height in the blockchain that can not be reorganized (i.e. + /// is backed by at least 2 Service Node, or 1 hardcoded checkpoint, 0 if N/A). Omitted if it + /// cannot be determined (typically because the node is still syncing). + /// \arg \c POS will be true if the next expected block is a POS block, false otherwise. + /// \arg \c POS_ideal_timestamp For POS blocks this is the ideal timestamp of the next block, + /// that is, the timestamp if the network was operating with perfect 2-minute blocks since the + /// POS hard fork. + /// \arg \c POS_target_timestamp For POS blocks this is the target timestamp of the next + /// block, which targets 2 minutes after the previous block but will be slightly faster/slower + /// if the previous block is behind/ahead of the ideal timestamp. + /// \arg \c difficulty Network mining difficulty; omitted when the network is expecting a POS + /// block. + /// \arg \c target Current target for next proof of work. + /// \arg \c tx_count Total number of non-coinbase transaction in the chain. + /// \arg \c tx_pool_size Number of transactions that have been broadcast but not included in a + /// block. + /// \arg \c mainnet Indicates whether the node is on the main network (`true`) or not (`false`). + /// \arg \c testnet Indicates that the node is on the test network (`true`). Will be omitted for + /// non-testnet. + /// \arg \c devnet Indicates that the node is on the dev network (`true`). Will be omitted for + /// non-devnet. + /// \arg \c fakechain States that the node is running in "fakechain" mode (`true`). Omitted + /// otherwise. + /// \arg \c nettype String value of the network type (mainnet, testnet, devnet, or fakechain). + /// \arg \c top_block_hash Hash of the highest block in the chain. Will be hex for JSON requests, + /// 32-byte binary value for bt requests. + /// \arg \c immutable_block_hash Hash of the highest block in the chain that can not be + /// reorganized. Hex string for json, bytes for bt. + /// \arg \c cumulative_difficulty Cumulative difficulty of all blocks in the blockchain. + /// \arg \c block_size_limit Maximum allowed block size. + /// \arg \c block_size_median Median block size of latest 100 blocks. + /// \arg \c bns_counts BNS registration counts. + /// \arg \c offline Indicates that the node is offline, if true. Omitted for online nodes. + /// \arg \c untrusted Indicates that the result was obtained using a bootstrap mode, and is therefore + /// not trusted (`true`). Omitted for non-bootstrap responses. + /// \arg \c database_size Current size of Blockchain data. Over public RPC this is rounded up to + /// the next-largest GB value. + /// \arg \c version Current version of this daemon, as a string. For a public node this will just + /// be the major and minor version (e.g. "9"); for an admin rpc endpoint this will return the + /// full version (e.g. "9.2.1"). + /// \arg \c status_line A short one-line summary string of the node (requires an + /// admin/unrestricted connection for most details) + /// + /// If the endpoint is a restricted (i.e. admin) endpoint then the following fields are also + /// included: + /// + /// \arg \c alt_blocks_count Number of alternative blocks to main chain. + /// \arg \c outgoing_connections_count Number of peers that you are connected to and getting + /// information from. + /// \arg \c incoming_connections_count Number of peers connected to and pulling from your node. + /// \arg \c white_peerlist_size White Peerlist Size + /// \arg \c grey_peerlist_size Grey Peerlist Size + /// \arg \c master_node Will be true if the node is running in --master-node mode. + /// \arg \c start_time Start time of the daemon, as UNIX time. + /// \arg \c last_storage_server_ping Last ping time of the storage server (0 if never or not + /// running as a master node) + /// \arg \c last_belnet_ping Last ping time of belnet (0 if never or not running as a master + /// node) + /// \arg \c free_space Available disk space on the node. + /// \arg \c bootstrap_daemon_address Bootstrap node to give immediate usability to wallets while + /// syncing by proxying RPC to it. (Note: the replies may be untrustworthy). + /// \arg \c height_without_bootstrap Current length of the local chain of the daemon. Only + /// included if a bootstrap daemon is configured. + /// \arg \c was_bootstrap_ever_used States if the bootstrap node has ever been used since the daemon + /// started. Omitted if no bootstrap node is configured. + struct GET_INFO : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_info", "getinfo"); } - - struct request : EMPTY {}; - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - uint64_t height; // Current length of longest chain known to daemon. - uint64_t target_height; // The height of the next block in the chain. - uint64_t immutable_height; // The latest height in the blockchain that can not be reorganized from (backed by atleast 2 Master Node, or 1 hardcoded checkpoint, 0 if N/A). - uint64_t POS_ideal_timestamp; // For POS blocks this is the ideal timestamp of the next block, that is, the timestamp if the network was operating with perfect 2-minute blocks since the POS hard fork. - uint64_t POS_target_timestamp; // For POS blocks this is the target timestamp of the next block, which targets 2 minutes after the previous block but will be slightly faster/slower if the previous block is behind/ahead of the ideal timestamp. - uint64_t difficulty; // Network difficulty (analogous to the strength of the network). - uint64_t target; // Current target for next proof of work. - uint64_t tx_count; // Total number of non-coinbase transaction in the chain. - uint64_t tx_pool_size; // Number of transactions that have been broadcast but not included in a block. - std::optional alt_blocks_count; // Number of alternative blocks to main chain. - std::optional outgoing_connections_count; // Number of peers that you are connected to and getting information from. - std::optional incoming_connections_count; // Number of peers connected to and pulling from your node. - std::optional white_peerlist_size; // White Peerlist Size - std::optional grey_peerlist_size; // Grey Peerlist Size - bool mainnet; // States if the node is on the mainnet (`true`) or not (`false`). - bool testnet; // States if the node is on the testnet (`true`) or not (`false`). - bool devnet; // States if the node is on the devnet (`true`) or not (`false`). - std::string nettype; // Nettype value used. - std::string top_block_hash; // Hash of the highest block in the chain. - std::string immutable_block_hash; // Hash of the highest block in the chain that can not be reorganized. - uint64_t cumulative_difficulty; // Cumulative difficulty of all blocks in the blockchain. - uint64_t block_size_limit; // Maximum allowed block size. - uint64_t block_weight_limit; // Maximum allowed block weight. - uint64_t block_size_median; // Median block size of latest 100 blocks. - uint64_t block_weight_median; // Median block weight of latest 100 blocks. - int bns_counts; // BNS registration counts. - std::optional master_node; // Will be true if the node is running in --service-node mode. - std::optional start_time; // Start time of the daemon, as UNIX time. - std::optional last_storage_server_ping; // Last ping time of the storage server (0 if never or not running as a service node) - std::optional last_belnet_ping; // Last ping time of belnet (0 if never or not running as a service node) - std::optional free_space; // Available disk space on the node. - bool offline; // States if the node is offline (`true`) or online (`false`). - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - std::optional bootstrap_daemon_address; // Bootstrap node to give immediate usability to wallets while syncing by proxying RPC to it. (Note: the replies may be untrustworthy). - std::optional height_without_bootstrap; // Current length of the local chain of the daemon. - std::optional was_bootstrap_ever_used; // States if a bootstrap node has ever been used since the daemon started. - uint64_t database_size; // Current size of Blockchain data. Over public RPC this is rounded up to the next-largest GB value. - std::string version; // Current version of software running. - std::string status_line; // A short one-line summary status of the node (requires an admin/unrestricted connection for most details) - - KV_MAP_SERIALIZABLE - }; }; //----------------------------------------------- @@ -2270,7 +2394,7 @@ namespace rpc { BELDEX_RPC_DOC_INTROSPECT // Get information on output blacklist. - struct GET_OUTPUT_BLACKLIST : PUBLIC, BINARY + struct GET_OUTPUT_BLACKLIST_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_output_blacklist.bin"); } struct request : EMPTY {}; @@ -2550,44 +2674,39 @@ namespace rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Performs a simple BNS lookup of a BLAKE2b-hashed name. This RPC method is meant for simple, - // single-value resolutions that do not care about registration details, etc.; if you need more - // information use BNS_NAMES_TO_OWNERS instead. - // - // Technical details: the returned value is encrypted using the name itself so that neither this - // beldexd responding to the RPC request nor any other blockchain observers can (easily) obtain the - // name of registered addresses or the registration details. Thus, from a client's point of view, - // resolving an BNS record involves: - // - // - Lower-case the name. - // - Calculate the name hash as a null-key, 32-byte BLAKE2b hash of the lower-case name. - // - Obtain the encrypted value and the nonce from this RPC call (or BNS_NAMES_TO_OWNERS); (encode - // the name hash using either hex or base64.). - // - Calculate the decryption key as a 32-byte BLAKE2b keyed hash of the name using the - // (unkeyed) name hash calculated above as the hash key. - // - Decrypt (and verify) using XChaCha20-Poly1305 (for example libsodium's - // crypto_aead_xchacha20poly1305_ietf_decrypt) using the above decryption key and using the - // first 24 bytes of the name hash as the public nonce. + /// Performs a simple BNS lookup of a BLAKE2b-hashed name. This RPC method is meant for simple, + /// single-value resolutions that do not care about registration details, etc.; if you need more + /// information use BNS_NAMES_TO_OWNERS instead. + /// + /// Returned values: + /// + /// \arg \c encrypted_value The encrypted BNS value, in hex. Will be omitted from the response if + /// the given name_hash is not registered. + /// \arg \c nonce The nonce value used for encryption, in hex. Will be omitted if the given name + /// is not registered. + /// + /// Technical details: the returned value is encrypted using the name itself so that neither this + /// oxend responding to the RPC request nor any other blockchain observers can (easily) obtain the + /// name of registered addresses or the registration details. Thus, from a client's point of view, + /// resolving an BNS record involves: + /// + /// 1. Lower-case the name. + /// 2. Calculate the name hash as a null-key, 32-byte BLAKE2b hash of the lower-case name. + /// 3. Obtain the encrypted value and the nonce from this RPC call (or BNS_NAMES_TO_OWNERS); when + /// using json encode the name hash using either hex or base64. + /// 4. Calculate the decryption key as a 32-byte BLAKE2b *keyed* hash of the name using the + /// (unkeyed) name hash calculated above (in step 2) as the hash key. + /// 5. Decrypt (and verify) using XChaCha20-Poly1305 (for example libsodium's + /// crypto_aead_xchacha20poly1305_ietf_decrypt) using the above decryption key and using the + /// first 24 bytes of the name hash as the public nonce. struct BNS_RESOLVE : PUBLIC { static constexpr auto names() { return NAMES("bns_resolve", "lns_resolve"); } - struct request - { - uint16_t type; // The BNS type (mandatory); currently supported values are: 0 = bchat, 1 = wallet, 2 = belnet. 6=eth_addr - std::string name_hash; // The 32-byte BLAKE2b hash of the name to look up, encoded as 64 hex digits or 44/43 base64 characters (with/without padding). - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::optional encrypted_value; // The encrypted BNS value, in hex. Will be omitted from the response if the given name_hash is not registered. - std::optional nonce; // The nonce value used for encryption, in hex. - - KV_MAP_SERIALIZABLE - }; + struct request_parameters { + int type = -1; ///< The BNS type (mandatory); currently supported values are: 0 = bchat, 1 = wallet, 2 = belnet, 6=eth_addr. + std::string name_hash; ///< The 32-byte BLAKE2b hash of the name to look up, encoded as 64 hex digits or 44/43 base64 characters (with/without padding). For bt-encoded requests this can also be the raw 32 bytes. + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -2635,20 +2754,27 @@ namespace rpc { /// ::response does not. using core_rpc_types = tools::type_list< GET_HEIGHT, - GET_BLOCKS_FAST, - GET_BLOCKS_BY_HEIGHT, - GET_ALT_BLOCKS_HASHES, - GET_HASHES_FAST, + GET_INFO, + BNS_RESOLVE, + // Deprecated Monero NIH binary endpoints: + GET_ALT_BLOCKS_HASHES_BIN, + GET_BLOCKS_BIN, + GET_BLOCKS_BY_HEIGHT_BIN, + GET_HASHES_BIN, + GET_OUTPUTS_BIN, + GET_OUTPUT_BLACKLIST_BIN, + GET_OUTPUT_DISTRIBUTION_BIN, + GET_TRANSACTION_POOL_HASHES_BIN, + GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN + >; + using FIXME_old_rpc_types = tools::type_list< GET_TRANSACTIONS, IS_KEY_IMAGE_SPENT, - GET_TX_GLOBAL_OUTPUTS_INDEXES, - GET_OUTPUTS_BIN, GET_OUTPUTS, SEND_RAW_TX, START_MINING, STOP_MINING, MINING_STATUS, - GET_INFO, GET_NET_STATS, SAVE_BC, GETBLOCKCOUNT, @@ -2666,7 +2792,6 @@ namespace rpc { SET_LOG_LEVEL, SET_LOG_CATEGORIES, GET_TRANSACTION_POOL, - GET_TRANSACTION_POOL_HASHES_BIN, GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_BACKLOG, GET_TRANSACTION_POOL_STATS, @@ -2691,7 +2816,6 @@ namespace rpc { RELAY_TX, SYNC_INFO, GET_OUTPUT_DISTRIBUTION, - GET_OUTPUT_DISTRIBUTION_BIN, POP_BLOCKS, PRUNE_BLOCKCHAIN, GET_QUORUM_STATE, @@ -2705,7 +2829,6 @@ namespace rpc { BELNET_PING, GET_STAKING_REQUIREMENT, GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, - GET_OUTPUT_BLACKLIST, GET_CHECKPOINTS, GET_MN_STATE_CHANGES, REPORT_PEER_STATUS, @@ -2714,7 +2837,6 @@ namespace rpc { BNS_NAMES_TO_OWNERS, BNS_LOOKUP, BNS_OWNERS_TO_NAMES, - BNS_RESOLVE, BNS_VALUE_DECRYPT, FLUSH_CACHE >; diff --git a/src/rpc/http_client.cpp b/src/rpc/http_client.cpp index ac9e9c7ae78..d24e379a20d 100755 --- a/src/rpc/http_client.cpp +++ b/src/rpc/http_client.cpp @@ -1,9 +1,10 @@ #include "http_client.h" #include #include -#include #include "common/string_util.h" -#include "cpr/ssl_options.h" +#include +#include +#include #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "rpc.http_client" @@ -130,6 +131,34 @@ void http_client::copy_params_from(const http_client& other) { auth = other.auth; } +nlohmann::json http_client::json_rpc(std::string_view method, std::optional params) { + nlohmann::json shell{ + {"id", json_rpc_id++}, + {"jsonrpc", "2.0"}, + {"method", method} + }; + if (params) + shell["params"] = std::move(*params); + + cpr::Response res = post("json_rpc", shell.dump(), {{"Content-Type", "application/json; charset=utf-8"}}); + + nlohmann::json result; + try { + auto response = nlohmann::json::parse(res.text); + + if (auto err = response.find("error"); err != response.end()) + throw http_client_response_error{false, (*err)["code"].get(), + "JSON RPC returned an error response: " + (*err)["message"].get()}; + + if (auto res = response.find("result"); res != response.end()) + result = std::move(*res); + + } catch (const nlohmann::json::parse_error& e) { + throw http_client_serialization_error{"Failed to deserialize response for json_rpc request for " + std::string{method} + ": " + e.what()}; + } + + return result; +} cpr::Response http_client::post(const std::string& uri, cpr::Body body, cpr::Header header) { if (base_url.str().empty()) diff --git a/src/rpc/http_client.h b/src/rpc/http_client.h index ffa92172d4f..588b1d7857f 100755 --- a/src/rpc/http_client.h +++ b/src/rpc/http_client.h @@ -17,6 +17,8 @@ #include #include +#include + namespace cryptonote::rpc { using namespace std::literals; @@ -143,6 +145,24 @@ class http_client /// Copies parameters (base url, timeout, authentication) from another http_client. void copy_params_from(const http_client& other); + /// Makes a JSON-RPC request; that is, a POST request to /json_rpc with a proper JSON-RPC wrapper + /// around some json data as the body. On a successful response the returned json body is + /// returned. + /// + /// \param method - the end-point to be passed as the "method" parameter of the JSON-RPC request. + /// \param params - the JSON to be sent as the JSON-RPC "params" value. Omit (or pass nullopt) to + /// omit the params value entirely. + /// + /// \returns nlohmann::json of the inner "result" field of the response, on success. + /// + /// \throws rpc::http_client_error on connection-related failure + /// \throws rpc::http_client_serialization_error on a serialization failure + /// \throws rpc::http_client_response_error on a successful HTTP request that returns a json_rpc + /// error, or on an HTTP request that returns an HTTP error code. + nlohmann::json json_rpc(std::string_view method, std::optional params = std::nullopt); + + /// FIXME: drop this. + /// /// Makes a JSON-RPC request; that is, a POST request to /json_rpc with a proper JSON-RPC wrapper /// around the serialized json data as the body. On a successful response the response is /// deserialized into a RPC::response which is returned. diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index 9fa72ea02c1..b525d9693f5 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -208,7 +208,7 @@ namespace cryptonote::rpc { bool aborted{false}; bool replied{false}; bool jsonrpc{false}; - std::string jsonrpc_id; // pre-formatted json value + nlohmann::json jsonrpc_id{nullptr}; std::vector> extra_headers; // Extra headers to send // If we have to drop the request because we are overloaded we want to reply with an error (so @@ -218,7 +218,7 @@ namespace cryptonote::rpc { if (replied || aborted) return; http.loop_defer([&http=http, &res=res, jsonrpc=jsonrpc] { if (jsonrpc) - http.jsonrpc_error_response(res, -32003, "Server busy, try again later"); + http.jsonrpc_error_response(res, -32003, "Server busy, try again later", nullptr); else http.error_response(res, http_server::HTTP_SERVICE_UNAVAILABLE, "Server busy, try again later"); }); @@ -337,7 +337,7 @@ namespace cryptonote::rpc { if (json_error != 0) { data.http.loop_defer([data=std::move(dataptr), json_error, msg=std::move(data.jsonrpc ? json_message : http_message)] { if (data->jsonrpc) - data->jsonrpc_error_response(data->res, json_error, msg); + data->jsonrpc_error_response(data->res, json_error, msg, data->jsonrpc_id); else data->error_response(data->res, http_server::HTTP_ERROR, msg.empty() ? std::nullopt : std::make_optional(msg)); }); @@ -542,51 +542,44 @@ namespace cryptonote::rpc { else body = (buffer += d); - auto& [ps, st_entry] = var::get(data->request.body = jsonrpc_params{}); - if(!ps.load_from_json(body)) - return data->jsonrpc_error_response(data->res, -32700, "Parse error"); - - epee::serialization::storage_entry id{std::string{}}; - ps.get_value("id", id, nullptr); + nlohmann::json jsonrpc; + try { + jsonrpc = nlohmann::json::parse(body); + } catch (const std::exception& e) { + return data->jsonrpc_error_response(data->res, -32700, "Parse error", nullptr); + } - std::string method; - if(!ps.get_value("method", method, nullptr)) - { + data->jsonrpc_id = std::move(jsonrpc["id"]); + const std::string* method; + try { + method = &jsonrpc["method"].get_ref(); + } catch (const std::exception& e) { MINFO("Invalid JSON RPC request from " << data->request.context.remote << ": no 'method' in request"); - return data->jsonrpc_error_response(data->res, -32600, "Invalid Request", id); + return data->jsonrpc_error_response(data->res, -32600, "Invalid Request", data->jsonrpc_id); } - auto it = rpc_commands.find(method); - if (it == rpc_commands.end() || it->second->is_binary) - { - MINFO("Invalid JSON RPC request from " << data->request.context.remote << ": method '" << method << "' is invalid"); - return data->jsonrpc_error_response(data->res, -32601, "Method not found", id); + if (auto it = rpc_commands.find(*method); + it != rpc_commands.end() && !it->second->is_binary) + data->call = it->second.get(); + else { + MINFO("Invalid JSON RPC request from " << data->request.context.remote << ": method '" << *method << "' is invalid"); + return data->jsonrpc_error_response(data->res, -32601, "Method not found", data->jsonrpc_id); } - data->call = it->second.get(); if (restricted && !data->call->is_public) { - MWARNING("Invalid JSON RPC request from " << data->request.context.remote << ": method '" << method << "' is restricted"); - return data->jsonrpc_error_response(data->res, 403, "Forbidden; this command is not available over public RPC", id); + MWARNING("Invalid JSON RPC request from " << data->request.context.remote << ": method '" << *method << "' is restricted"); + return data->jsonrpc_error_response(data->res, 403, "Forbidden; this command is not available over public RPC", data->jsonrpc_id); } - MDEBUG("Incoming JSON RPC request for " << method << " from " << data->request.context.remote); - - { - std::ostringstream o; - epee::serialization::dump_as_json(o, id, 0 /*indent*/, false /*newlines*/); - data->jsonrpc_id = o.str(); - } + MDEBUG("Incoming JSON RPC request for " << *method << " from " << data->request.context.remote); - // Try to load "params" into a generic epee value; if it fails (because there is no "params") - // then we replace request.body with an empty string (instead of the epee jsonrpc_params - // alternative) to signal that no params were provided at all. - if (!ps.get_value("params", st_entry, nullptr)) - data->request.body = ""sv; + if (auto it = jsonrpc.find("params"); it != jsonrpc.end()) + data->request.body = *it; auto& omq = data->core_rpc.get_core().get_omq(); std::string cat{data->call->is_public ? "rpc" : "admin"}; - std::string cmd{"jsonrpc:" + method}; // Used for LMQ job logging; prefixed with jsonrpc: so we can distinguish it + std::string cmd{"jsonrpc:" + *method}; // Used for LMQ job logging; prefixed with jsonrpc: so we can distinguish it std::string remote{data->request.context.remote}; omq.inject_task(std::move(cat), std::move(cmd), std::move(remote), [data=std::move(data)] { invoke_rpc(std::move(data)); }); }); diff --git a/src/rpc/http_server_base.cpp b/src/rpc/http_server_base.cpp index 129afb56679..d91c3531c2a 100755 --- a/src/rpc/http_server_base.cpp +++ b/src/rpc/http_server_base.cpp @@ -9,6 +9,8 @@ #include "epee/net/jsonrpc_structs.h" #include "epee/storages/portable_storage_template_helper.h" +#include + namespace cryptonote::rpc { /// Checks an Authorization header for Basic login credentials. @@ -75,23 +77,20 @@ namespace cryptonote::rpc { } // Similar to the above, but for JSON errors (which are 200 OK + error embedded in JSON) - void http_server_base::jsonrpc_error_response(HttpResponse& res, int code, std::string message, std::optional id) const + void http_server_base::jsonrpc_error_response(HttpResponse& res, int code, std::string message, nlohmann::json id) const { - epee::json_rpc::error_response rsp; - rsp.jsonrpc = "2.0"; - if (id) - rsp.id = *id; - rsp.error.code = code; - rsp.error.message = std::move(message); - std::string body; - epee::serialization::store_t_to_json(rsp, body); - if (body.capacity() > body.size()) - body += '\n'; res.writeStatus("200 OK"sv); res.writeHeader("Server", m_server_header); res.writeHeader("Content-Type", "application/json"); if (m_closing) res.writeHeader("Connection", "close"); - res.end(body); + res.end(nlohmann::json{ + {"jsonrpc", "2.0"}, + {"id", std::move(id)}, + {"error", nlohmann::json{ + {"code", code}, + {"message", std::move(message)} + }} + }.dump()); if (m_closing) res.close(); } diff --git a/src/rpc/http_server_base.h b/src/rpc/http_server_base.h index af08bb1f669..ecc33201688 100755 --- a/src/rpc/http_server_base.h +++ b/src/rpc/http_server_base.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -40,7 +41,7 @@ namespace cryptonote::rpc { HttpResponse& res, int code, std::string message, - std::optional = std::nullopt) const; + nlohmann::json id) const; // Posts a callback to the uWebSockets thread loop controlling this connection; all writes must // be done from that thread, and so this method is provided to defer a callback from another diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index 3850a569277..ae3af7e1638 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -4,6 +4,7 @@ #include "oxenmq/oxenmq.h" #include +// FIXME: Rename this to omq_server.{h,cpp} #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc" @@ -199,7 +200,8 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog request.context.admin = m.access.auth >= AuthLevel::admin; request.context.source = rpc_source::omq; request.context.remote = m.remote; - request.body = m.data.empty() ? ""sv : m.data[0]; + if (!m.data.empty()) + request.body = m.data[0]; try { m.send_reply(LMQ_OK, call.invoke(std::move(request), rpc_)); diff --git a/utils/lmq-rpc.py b/utils/lmq-rpc.py index a5ac2e5068b..16021da42eb 100755 --- a/utils/lmq-rpc.py +++ b/utils/lmq-rpc.py @@ -72,7 +72,11 @@ print("(empty reply data)", file=sys.stderr) else: for x in m[3:]: - print(x.decode(), end="\n\n") + if x.startswith(b'd'): + print(x, end="\n\n") + else: + print(x.decode(), end="\n\n") + else: print("Request timed out", file=sys.stderr) socket.close(linger=0) From fea8bb632e17d07c9d47a44b366268cea6aff805 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 16 Apr 2025 23:16:19 +0530 Subject: [PATCH 019/182] lmq-rpc.py: curve:// url support and binary bt printing --- utils/lmq-rpc.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/utils/lmq-rpc.py b/utils/lmq-rpc.py index 16021da42eb..9167a9734eb 100755 --- a/utils/lmq-rpc.py +++ b/utils/lmq-rpc.py @@ -20,7 +20,7 @@ socket.setsockopt(zmq.HANDSHAKE_IVL, 5000) #socket.setsockopt(zmq.IMMEDIATE, 1) -if len(sys.argv) > 1 and any(sys.argv[1].startswith(x) for x in ("ipc://", "tcp://")): +if len(sys.argv) > 1 and any(sys.argv[1].startswith(x) for x in ("ipc://", "tcp://", "curve://")): remote = sys.argv[1] del sys.argv[1] else: @@ -28,6 +28,18 @@ curve_pubkey = b'' my_privkey, my_pubkey = b'', b'' + +# If given a curve://whatever/pubkey argument then transform it into 'tcp://whatever' and put the +# 'pubkey' back into argv to be handled below. +if remote.startswith("curve://"): + pos = remote.rfind('/') + pkhex = remote[pos+1:] + remote = "tcp://" + remote[8:pos] + if len(pkhex) != 64 or not all(x in "0123456789abcdefABCDEF" for x in pkhex): + print("curve:// addresses must be in the form curve://HOST:PORT/REMOTE_PUBKEY_HEX", file=sys.stderr) + sys.exit(1) + sys.argv[1:0] = [pkhex] + if len(sys.argv) > 1 and len(sys.argv[1]) == 64 and all(x in "0123456789abcdefABCDEF" for x in sys.argv[1]): curve_pubkey = bytes.fromhex(sys.argv[1]) del sys.argv[1] @@ -72,8 +84,9 @@ print("(empty reply data)", file=sys.stderr) else: for x in m[3:]: - if x.startswith(b'd'): - print(x, end="\n\n") + print("{} bytes data part:".format(len(x)), file=sys.stderr) + if any(x.startswith(y) for y in (b'd', b'l', b'i')) and x.endswith(b'e'): + sys.stdout.buffer.write(x) else: print(x.decode(), end="\n\n") From d86aeff2f19bfda9092686c47b4e6ebfc2d5202f Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 16 Apr 2025 23:17:48 +0530 Subject: [PATCH 020/182] Add a version of tools::join that also transforms --- src/common/string_util.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/common/string_util.h b/src/common/string_util.h index 945989446a9..0d616934ca3 100755 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "epee/span.h" // epee namespace tools { @@ -74,6 +75,23 @@ std::string join(std::string_view delimiter, It begin, It end) { template std::string join(std::string_view delimiter, const Container& c) { return join(delimiter, c.begin(), c.end()); } +/// Similar to join(), but first applies a transformation to each element. +template +std::string join_transform(std::string_view delimiter, It begin, It end, UnaryOperation transform) { + std::ostringstream o; + if (begin != end) + o << transform(*begin++); + while (begin != end) + o << delimiter << transform(*begin++); + return o.str(); +} + +/// Wrapper around the above that takes a container and passes c.begin(), c.end(). +template +std::string join_transform(std::string_view delimiter, const Container& c, UnaryOperation&& transform) { + return join_transform(delimiter, c.begin(), c.end(), std::forward(transform)); +} + /// Simple version of whitespace trimming: mutates the given string view to remove leading /// space, \t, \r, \n. (More exotic and locale-dependent whitespace is not removed). void trim(std::string_view& s); From b3ed2dbbba00b4c7e8f8d0119add8c41c8ce2417 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 17 Apr 2025 00:04:34 +0530 Subject: [PATCH 021/182] Move json binary handling code; teach it about vectors --- src/cryptonote_core/cryptonote_core.cpp | 16 ++-- src/daemon/rpc_command_executor.cpp | 31 +++---- src/daemon/rpc_command_executor.h | 2 +- src/rpc/CMakeLists.txt | 1 + src/rpc/core_rpc_server.cpp | 19 ++--- src/rpc/core_rpc_server_command_parser.cpp | 85 +++++-------------- src/rpc/core_rpc_server_commands_defs.cpp | 9 --- src/rpc/core_rpc_server_commands_defs.h | 49 +---------- src/rpc/rpc_binary.cpp | 42 ++++++++++ src/rpc/rpc_binary.h | 94 ++++++++++++++++++++++ 10 files changed, 196 insertions(+), 152 deletions(-) create mode 100644 src/rpc/rpc_binary.cpp create mode 100644 src/rpc/rpc_binary.h diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 3b347cf7afa..372dbcccef6 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -389,7 +389,7 @@ namespace cryptonote bool args_okay = true; if (m_quorumnet_port == 0) { - MERROR("Quorumnet port cannot be 0; please specify a valid port to listen on with: '--" << arg_quorumnet_port.name << " '"); + MFATAL("Quorumnet port cannot be 0; please specify a valid port to listen on with: '--" << arg_quorumnet_port.name << " '"); args_okay = false; } @@ -397,7 +397,7 @@ namespace cryptonote if (pub_ip.size()) { if (!epee::string_tools::get_ip_int32_from_string(m_mn_public_ip, pub_ip)) { - MERROR("Unable to parse IPv4 public address from: " << pub_ip); + MFATAL("Unable to parse IPv4 public address from: " << pub_ip); args_okay = false; } @@ -405,19 +405,19 @@ namespace cryptonote if (m_master_node_list.debug_allow_local_ips) { MWARNING("Address given for public-ip is not public; allowing it because dev-allow-local-ips was specified. This master node WILL NOT WORK ON THE PUBLIC BELDEX NETWORK!"); } else { - MERROR("Address given for public-ip is not public: " << epee::string_tools::get_ip_string_from_int32(m_mn_public_ip)); + MFATAL("Address given for public-ip is not public: " << epee::string_tools::get_ip_string_from_int32(m_mn_public_ip)); args_okay = false; } } } else { - MERROR("Please specify an IPv4 public address which the master node & storage server is accessible from with: '--" << arg_public_ip.name << " '"); + MFATAL("Please specify an IPv4 public address which the master node & storage server is accessible from with: '--" << arg_public_ip.name << " '"); args_okay = false; } if (!args_okay) { - MERROR("IMPORTANT: One or more required master node-related configuration settings/options were omitted or invalid; " + MFATAL("IMPORTANT: One or more required master node-related configuration settings/options were omitted or invalid; " << "please fix them and restart beldexd."); return false; } @@ -627,7 +627,7 @@ namespace cryptonote // make sure the data directory exists, and try to lock it if (std::error_code ec; !fs::is_directory(folder, ec) && !fs::create_directories(folder, ec) && ec) { - MERROR("Failed to create directory " + folder.u8string() + (ec ? ": " + ec.message() : ""s)); + MFATAL("Failed to create directory " + folder.u8string() + (ec ? ": " + ec.message() : ""s)); return false; } @@ -654,7 +654,7 @@ namespace cryptonote // reset the db by removing the database file before opening it if (!db->remove_data_file(folder)) { - MERROR("Failed to remove data file in " << folder); + MFATAL("Failed to remove data file in " << folder); return false; } fs::remove(bns_db_file_path); @@ -889,7 +889,7 @@ namespace cryptonote try { generate_pair(privkey, pubkey); } catch (const std::exception& e) { - MERROR("failed to generate keypair " << e.what()); + MFATAL("failed to generate keypair " << e.what()); return false; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 69289d9d2b4..59f9ddbb4c1 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -60,6 +60,7 @@ #define BELDEX_DEFAULT_LOG_CATEGORY "daemon" using namespace cryptonote::rpc; +using nlohmann::json; using cryptonote::hf; @@ -256,13 +257,13 @@ static auto try_running(Callback code, std::string_view error_prefix) -> std::op } } -nlohmann::json rpc_command_executor::invoke( +json rpc_command_executor::invoke( std::string_view method, bool public_method, - std::optional params, + std::optional params, bool check_status_ok) { - nlohmann::json result; + json result; if (auto* rpc_client = std::get_if(&m_rpc)) { result = rpc_client->json_rpc(method, std::move(params)); @@ -270,13 +271,13 @@ nlohmann::json rpc_command_executor::invoke( assert(m_omq); auto conn = std::get(m_rpc); auto endpoint = (public_method ? "rpc." : "admin.") + std::string{method}; - std::promise result_p; + std::promise result_p; m_omq->request(conn, endpoint, [&result_p](bool success, auto data) { try { if (!success) throw std::runtime_error{"Request timed out"}; if (data.size() >= 2 && data[0] == "200") - result_p.set_value(nlohmann::json::parse(data[1])); + result_p.set_value(json::parse(data[1])); else throw std::runtime_error{"RPC method failed: " + ( data.empty() ? "empty response" : @@ -465,7 +466,7 @@ bool rpc_command_executor::hide_hash_rate() { } bool rpc_command_executor::show_difficulty() { - auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) return false; auto& info = *maybe_info; @@ -523,7 +524,7 @@ static float get_sync_percentage(const GET_INFO::response &ires) } bool rpc_command_executor::show_status() { - auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) return false; auto& info = *maybe_info; @@ -804,7 +805,7 @@ bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint // negative: relative to the end if (start_block_index < 0) { - auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) return false; auto& info = *maybe_info; @@ -894,7 +895,7 @@ bool rpc_command_executor::set_log_categories(std::string categories) { bool rpc_command_executor::print_height() { if (auto height = try_running([this] { - return invoke(std::nullopt).at("height").get(); + return invoke().at("height").get(); }, "Failed to retrieve height")) { tools::success_msg_writer() << *height; return true; @@ -1109,7 +1110,7 @@ bool rpc_command_executor::print_transaction_pool_short() { bool rpc_command_executor::print_transaction_pool_stats() { GET_TRANSACTION_POOL_STATS::response res{}; auto full_reward_zone = try_running([this] { - return invoke(std::nullopt).at("block_size_limit").get() / 2; + return invoke().at("block_size_limit").get() / 2; }, "Failed to retrieve node info"); if (!full_reward_zone) return false; @@ -1385,7 +1386,7 @@ bool rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t count bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, uint64_t last_blocks) { auto height = try_running([this] { - return invoke(std::nullopt).at("height").get(); + return invoke().at("height").get(); }, "Failed to retrieve node info"); if (!height) return false; @@ -1473,7 +1474,7 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) { - auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) return false; auto& info = *maybe_info; @@ -1857,7 +1858,7 @@ bool rpc_command_executor::print_mn(const std::vector &args) req.master_node_pubkeys.push_back(arg); } - auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) return false; auto& info = *maybe_info; @@ -2046,7 +2047,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) auto scoped_log_cats = std::unique_ptr(new clear_log_categories()); // Check if the daemon was started in master Node or not - auto maybe_info = try_running([this] { return invoke(std::nullopt); }, "Failed to retrieve node info"); + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) return false; auto& info = *maybe_info; @@ -2621,7 +2622,7 @@ bool rpc_command_executor::set_bootstrap_daemon( bool rpc_command_executor::version() { auto version = try_running([this] { - return invoke(std::nullopt).at("version").get(); + return invoke().at("version").get(); }, "Failed to retrieve node info"); if (!version) return false; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 6b2f9a1baa6..3c765137ba4 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -127,7 +127,7 @@ class rpc_command_executor final { /// @param check_status_ok whether we require the result to have a "status" key set to STATUS_OK /// to consider the request successful template && !cryptonote::rpc::FIXME_has_nested_response_v, int> = 0> - nlohmann::json invoke(std::optional params, bool check_status_ok = true) + nlohmann::json invoke(std::optional params = std::nullopt, bool check_status_ok = true) { return invoke(RPC::names()[0], std::is_base_of_v, std::move(params), check_status_ok); } diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 0595966753d..98b2c783892 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(rpc_commands core_rpc_server_commands_defs.cpp core_rpc_server_command_parser.cpp + rpc_binary.cpp ) add_library(rpc_server_base diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a17aab05069..7ba0b8781c3 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -74,12 +74,13 @@ namespace cryptonote::rpc { + using nlohmann::json; namespace { - oxenmq::bt_value json_to_bt(nlohmann::json&& j) { + oxenc::bt_value json_to_bt(json&& j) { using namespace oxenmq; if (j.is_object()) { - bt_dict res; + oxenc::bt_dict res; for (auto& [k, v] : j.items()) { if (v.is_null()) continue; // skip k-v pairs with a null v (for other nulls we fail). @@ -88,7 +89,7 @@ namespace cryptonote::rpc { return res; } if (j.is_array()) { - bt_list res; + oxenc::bt_list res; for (auto& v : j) res.push_back(json_to_bt(std::move(v))); return res; @@ -123,9 +124,9 @@ namespace cryptonote::rpc { parse_request(rpc, oxenc::bt_dict_consumer{*body}); } else - parse_request(rpc, nlohmann::json::parse(*body)); - } else if (auto* json = std::get_if(&request.body)) { - parse_request(rpc, std::move(*json)); + parse_request(rpc, json::parse(*body)); + } else if (auto* j = std::get_if(&request.body)) { + parse_request(rpc, std::move(*j)); } else { assert(std::holds_alternative(request.body)); parse_request(rpc, std::monostate{}); @@ -137,7 +138,7 @@ namespace cryptonote::rpc { server.invoke(rpc, std::move(request.context)); if (rpc.response.is_null()) - rpc.response = nlohmann::json::object(); + rpc.response = json::object(); if (rpc.is_bt()) return bt_serialize(json_to_bt(std::move(rpc.response))); @@ -430,7 +431,7 @@ namespace cryptonote::rpc { if (context.admin) { bool mn = m_core.master_node(); - info.response["master_node"] = sn; + info.response["master_node"] = mn; info.response["start_time"] = m_core.get_start_time(); if (mn) { info.response["last_storage_server_ping"] = m_core.m_last_storage_server_ping.load(); @@ -451,7 +452,7 @@ namespace cryptonote::rpc { info.response["database_size"] = context.admin ? db_size : round_up(db_size, 1'000'000'000); info.response["version"] = context.admin ? BELDEX_VERSION_FULL : std::to_string(BELDEX_VERSION[0]); info.response["status_line"] = context.admin ? m_core.get_status_string() : - "v" + std::to_string(BELDEX_VERSION[0]) + "; Height: " + std::to_string(res.height); + "v" + std::to_string(BELDEX_VERSION[0]) + "; Height: " + std::to_string(height); info.response["status"] = STATUS_OK; } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 089fcc7f76b..32726ef7ea9 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -6,65 +6,7 @@ #include namespace cryptonote::rpc { - - // Binary types that we support as input parameters. For json, these must be specified as hex or - // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. - template - inline constexpr bool is_binary_parameter = false; - template <> - inline constexpr bool is_binary_parameter = true; - - // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw - // bytes. - template >> - void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) { - constexpr size_t raw_size = sizeof(T); - constexpr size_t hex_size = raw_size * 2; - constexpr size_t b64_padded = (raw_size + 2) / 3 * 4; - constexpr size_t b64_padding = raw_size % 3 == 1 ? 2 : raw_size % 3 == 2 ? 1 : 0; - constexpr size_t b64_unpadded = b64_padded - b64_padding; - constexpr std::string_view b64_padding_string = b64_padding == 2 ? "=="sv : b64_padding == 1 ? "="sv : ""sv; - if (allow_raw && bytes.size() == raw_size) { - std::memcpy(&val, bytes.data(), bytes.size()); - return; - } else if (bytes.size() == hex_size) { - if (oxenc::is_hex(bytes)){ - oxenc::from_hex(bytes.begin(), bytes.end(), reinterpret_cast(&val)); - return; - } - } else if (bytes.size() == b64_unpadded || - (b64_padding > 0 && bytes.size() == b64_padded && bytes.substr(b64_unpadded) == b64_padding_string)) { - if (oxenc::is_base64(bytes)){ - oxenc::from_base64(bytes.begin(), bytes.end(), reinterpret_cast(&val)); - return; - } - } - - throw std::runtime_error{"Invalid binary value: unexpected size and/or encoding"}; - } - -} - -// Specializations of binary types for deserialization; when receiving these from json we expect -// them encoded in hex or base64. These may *not* be used for serialization, and will throw if so -// invoked; for serialization you need to use RPC_COMMAND::response_binary instead. -namespace nlohmann { - template - struct adl_serializer>> { - static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); - - static void to_json(json& j, const T&) { - throw std::logic_error{"Internal error: binary types are not directly serialization"}; - } - static void from_json(const json& j, T& val) { - load_binary_parameter(j.get(), false /*no raw*/, val); - } - }; -} - - -namespace cryptonote::rpc { - + using nlohmann::json; namespace { // Checks that key names are given in ascending order @@ -95,7 +37,7 @@ namespace cryptonote::rpc { using oxenc::bt_dict_consumer; - using json_range = std::pair; + using json_range = std::pair; // Advances the dict consumer to the first element >= the given name. Returns true if found, // false if it advanced beyond the requested name. This is exactly the same as @@ -121,8 +63,20 @@ namespace cryptonote::rpc { val = d.consume_string_view(); else if constexpr (is_binary_parameter) load_binary_parameter(d.consume_string_view(), true /*allow raw*/, val); + else if constexpr (is_binary_vector) { + val.clear(); + auto lc = d.consume_list_consumer(); + while (!lc.is_finished()) + load_binary_parameter(lc.consume_string_view(), true /*allow raw*/, val.emplace_back()); + } else if constexpr (std::is_same_v) val = std::chrono::system_clock::time_point{std::chrono::seconds{d.consume_integer()}}; + else if constexpr (std::is_same_v> || std::is_same_v>) { + val.clear(); + auto lc = d.consume_list_consumer(); + while (!lc.is_finished()) + val.emplace_back(lc.consume_string_view()); + } else static_assert(std::is_same_v, "Unsupported load_value type"); } @@ -166,8 +120,11 @@ namespace cryptonote::rpc { val = i; } else if constexpr (std::is_same_v || std::is_same_v) { val = e.get(); - } else if constexpr (is_binary_parameter) { - load_binary_parameter(e.get(), false /*no raw bytes*/, val); + } else if constexpr (is_binary_parameter || + is_binary_vector || + std::is_same_v> || + std::is_same_v>) { + val = e.get(); } else if constexpr (std::is_same_v) { val = std::chrono::system_clock::time_point{std::chrono::seconds{e.get()}}; } else { @@ -201,8 +158,8 @@ namespace cryptonote::rpc { template void get_values(Input& in, std::string_view name, T&& val, More&&... more) { if constexpr (std::is_same_v) { - if (auto* json = std::get_if(&in)) { - json_range r{json->cbegin(), json->cend()}; + if (auto* json_in = std::get_if(&in)) { + json_range r{json_in->cbegin(), json_in->cend()}; get_values(r, name, val, std::forward(more)...); } else if (auto* dict = std::get_if(&in)) { get_values(*dict, name, val, std::forward(more)...); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 55da1acdffa..fc8913266e1 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -4,15 +4,6 @@ namespace cryptonote::rpc { -nlohmann::json& json_binary_proxy::operator=(std::string_view binary_data) { - switch (format) { - case fmt::bt: return e = binary_data; - case fmt::hex: return e = oxenc::to_hex(binary_data); - case fmt::base64: return e = oxenc::to_base64(binary_data); - } - throw std::runtime_error{"Internal error: invalid binary encoding"}; -} - void RPC_COMMAND::set_bt() { bt = true; response_b64.format = json_binary_proxy::fmt::bt; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index efe485dfa1c..db555081c29 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -51,6 +51,7 @@ #include "cryptonote_core/master_node_list.h" #include "common/beldex.h" +#include "rpc_binary.h" #include #include #include @@ -102,48 +103,6 @@ namespace rpc { } } - // Wrapper around a nlohmann::json - class json_binary_proxy { - nlohmann::json& e; - enum class fmt { bt, hex, base64 } format; - friend struct RPC_COMMAND; - explicit json_binary_proxy(nlohmann::json& elem, fmt format) - : e{elem}, format{format} {} - json_binary_proxy() = delete; - - public: - json_binary_proxy(const json_binary_proxy&) = default; - json_binary_proxy(json_binary_proxy&&) = default; - - /// Dereferencing a proxy element accesses the underlying nlohmann::json - nlohmann::json& operator*() { return e; } - nlohmann::json* operator->() { return &e; } - - /// Descends into the json object, returning a new binary value proxy around the child element. - template - json_binary_proxy operator[](T&& key) { - return json_binary_proxy{e[std::forward(key)], format}; - } - - /// Assigns binary data from a string_view/string/etc. - nlohmann::json& operator=(std::string_view binary_data); - - /// Assigns binary data from a string_view over a 1-byte, non-char type (e.g. unsigned char or - /// uint8_t). - template , int> = 0> - nlohmann::json& operator=(std::basic_string_view binary_data) { - return *this = std::string_view{reinterpret_cast(binary_data.data()), binary_data.size()}; - } - - /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its - /// contents as the binary value. - template && !std::is_scalar_v && std::is_trivial_v && std::has_unique_object_representations_v, int> = 0> - nlohmann::json& operator=(const T& val) { - return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; - } - }; - /// Base class that all RPC commands must inherit from (either directly or via one or more of the /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be @@ -173,11 +132,9 @@ namespace rpc { /// std::string binary = some_binary_data(); /// cmd.response["binary_value"] = is_bt ? binary : oxenc::to_hex(binary); /// - /// or, more conveniently, using one of the shortcut methods: - /// - /// cmd.response_binary(cmd.response["binary_value"], some_binary_data()); - /// cmd.response_binary("binary_value", some_binary_data()); + /// or, more conveniently, using the shortcut interface: /// + /// cmd.response_hex["binary_value"] = some_binary_data(); nlohmann::json response; /// Proxy object that is used to set binary data in `response`, encoding it as hex if this diff --git a/src/rpc/rpc_binary.cpp b/src/rpc/rpc_binary.cpp new file mode 100644 index 00000000000..eef636c4737 --- /dev/null +++ b/src/rpc/rpc_binary.cpp @@ -0,0 +1,42 @@ +#include "rpc_binary.h" +#include +#include + +namespace cryptonote::rpc { + + void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) { + if (allow_raw && bytes.size() == raw_size) { + std::memcpy(val_data, bytes.data(), bytes.size()); + return; + } else if (bytes.size() == raw_size * 2) { + if (oxenc::is_hex(bytes)){ + oxenc::from_hex(bytes.begin(), bytes.end(), val_data); + return; + } + } else { + const size_t b64_padded = (raw_size + 2) / 3 * 4; + const size_t b64_padding = raw_size % 3 == 1 ? 2 : raw_size % 3 == 2 ? 1 : 0; + const size_t b64_unpadded = b64_padded - b64_padding; + const std::string_view b64_padding_string = b64_padding == 2 ? "=="sv : b64_padding == 1 ? "="sv : ""sv; + if (bytes.size() == b64_unpadded || + (b64_padding > 0 && bytes.size() == b64_padded && bytes.substr(b64_unpadded) == b64_padding_string)) { + if (oxenc::is_base64(bytes)){ + oxenc::from_base64(bytes.begin(), bytes.end(), val_data); + return; + } + } + } + + throw std::runtime_error{"Invalid binary value: unexpected size and/or encoding"}; + } + + nlohmann::json& json_binary_proxy::operator=(std::string_view binary_data) { + switch (format) { + case fmt::bt: return e = binary_data; + case fmt::hex: return e = oxenc::to_hex(binary_data); + case fmt::base64: return e = oxenc::to_base64(binary_data); + } + throw std::runtime_error{"Internal error: invalid binary encoding"}; + } + +} \ No newline at end of file diff --git a/src/rpc/rpc_binary.h b/src/rpc/rpc_binary.h new file mode 100644 index 00000000000..b4576a2809d --- /dev/null +++ b/src/rpc/rpc_binary.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include + +using namespace std::literals; + +namespace cryptonote::rpc { + + // Binary types that we support for rpc input/output. For json, these must be specified as hex or + // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. + template + inline constexpr bool is_binary_parameter = false; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + + template + inline constexpr bool is_binary_vector = false; + template + inline constexpr bool is_binary_vector> = is_binary_parameter; + + + void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); + + // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw + // bytes. + template >> + void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) { + load_binary_parameter_impl(bytes, sizeof(T), allow_raw, reinterpret_cast(&val)); + } + + // Wrapper around a nlohmann::json that assigns a binary value either as binary (for bt-encoding); + // or as hex or base64 (for json-encoding). + class json_binary_proxy { + public: + nlohmann::json& e; + enum class fmt { bt, hex, base64 } format; + explicit json_binary_proxy(nlohmann::json& elem, fmt format) + : e{elem}, format{format} {} + json_binary_proxy() = delete; + + json_binary_proxy(const json_binary_proxy&) = default; + json_binary_proxy(json_binary_proxy&&) = default; + + /// Dereferencing a proxy element accesses the underlying nlohmann::json + nlohmann::json& operator*() { return e; } + nlohmann::json* operator->() { return &e; } + + /// Descends into the json object, returning a new binary value proxy around the child element. + template + json_binary_proxy operator[](T&& key) { + return json_binary_proxy{e[std::forward(key)], format}; + } + + /// Assigns binary data from a string_view/string/etc. + nlohmann::json& operator=(std::string_view binary_data); + + /// Assigns binary data from a string_view over a 1-byte, non-char type (e.g. unsigned char or + /// uint8_t). + template , int> = 0> + nlohmann::json& operator=(std::basic_string_view binary_data) { + return *this = std::string_view{reinterpret_cast(binary_data.data()), binary_data.size()}; + } + + /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its + /// contents as the binary value. + template , int> = 0> + nlohmann::json& operator=(const T& val) { + return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; + } + }; + +} + +// Specializations of binary types for deserialization; when receiving these from json we expect +// them encoded in hex or base64. These may *not* be used for serialization, and will throw if so +// invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. +namespace nlohmann { + template + struct adl_serializer>> { + static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); + + static void to_json(json& j, const T&) { + throw std::logic_error{"Internal error: binary types are not directly serializable"}; + } + static void from_json(const json& j, T& val) { + cryptonote::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); + } + }; +} From 22f7f5d553bde2a37bd319ebbca76f81a774c10e Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 17 Apr 2025 11:44:00 +0530 Subject: [PATCH 022/182] print_money: allow trimming unused decimals --- .../cryptonote_format_utils.cpp | 40 +++++-------------- .../cryptonote_format_utils.h | 4 +- 2 files changed, 13 insertions(+), 31 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 529e1fd1a27..bb48b0aa4aa 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1100,36 +1100,18 @@ namespace cryptonote cn_fast_hash(blob.data(), blob.size(), res); } //--------------------------------------------------------------- - std::string get_unit(unsigned int decimal_point) - { - if (decimal_point == (unsigned int)-1) - decimal_point = beldex::DISPLAY_DECIMAL_POINT; - switch (decimal_point) - { - case 9: - return "beldex"; - case 6: - return "megarok"; - case 3: - return "kilorok"; - case 0: - return "rok"; - default: - ASSERT_MES_AND_THROW("Invalid decimal point specification: " << decimal_point); - } - } - //--------------------------------------------------------------- - std::string print_money(uint64_t amount, unsigned int decimal_point) - { - if (decimal_point == (unsigned int)-1) - decimal_point = beldex::DISPLAY_DECIMAL_POINT; - std::string s = std::to_string(amount); - if(s.size() < decimal_point+1) - { - s.insert(0, decimal_point+1 - s.size(), '0'); + std::string print_money(uint64_t amount, bool trim_insignificant) + { + std::string s = tools::int_to_string(amount); + if (s.size() <= beldex::DISPLAY_DECIMAL_POINT) + s.insert(0, beldex::DISPLAY_DECIMAL_POINT - s.size() + 1, '0'); + s.insert(s.size() - beldex::DISPLAY_DECIMAL_POINT, "."); + if (trim_insignificant) { + while (s.back() == '0') + s.pop_back(); + if (s.back() == '.') + s.pop_back(); } - if (decimal_point > 0) - s.insert(s.size() - decimal_point, "."); return s; } //--------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index ee37b2298e1..a98855df0a5 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -219,8 +219,8 @@ namespace cryptonote uint64_t get_block_height(const block& b); std::vector relative_output_offsets_to_absolute(const std::vector& off); std::vector absolute_output_offsets_to_relative(const std::vector& off); - std::string get_unit(unsigned int decimal_point = -1); - std::string print_money(uint64_t amount, unsigned int decimal_point = -1); + inline constexpr std::string_view get_unit() { return "BDX"sv; } + std::string print_money(uint64_t amount, bool trim_insignificant = false); std::string print_tx_verification_context (tx_verification_context const &tvc, transaction const *tx = nullptr); std::string print_vote_verification_context(vote_verification_context const &vvc, master_nodes::quorum_vote_t const *vote = nullptr); From 0fb26b621f0ea352967986ef948260e5de4fa5f1 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Thu, 17 Apr 2025 12:25:16 +0530 Subject: [PATCH 023/182] GET_NET_STATS - To new RPC format --- src/rpc/core_rpc_server.cpp | 13 ++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 16 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 28 +++++++++++------------ 4 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7ba0b8781c3..59c8eb36519 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -457,23 +457,20 @@ namespace cryptonote::rpc { info.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GET_NET_STATS::response core_rpc_server::invoke(GET_NET_STATS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_NET_STATS& get_net_stats, rpc_context context) { - GET_NET_STATS::response res{}; - PERF_TIMER(on_get_net_stats); // No bootstrap daemon check: Only ever get stats about local server - res.start_time = (uint64_t)m_core.get_start_time(); + get_net_stats.response["start_time"] = (uint64_t)m_core.get_start_time(); { std::lock_guard lock{epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in}; - epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(res.total_packets_in, res.total_bytes_in); + epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(get_net_stats.response["total_packets_in"], get_net_stats.response["total_bytes_in"]); } { std::lock_guard lock{epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_out}; - epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(res.total_packets_out, res.total_bytes_out); + epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(get_net_stats.response["total_packets_out"], get_net_stats.response["total_bytes_out"]); } - res.status = STATUS_OK; - return res; + get_net_stats.response["status"] = STATUS_OK; } namespace { //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 549c0d44eb9..d0989def257 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -201,6 +201,7 @@ namespace cryptonote::rpc { void invoke(GET_HEIGHT& req, rpc_context context); void invoke(GET_INFO& info, rpc_context context); void invoke(BNS_RESOLVE& resolve, rpc_context context); + void invoke(GET_NET_STATS& get_net_stats, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -220,7 +221,6 @@ namespace cryptonote::rpc { STOP_MINING::response invoke(STOP_MINING::request&& req, rpc_context context); MINING_STATUS::response invoke(MINING_STATUS::request&& req, rpc_context context); GET_OUTPUTS::response invoke(GET_OUTPUTS::request&& req, rpc_context context); - GET_NET_STATS::response invoke(GET_NET_STATS::request&& req, rpc_context context); SAVE_BC::response invoke(SAVE_BC::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index fc8913266e1..50c349df582 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -345,14 +345,14 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_NET_STATS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(start_time) - KV_SERIALIZE(total_packets_in) - KV_SERIALIZE(total_bytes_in) - KV_SERIALIZE(total_packets_out) - KV_SERIALIZE(total_bytes_out) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_NET_STATS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(start_time) +// KV_SERIALIZE(total_packets_in) +// KV_SERIALIZE(total_bytes_in) +// KV_SERIALIZE(total_packets_out) +// KV_SERIALIZE(total_bytes_out) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKCOUNT::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index db555081c29..ee4f988e4f4 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -745,23 +745,21 @@ namespace rpc { }; //----------------------------------------------- - BELDEX_RPC_DOC_INTROSPECT - struct GET_NET_STATS : LEGACY + /// Retrieve general information about the state of the network. + /// + /// Inputs: none. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p start_time something. + /// \p total_packets_in something. + /// \p total_bytes_in something. + /// \p total_packets_out something. + /// \p total_bytes_out something. + struct GET_NET_STATS : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_net_stats"); } - - struct request : EMPTY {}; - struct response - { - std::string status; - uint64_t start_time; - uint64_t total_packets_in; - uint64_t total_bytes_in; - uint64_t total_packets_out; - uint64_t total_bytes_out; - - KV_MAP_SERIALIZABLE - }; }; From a177299da83a2e19141c255efb682920abb3ed8d Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Thu, 17 Apr 2025 13:08:19 +0530 Subject: [PATCH 024/182] START_MINING and GET_OUTPUTS to new RPC format --- src/rpc/core_rpc_server.cpp | 75 ++++++++++++----------- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_commands_defs.cpp | 66 ++++++++++---------- src/rpc/core_rpc_server_commands_defs.h | 64 ++++++++++--------- 4 files changed, 108 insertions(+), 101 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 59c8eb36519..de5966c28dd 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -639,34 +639,35 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_OUTPUTS::response core_rpc_server::invoke(GET_OUTPUTS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_OUTPUTS& get_outputs, rpc_context context) { - GET_OUTPUTS::response res{}; - PERF_TIMER(on_get_outs); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + //TODO this bootstrap daemon call to work for new RPC design + //if (use_bootstrap_daemon_if_necessary(req, res)) + //return; - if (!context.admin && req.outputs.size() > GET_OUTPUTS::MAX_COUNT) { - res.status = "Too many outs requested"; + if (!context.admin && get_outputs.request["outputs"].size() > GET_OUTPUTS::MAX_COUNT) { + get_outputs.response["status"] = "Too many outs requested"; return res; } GET_OUTPUTS_BIN::request req_bin{}; - req_bin.outputs = req.outputs; - req_bin.get_txid = req.get_txid; + req_bin.outputs = get_outputs.request["outputs"]; + req_bin.get_txid = get_outputs.request["get_txid"]; GET_OUTPUTS_BIN::response res_bin{}; if (!m_core.get_outs(req_bin, res_bin)) { - res.status = "Failed"; - return res; + get_outputs.response["status"] = "Failed"; + return; } + get_output.response["outs"] = std::vector{}; + // convert to text for (const auto &i: res_bin.outs) { - res.outs.emplace_back(); - auto& outkey = res.outs.back(); + get_output.response["outs"].emplace_back(); + auto& outkey = get_output.response["outs"].back(); outkey.key = tools::type_to_hex(i.key); outkey.mask = tools::type_to_hex(i.mask); outkey.unlocked = i.unlocked; @@ -674,7 +675,7 @@ namespace cryptonote::rpc { outkey.txid = tools::type_to_hex(i.txid); } - res.status = STATUS_OK; + get_outputs.response["status"] = STATUS_OK; return res; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1216,24 +1217,22 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - START_MINING::response core_rpc_server::invoke(START_MINING::request&& req, rpc_context context) + void core_rpc_server::invoke(START_MINING& start_mining, rpc_context context) { - START_MINING::response res{}; - PERF_TIMER(on_start_mining); CHECK_CORE_READY(); cryptonote::address_parse_info info; - if(!get_account_address_from_str(info, m_core.get_nettype(), req.miner_address)) + if(!get_account_address_from_str(info, m_core.get_nettype(), start_mining.request['miner_address'])) { - res.status = "Failed, wrong address"; - LOG_PRINT_L0(res.status); - return res; + start_mining.response["status"] = "Failed, wrong address"; + LOG_PRINT_L0(start_mining.response["status"]); + return; } if (info.is_subaddress) { - res.status = "Mining to subaddress isn't supported yet"; - LOG_PRINT_L0(res.status); - return res; + start_mining.response["status"] = "Mining to subaddress isn't supported yet"; + LOG_PRINT_L0(start_mining.response["status"]); + return; } unsigned int concurrency_count = std::thread::hardware_concurrency() * 4; @@ -1246,27 +1245,31 @@ namespace cryptonote::rpc { // if there are more threads requested than the hardware supports // then we fail and log that. - if(req.threads_count > concurrency_count) + if(start_mining.request["threads_count"] > concurrency_count) { - res.status = "Failed, too many threads relative to CPU cores."; - LOG_PRINT_L0(res.status); - return res; + start_mining.response["status"] = "Failed, too many threads relative to CPU cores."; + LOG_PRINT_L0(start_mining.response["status"]); + return; } cryptonote::miner &miner= m_core.get_miner(); if (miner.is_mining()) { - res.status = "Already mining"; - return res; + start_mining.response["status"] = "Already mining"; + LOG_PRINT_L0(start_mining.response["status"]); + return; } - if(!miner.start(info.address, static_cast(req.threads_count), req.num_blocks)) + + if(!miner.start(info.address, static_cast(start_mining.request["threads_count"]), start_mining.request["num_blocks"])) { - res.status = "Failed, mining not started"; - LOG_PRINT_L0(res.status); - return res; + start_mining.response["status"] = "Failed, mining not started"; + LOG_PRINT_L0(start_mining.response["status"]); + return; } - res.status = STATUS_OK; - return res; + + start_mining.response["status"] = STATUS_OK; + LOG_PRINT_L0(start_mining.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ STOP_MINING::response core_rpc_server::invoke(STOP_MINING::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index d0989def257..338a9c92ac2 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -202,6 +202,8 @@ namespace cryptonote::rpc { void invoke(GET_INFO& info, rpc_context context); void invoke(BNS_RESOLVE& resolve, rpc_context context); void invoke(GET_NET_STATS& get_net_stats, rpc_context context); + void invoke(GET_OUTPUTS& get_outputs, rpc_context context); + void invoke(START_MINING& start_mining, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -217,10 +219,8 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: IS_KEY_IMAGE_SPENT::response invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context); SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); - START_MINING::response invoke(START_MINING::request&& req, rpc_context context); STOP_MINING::response invoke(STOP_MINING::request&& req, rpc_context context); MINING_STATUS::response invoke(MINING_STATUS::request&& req, rpc_context context); - GET_OUTPUTS::response invoke(GET_OUTPUTS::request&& req, rpc_context context); SAVE_BC::response invoke(SAVE_BC::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 50c349df582..cd17aa8f23f 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -240,26 +240,26 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::request) - KV_SERIALIZE(outputs) - KV_SERIALIZE(get_txid) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::request) +// KV_SERIALIZE(outputs) +// KV_SERIALIZE(get_txid) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::outkey) - KV_SERIALIZE(key) - KV_SERIALIZE(mask) - KV_SERIALIZE(unlocked) - KV_SERIALIZE(height) - KV_SERIALIZE(txid) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::outkey) +// KV_SERIALIZE(key) +// KV_SERIALIZE(mask) +// KV_SERIALIZE(unlocked) +// KV_SERIALIZE(height) +// KV_SERIALIZE(txid) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::response) - KV_SERIALIZE(outs) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::response) +// KV_SERIALIZE(outs) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(SEND_RAW_TX::request) @@ -280,25 +280,25 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SEND_RAW_TX::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(START_MINING::request) - KV_SERIALIZE(miner_address) - KV_SERIALIZE(threads_count) - KV_SERIALIZE_OPT(num_blocks, uint64_t{0}) - KV_SERIALIZE_OPT(slow_mining, false) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(START_MINING::request) +// KV_SERIALIZE(miner_address) +// KV_SERIALIZE(threads_count) +// KV_SERIALIZE_OPT(num_blocks, uint64_t{0}) +// KV_SERIALIZE_OPT(slow_mining, false) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(MINING_STATUS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(active) - KV_SERIALIZE(speed) - KV_SERIALIZE(threads_count) - KV_SERIALIZE(address) - KV_SERIALIZE(pow_algorithm) - KV_SERIALIZE(block_target) - KV_SERIALIZE(block_reward) - KV_SERIALIZE(difficulty) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(MINING_STATUS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(active) +// KV_SERIALIZE(speed) +// KV_SERIALIZE(threads_count) +// KV_SERIALIZE(address) +// KV_SERIALIZE(pow_algorithm) +// KV_SERIALIZE(block_target) +// KV_SERIALIZE(block_reward) +// KV_SERIALIZE(difficulty) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_INFO::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ee4f988e4f4..b95c231f765 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -547,7 +547,27 @@ namespace rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT + //----------------------------------------------- + /// Retrieve outputs + /// + /// Inputs: + /// + /// \p outputs Array of structure `get_outputs_out`. + /// \p get_txid Request the TXID/hash of the transaction as well. + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore untrusted ('true'), or when the daemon is fully synced ('false'). + /// \p outs List of outkey information. + /// + /// Outkey Information: + /// + /// \p key The public key of the output. + /// \p mask something + /// \p unlocked States if output is locked (`false`) or not (`true`). + /// \p height Block height of the output. + /// \p txid Transaction id. struct GET_OUTPUTS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_outs"); } @@ -555,14 +575,6 @@ namespace rpc { /// Maximum outputs that may be requested in a single request (unless admin) static constexpr size_t MAX_COUNT = 5000; - struct request - { - std::vector outputs; // Array of structure `get_outputs_out`. - bool get_txid; // Request the TXID/hash of the transaction as well. - - KV_MAP_SERIALIZABLE - }; - struct outkey { std::string key; // The public key of the output. @@ -573,15 +585,6 @@ namespace rpc { KV_MAP_SERIALIZABLE }; - - struct response - { - std::vector outs; // List of outkey information. - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -613,22 +616,23 @@ namespace rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Start mining on the daemon. + //----------------------------------------------- + /// Start mining on the daemon + /// + /// Inputs: + /// + /// \p miner_address Account address to mine to. + /// \p threads_count Number of mining threads to run. + /// \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). + /// \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily inteded for testing. + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. struct START_MINING : LEGACY { static constexpr auto names() { return NAMES("start_mining"); } - struct request - { - std::string miner_address; // Account address to mine to. - uint64_t threads_count; // Number of mining thread to run. - uint64_t num_blocks; // Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default) - bool slow_mining; // Do slow mining (i.e. don't allocate RandomX cache); primarily intended for testing - - KV_MAP_SERIALIZABLE - }; - struct response : STATUS {}; }; From 95760ac65cda2020070ea9f6b6db40724380a64a Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Thu, 17 Apr 2025 14:47:32 +0530 Subject: [PATCH 025/182] New RPC formats for STOP_MINING, SAVE_BC, STOP_DAEMON, GETBLOCKCOUNT --- src/rpc/core_rpc_server.cpp | 60 +++++++++--------- src/rpc/core_rpc_server.h | 8 +-- src/rpc/core_rpc_server_commands_defs.cpp | 8 +-- src/rpc/core_rpc_server_commands_defs.h | 76 +++++++++++++---------- 4 files changed, 79 insertions(+), 73 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index de5966c28dd..435bf2695ed 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1272,26 +1272,25 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - STOP_MINING::response core_rpc_server::invoke(STOP_MINING::request&& req, rpc_context context) + void core_rpc_server::invoke(STOP_MINING& stop_mining, rpc_context context) { - STOP_MINING::response res{}; - PERF_TIMER(on_stop_mining); cryptonote::miner &miner= m_core.get_miner(); if(!miner.is_mining()) { - res.status = "Mining never started"; - LOG_PRINT_L0(res.status); - return res; + stop_mining.response["status"] = "Mining never started"; + LOG_PRINT_L0(stop_mining.response["status"]); + return; } if(!miner.stop()) { - res.status = "Failed, mining not stopped"; - LOG_PRINT_L0(res.status); - return res; + stop_mining.response["status"] = "Failed, mining not stopped"; + LOG_PRINT_L0(stop_mining.response["status"]); + return; } - res.status = STATUS_OK; - return res; + stop_mining.response["status"] = STATUS_OK; + LOG_PRINT_L0(stop_mining.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ MINING_STATUS::response core_rpc_server::invoke(MINING_STATUS::request&& req, rpc_context context) @@ -1323,18 +1322,18 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - SAVE_BC::response core_rpc_server::invoke(SAVE_BC::request&& req, rpc_context context) + void core_rpc_server::invoke(SAVE_BC& save_bc, rpc_context context) { - SAVE_BC::response res{}; - PERF_TIMER(on_save_bc); if( !m_core.get_blockchain_storage().store_blockchain() ) { - res.status = "Error while storing blockchain"; - return res; + save_bc.response["status"] = "Error while storing blockchain"; + LOG_PRINT_L0(save_bc.response["status"]); + return; } - res.status = STATUS_OK; - return res; + save_bc.response["status"] = STATUS_OK; + LOG_PRINT_L0(save_bc.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ GET_PEER_LIST::response core_rpc_server::invoke(GET_PEER_LIST::request&& req, rpc_context context) @@ -1545,14 +1544,13 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - STOP_DAEMON::response core_rpc_server::invoke(STOP_DAEMON::request&& req, rpc_context context) + void core_rpc_server::invoke(STOP_DAEMON& stop_daemon, rpc_context context) { - STOP_DAEMON::response res{}; - PERF_TIMER(on_stop_daemon); m_p2p.send_stop_signal(); - res.status = STATUS_OK; - return res; + stop_daemon.response["status"] = STATUS_OK; + LOG_PRINT_L0(stop_daemon.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1582,22 +1580,22 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GETBLOCKCOUNT::response core_rpc_server::invoke(GETBLOCKCOUNT::request&& req, rpc_context context) + void core_rpc_server::invoke(GETBLOCKCOUNT& getblockcount, rpc_context context) { - GETBLOCKCOUNT::response res{}; - PERF_TIMER(on_getblockcount); { std::shared_lock lock{m_bootstrap_daemon_mutex}; if (m_should_use_bootstrap_daemon) { - res.status = "This command is unsupported for bootstrap daemon"; - return res; + getblockcount.response["status"] = "This command is unsupported for bootstrap daemon"; + LOG_PRINT_L0(getblockcount.response["status"]); + return; } } - res.count = m_core.get_current_blockchain_height(); - res.status = STATUS_OK; - return res; + getblockcount.response["count"] = m_core.get_current_blockchain_height(); + getblockcount.response["status"] = STATUS_OK; + LOG_PRINT_L0(getblockcount.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ GETBLOCKHASH::response core_rpc_server::invoke(GETBLOCKHASH::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 338a9c92ac2..bca9b52a36d 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -204,6 +204,10 @@ namespace cryptonote::rpc { void invoke(GET_NET_STATS& get_net_stats, rpc_context context); void invoke(GET_OUTPUTS& get_outputs, rpc_context context); void invoke(START_MINING& start_mining, rpc_context context); + void invoke(STOP_MINING& stop_mining, rpc_context context); + void invoke(SAVE_BC& save_bc, rpc_context context); + void invoke(STOP_DAEMON& stop_daemon, rpc_context context); + void invoke(GETBLOCKCOUNT& getblockcount, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -219,9 +223,7 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: IS_KEY_IMAGE_SPENT::response invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context); SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); - STOP_MINING::response invoke(STOP_MINING::request&& req, rpc_context context); MINING_STATUS::response invoke(MINING_STATUS::request&& req, rpc_context context); - SAVE_BC::response invoke(SAVE_BC::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); SET_LOG_HASH_RATE::response invoke(SET_LOG_HASH_RATE::request&& req, rpc_context context); @@ -231,14 +233,12 @@ namespace cryptonote::rpc { GET_TRANSACTION_POOL_HASHES::response invoke(GET_TRANSACTION_POOL_HASHES::request&& req, rpc_context context); GET_TRANSACTION_POOL_STATS::response invoke(GET_TRANSACTION_POOL_STATS::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - STOP_DAEMON::response invoke(STOP_DAEMON::request&& req, rpc_context context); GET_LIMIT::response invoke(GET_LIMIT::request&& req, rpc_context context); SET_LIMIT::response invoke(SET_LIMIT::request&& req, rpc_context context); OUT_PEERS::response invoke(OUT_PEERS::request&& req, rpc_context context); IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); POP_BLOCKS::response invoke(POP_BLOCKS::request&& req, rpc_context context); - GETBLOCKCOUNT::response invoke(GETBLOCKCOUNT::request&& req, rpc_context context); GETBLOCKHASH::response invoke(GETBLOCKHASH::request&& req, rpc_context context); GETBLOCKTEMPLATE::response invoke(GETBLOCKTEMPLATE::request&& req, rpc_context context); SUBMITBLOCK::response invoke(SUBMITBLOCK::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index cd17aa8f23f..93cd701fb6f 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -355,10 +355,10 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKCOUNT::response) - KV_SERIALIZE(count) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKCOUNT::response) +// KV_SERIALIZE(count) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() bool GETBLOCKHASH::request::load(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index b95c231f765..cd270476be5 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -626,7 +626,7 @@ namespace rpc { /// \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). /// \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily inteded for testing. /// - /// Output values available from a public RPC endpoint: + /// Output values available from a restricted/admin RPC endpoint: /// /// \p status General RPC status string. `"OK"` means everything looks good. struct START_MINING : LEGACY @@ -636,14 +636,17 @@ namespace rpc { struct response : STATUS {}; }; - BELDEX_RPC_DOC_INTROSPECT - // Stop mining on the daemon. + //----------------------------------------------- + /// Stop mining on the daemon. + /// + /// Inputs: none + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. struct STOP_MINING : LEGACY { static constexpr auto names() { return NAMES("stop_mining"); } - - struct request : EMPTY {}; - struct response : STATUS {}; }; BELDEX_RPC_DOC_INTROSPECT @@ -767,31 +770,33 @@ namespace rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Save the blockchain. The blockchain does not need saving and is always saved when modified, - // however it does a sync to flush the filesystem cache onto the disk for safety purposes against Operating System or Hardware crashes. + //----------------------------------------------- + /// Save the blockchain. The blockchain does not need saving and is always saved when modified, + /// however it does a sync to flush the filesystem cache onto the disk for safety purposes, + /// against Operating System or Hardware crashes. + /// + /// Inputs: none + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. struct SAVE_BC : LEGACY { static constexpr auto names() { return NAMES("save_bc"); } - - struct request : EMPTY {}; - struct response : STATUS {}; }; - BELDEX_RPC_DOC_INTROSPECT - // Look up how many blocks are in the longest chain known to the node. + //----------------------------------------------- + /// Look up how many blocks are in the longest chain known to the node. + /// + /// Inputs: none + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p count Number of blocks in logest chain seen by the node. struct GETBLOCKCOUNT : PUBLIC { static constexpr auto names() { return NAMES("get_block_count", "getblockcount"); } - - struct request : EMPTY {}; - struct response - { - uint64_t count; // Number of blocks in longest chain seen by the node. - std::string status; // General RPC error code. "OK" means everything looks good. - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -1415,14 +1420,17 @@ namespace rpc { struct response : STATUS {}; }; - BELDEX_RPC_DOC_INTROSPECT - // Send a command to the daemon to safely disconnect and shut down. + //----------------------------------------------- + /// Stop the daemon. + /// + /// Inputs: none + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. struct STOP_DAEMON : LEGACY { static constexpr auto names() { return NAMES("stop_daemon"); } - - struct request : EMPTY {}; - struct response : STATUS {}; }; BELDEX_RPC_DOC_INTROSPECT @@ -2715,6 +2723,12 @@ namespace rpc { GET_HEIGHT, GET_INFO, BNS_RESOLVE, + GET_OUTPUTS, + START_MINING, + STOP_MINING, + SAVE_BC, + STOP_DAEMON, + GETBLOCKCOUNT, // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN, GET_BLOCKS_BIN, @@ -2729,14 +2743,9 @@ namespace rpc { using FIXME_old_rpc_types = tools::type_list< GET_TRANSACTIONS, IS_KEY_IMAGE_SPENT, - GET_OUTPUTS, SEND_RAW_TX, - START_MINING, - STOP_MINING, MINING_STATUS, GET_NET_STATS, - SAVE_BC, - GETBLOCKCOUNT, GETBLOCKHASH, GETBLOCKTEMPLATE, SUBMITBLOCK, @@ -2757,7 +2766,6 @@ namespace rpc { GET_CONNECTIONS, GET_BLOCK_HEADERS_RANGE, SET_BOOTSTRAP_DAEMON, - STOP_DAEMON, GET_LIMIT, SET_LIMIT, OUT_PEERS, From df880320b7a152f5ee6039607c14bf80dece933c Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Thu, 17 Apr 2025 15:49:37 +0530 Subject: [PATCH 026/182] Convert RPC functions to new format: MINING_STATUS, GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_BACKLOG, GET_TRANSACTION_POOL_STATS, GET_CONNECTIONS --- src/rpc/core_rpc_server.cpp | 98 ++++++++-------- src/rpc/core_rpc_server.h | 10 +- src/rpc/core_rpc_server_commands_defs.cpp | 38 +++---- src/rpc/core_rpc_server_commands_defs.h | 131 ++++++++++------------ 4 files changed, 132 insertions(+), 145 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 435bf2695ed..82d7f5e7ed2 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1293,33 +1293,32 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - MINING_STATUS::response core_rpc_server::invoke(MINING_STATUS::request&& req, rpc_context context) + void core_rpc_server::invoke(MINING_STATUS& mining_status, rpc_context context) { - MINING_STATUS::response res{}; - PERF_TIMER(on_mining_status); const miner& lMiner = m_core.get_miner(); - res.active = lMiner.is_mining(); - res.block_target = tools::to_seconds(old::TARGET_BLOCK_TIME_12); // old_block_time - res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(false /*POS*/); + mining_status.response["active"] = lMiner.is_mining(); + mining_status.response["block_target"] = tools::to_seconds(old::TARGET_BLOCK_TIME_12); // old_block_time + mining_status.response["difficulty"] = m_core.get_blockchain_storage().get_difficulty_for_next_block(false /*POS*/); if ( lMiner.is_mining() ) { - res.speed = lMiner.get_speed(); - res.threads_count = lMiner.get_threads_count(); - res.block_reward = lMiner.get_block_reward(); + mining_status.response["speed"] = lMiner.get_speed(); + mining_status.response["threads_count"] = lMiner.get_threads_count(); + mining_status.response["block_reward"] = lMiner.get_block_reward(); } const account_public_address& lMiningAdr = lMiner.get_mining_address(); if (lMiner.is_mining()) - res.address = get_account_address_as_str(nettype(), false, lMiningAdr); + mining_status.response["address"] = get_account_address_as_str(nettype(), false, lMiningAdr); const auto major_version = m_core.get_blockchain_storage().get_network_version(); - res.pow_algorithm = + mining_status.response["pow_algorithm"] = major_version >= hf::hf13_checkpointing ? "RandomX (BELDEX variant)" : - major_version == hf::hf11_infinite_staking ? "Cryptonight Turtle Light (Variant 2)" : + major_version == hf::hf11_infinite_staking ? "Cryptonight Turtle Light (Variant 2)" : "Cryptonight Heavy (Variant 2)"; - res.status = STATUS_OK; - return res; + mining_status.response["status"] = STATUS_OK; + LOG_PRINT_L0(mining_status.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(SAVE_BC& save_bc, rpc_context context) @@ -1502,34 +1501,36 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_TRANSACTION_POOL_HASHES::response core_rpc_server::invoke(GET_TRANSACTION_POOL_HASHES::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_context context) { - GET_TRANSACTION_POOL_HASHES::response res{}; - PERF_TIMER(on_get_transaction_pool_hashes); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + //TODO handle bootstrap daemon with RPC + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; std::vector tx_hashes; m_core.get_pool().get_transaction_hashes(tx_hashes, context.admin); - res.tx_hashes.reserve(tx_hashes.size()); + get_transaction_pool_hashes.response["tx_hashes"].reserve(tx_hashes.size()); for (const crypto::hash &tx_hash: tx_hashes) - res.tx_hashes.push_back(tools::type_to_hex(tx_hash)); - res.status = STATUS_OK; - return res; + get_transaction_pool_hashes.response["tx_hashes"].push_back(tools::type_to_hex(tx_hash)); + get_transaction_pool_hashes.response["status"] = STATUS_OK; + LOG_PRINT_L0(get_transaction_pool_hashes.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ - GET_TRANSACTION_POOL_STATS::response core_rpc_server::invoke(GET_TRANSACTION_POOL_STATS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context) { - GET_TRANSACTION_POOL_STATS::response res{}; - PERF_TIMER(on_get_transaction_pool_stats); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - - m_core.get_pool().get_transaction_stats(res.pool_stats, context.admin); - res.status = STATUS_OK; - return res; + //TODO handle bootstrap daemon + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; + + rpc::txpool_stats stats; + m_core.get_pool().get_transaction_stats(stats, context.admin); + get_transaction_pool_stats.response["pool_stats"] = stats; + get_transaction_pool_stats.response["status"] = STATUS_OK; + LOG_PRINT_L0(get_transaction_pool_stats.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ SET_BOOTSTRAP_DAEMON::response core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context) @@ -2080,17 +2081,14 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_CONNECTIONS::response core_rpc_server::invoke(GET_CONNECTIONS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_CONNECTIONS& get_connections, rpc_context context) { - GET_CONNECTIONS::response res{}; - PERF_TIMER(on_get_connections); - res.connections = m_p2p.get_payload_object().get_connections(); - - res.status = STATUS_OK; - - return res; + get_connections.response["connections"] = m_p2p.get_payload_object().get_connections(); + get_connections.response["status"] = STATUS_OK; + LOG_PRINT_L0(get_connections.response["status"]); + return; } //------------------------------------------------------------------------------------------------------------------------------ HARD_FORK_INFO::response core_rpc_server::invoke(HARD_FORK_INFO::request&& req, rpc_context context) @@ -2565,17 +2563,19 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_TRANSACTION_POOL_BACKLOG::response core_rpc_server::invoke(GET_TRANSACTION_POOL_BACKLOG::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context) { - GET_TRANSACTION_POOL_BACKLOG::response res{}; - PERF_TIMER(on_get_txpool_backlog); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - - m_core.get_pool().get_transaction_backlog(res.backlog); - res.status = STATUS_OK; - return res; + //TODO handle bootstrap daemon + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; + + std::vector backlog; + m_core.get_pool().get_transaction_backlog(backlog); + get_transaction_pool_backlog.response["backlog"] = backlog; + get_transaction_backlog.response["status"] = STATUS_OK; + LOG_PRINT_L0(get_transaction_backlog.response["status"]); + return; } namespace { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index bca9b52a36d..fe9b73c1b97 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -208,6 +208,11 @@ namespace cryptonote::rpc { void invoke(SAVE_BC& save_bc, rpc_context context); void invoke(STOP_DAEMON& stop_daemon, rpc_context context); void invoke(GETBLOCKCOUNT& getblockcount, rpc_context context); + void invoke(MINING_STATUS& mining_status, rpc_context context); + void invoke(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_context context); + void invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context); + void invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context); + void invoke(GET_CONNECTIONS& get_connections, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -223,15 +228,12 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: IS_KEY_IMAGE_SPENT::response invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context); SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); - MINING_STATUS::response invoke(MINING_STATUS::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); SET_LOG_HASH_RATE::response invoke(SET_LOG_HASH_RATE::request&& req, rpc_context context); SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); GET_TRANSACTION_POOL::response invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context); - GET_TRANSACTION_POOL_HASHES::response invoke(GET_TRANSACTION_POOL_HASHES::request&& req, rpc_context context); - GET_TRANSACTION_POOL_STATS::response invoke(GET_TRANSACTION_POOL_STATS::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_LIMIT::response invoke(GET_LIMIT::request&& req, rpc_context context); SET_LIMIT::response invoke(SET_LIMIT::request&& req, rpc_context context); @@ -248,7 +250,6 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); - GET_CONNECTIONS::response invoke(GET_CONNECTIONS::request&& req, rpc_context context); HARD_FORK_INFO::response invoke(HARD_FORK_INFO::request&& req, rpc_context context); SETBANS::response invoke(SETBANS::request&& req, rpc_context context); GETBANS::response invoke(GETBANS::request&& req, rpc_context context); @@ -261,7 +262,6 @@ namespace cryptonote::rpc { GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); SYNC_INFO::response invoke(SYNC_INFO::request&& req, rpc_context context); - GET_TRANSACTION_POOL_BACKLOG::response invoke(GET_TRANSACTION_POOL_BACKLOG::request&& req, rpc_context context); PRUNE_BLOCKCHAIN::response invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 93cd701fb6f..ff64dd2ae78 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -621,18 +621,18 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES::response) - KV_SERIALIZE(status) - KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(tx_hashes) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_BACKLOG::response) - KV_SERIALIZE(status) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(backlog) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_BACKLOG::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE_CONTAINER_POD_AS_BLOB(backlog) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(txpool_histo) @@ -658,17 +658,17 @@ KV_SERIALIZE_MAP_CODE_BEGIN(txpool_stats) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_STATS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(pool_stats) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_STATS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(pool_stats) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_CONNECTIONS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(connections) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_CONNECTIONS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(connections) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADERS_RANGE::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index cd270476be5..d776b7461db 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -649,27 +649,25 @@ namespace rpc { static constexpr auto names() { return NAMES("stop_mining"); } }; - BELDEX_RPC_DOC_INTROSPECT - // Get the mining status of the daemon. + //----------------------------------------------- + /// Get the mining status of the daemon. + /// + /// Inputs: none + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p active States if mining is enabled (`true`) or disabled (`false`). + /// \p speed Mining power in hashes per seconds. + /// \p threads_count Number of running mining threads. + /// \p address Account address daemon is mining to. Empty if not mining. + /// \p pow_algorithm Current hashing algorithm name + /// \p block_target The expected time to solve per block, i.e. TARGET_BLOCK_TIME + /// \p block_reward Block reward for the current block being mined. + /// \p difficulty The difficulty for the current block being mined. struct MINING_STATUS : LEGACY { static constexpr auto names() { return NAMES("mining_status"); } - - struct request : EMPTY {}; - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - bool active; // States if mining is enabled (`true`) or disabled (`false`). - uint64_t speed; // Mining power in hashes per seconds. - uint32_t threads_count; // Number of running mining threads. - std::string address; // Account address daemon is mining to. Empty if not mining. - std::string pow_algorithm; // Current hashing algorithm name - uint32_t block_target; // The expected time to solve per block, i.e. TARGET_BLOCK_TIME_12 - uint64_t block_reward; // Block reward for the current block being mined. - uint64_t difficulty; // The difficulty for the current block being mined. - - KV_MAP_SERIALIZABLE - }; }; /// Retrieve general information about the state of the node and the network. @@ -1265,21 +1263,19 @@ namespace rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get hashes from transaction pool. + //----------------------------------------------- + /// Get hashes from transaction pool. + /// + /// Inputs: none + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p tx_hashes List of transaction hashes, + /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } - - struct request : EMPTY {}; - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector tx_hashes; // List of transaction hashes, - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -1290,22 +1286,19 @@ namespace rpc { uint64_t time_in_pool; }; - BELDEX_RPC_DOC_INTROSPECT - // Get all transaction pool backlog. + //----------------------------------------------- + /// Get all transaction pool backlog. + /// + /// Inputs: none + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p backlog Array of structures tx_backlog_entry (in binary form): + /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_BACKLOG : PUBLIC { static constexpr auto names() { return NAMES("get_txpool_backlog"); } - - struct request : EMPTY {}; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector backlog; // Array of structures tx_backlog_entry (in binary form): - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -1339,39 +1332,33 @@ namespace rpc { KV_MAP_SERIALIZABLE }; - BELDEX_RPC_DOC_INTROSPECT - // Get the transaction pool statistics. + //----------------------------------------------- + /// Get the transaction pool statistics. + /// + /// Inputs: none + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p pool_stats List of pool stats. + /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } - - struct request : EMPTY {}; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - txpool_stats pool_stats; // List of pool stats: - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; - BELDEX_RPC_DOC_INTROSPECT - // Retrieve information about incoming and outgoing connections to your node. + //----------------------------------------------- + /// Retrieve information about incoming and outgoing connections to your node. + /// + /// Inputs: none + /// + /// Output values available from a public RPC endpoint: + /// + /// \p status General RPC status string. `"OK"` means everything looks good. + /// \p connections List of all connections and their info: struct GET_CONNECTIONS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_connections"); } - - struct request : EMPTY {}; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::list connections; // List of all connections and their info: - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -2729,6 +2716,10 @@ namespace rpc { SAVE_BC, STOP_DAEMON, GETBLOCKCOUNT, + MINING_STATUS, + GET_TRANSACTION_POOL_HASHES, + GET_TRANSACTION_POOL_BACKLOG, + GET_TRANSACTION_POOL_STATS, // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN, GET_BLOCKS_BIN, @@ -2744,7 +2735,6 @@ namespace rpc { GET_TRANSACTIONS, IS_KEY_IMAGE_SPENT, SEND_RAW_TX, - MINING_STATUS, GET_NET_STATS, GETBLOCKHASH, GETBLOCKTEMPLATE, @@ -2760,9 +2750,6 @@ namespace rpc { SET_LOG_LEVEL, SET_LOG_CATEGORIES, GET_TRANSACTION_POOL, - GET_TRANSACTION_POOL_HASHES, - GET_TRANSACTION_POOL_BACKLOG, - GET_TRANSACTION_POOL_STATS, GET_CONNECTIONS, GET_BLOCK_HEADERS_RANGE, SET_BOOTSTRAP_DAEMON, From 9350d0ab0dd3c0a32c7bef97622578b345905d9b Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 17 Apr 2025 17:12:39 +0530 Subject: [PATCH 027/182] RPC: GET_MASTER_NODES & GET_MASTER_NODE_STATUS --- src/common/string_util.h | 9 + .../cryptonote_format_utils.cpp | 23 +- src/daemon/rpc_command_executor.cpp | 443 ++++++++-------- src/daemon/rpc_command_executor.h | 2 +- src/rpc/core_rpc_server.cpp | 402 ++++++++------- src/rpc/core_rpc_server.h | 11 +- src/rpc/core_rpc_server_command_parser.cpp | 35 ++ src/rpc/core_rpc_server_command_parser.h | 4 +- src/rpc/core_rpc_server_commands_defs.cpp | 290 +++++------ src/rpc/core_rpc_server_commands_defs.h | 487 ++++++++++-------- 10 files changed, 943 insertions(+), 763 deletions(-) diff --git a/src/common/string_util.h b/src/common/string_util.h index 0d616934ca3..fd61b5bbdd3 100755 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -110,6 +110,15 @@ bool parse_int(const std::string_view str, T& value, int base = 10) { return true; } +/// Converts an integer value into a string via std::to_chars (i.e. without locale). +template >> +std::string int_to_string(const T& value, int base = 10) { + char buf[8*sizeof(T) + std::is_signed_v]; // maximum possible size with smallest possible base (2) + auto [p, ec] = std::to_chars(std::begin(buf), std::end(buf), value, base); + assert(ec == std::errc{}); // Our buffer should be big enough for anything + return {buf, p}; +} + /// Returns a string_view that views the data of the given object; this is not something you want to /// do unless the struct is specifically design to be used this way. The value must be a standard /// layout type; it should really require is_trivial, too, but we have classes (like crypto keys) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index bb48b0aa4aa..0a70a9e86d2 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1100,17 +1100,18 @@ namespace cryptonote cn_fast_hash(blob.data(), blob.size(), res); } //--------------------------------------------------------------- - std::string print_money(uint64_t amount, bool trim_insignificant) - { - std::string s = tools::int_to_string(amount); - if (s.size() <= beldex::DISPLAY_DECIMAL_POINT) - s.insert(0, beldex::DISPLAY_DECIMAL_POINT - s.size() + 1, '0'); - s.insert(s.size() - beldex::DISPLAY_DECIMAL_POINT, "."); - if (trim_insignificant) { - while (s.back() == '0') - s.pop_back(); - if (s.back() == '.') - s.pop_back(); + std::string print_money(uint64_t amount, bool strip_zeros) { + constexpr unsigned int decimal_point = beldex::DISPLAY_DECIMAL_POINT; + std::string s = std::to_string(amount); + if (s.size() < decimal_point + 1) { + s.insert(0, decimal_point + 1 - s.size(), '0'); + } + s.insert(s.size() - decimal_point, "."); + if (strip_zeros) { + while (s.back() == '0') + s.pop_back(); + if (s.back() == '.') + s.pop_back(); } return s; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 59f9ddbb4c1..07f04586344 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -22,7 +22,7 @@ // 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 +// PROCUREMENT OF SUBSTITUTE GOODS OR MASTERS; 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. @@ -566,20 +566,21 @@ bool rpc_command_executor::show_status() { return false; my_mn_key = std::move(res.master_node_pubkey); - GET_MASTER_NODES::request mn_req{}; - GET_MASTER_NODES::response mn_res{}; - mn_req.master_node_pubkeys.push_back(my_mn_key); - if (invoke(std::move(mn_req), mn_res, "") && mn_res.master_node_states.size() == 1) + auto maybe_mns = try_running([&] { return invoke(json{{"master_node_pubkeys", json::array({my_mn_key})}}); }, "Failed to retrieve master node info"); + + if (maybe_mns) { { - auto &entry = mn_res.master_node_states.front(); - my_mn_registered = true; - my_mn_staked = entry.total_contributed >= entry.staking_requirement; - my_mn_active = entry.active; - my_decomm_remaining = entry.earned_downtime_blocks; - my_mn_last_uptime = entry.last_uptime_proof; - my_reason_all = entry.last_decommission_reason_consensus_all; - my_reason_any = entry.last_decommission_reason_consensus_any; + if (auto it = maybe_mns->find("master_node_states"); it != maybe_mns->end() && it->is_array() && it->size() > 0) { + auto& state = it->front(); + my_mn_registered = true; + my_mn_staked = state["total_contributed"].get() >= state["staking_requirement"].get(); + my_mn_active = state["active"].get(); + my_decomm_remaining = state["earned_downtime_blocks"].get(); + my_mn_last_uptime = state["last_uptime_proof"].get(); + my_reason_all = state["last_decommission_reason_consensus_all"].get(); + my_reason_any = state["last_decommission_reason_consensus_any"].get(); + } } } @@ -1610,40 +1611,60 @@ static std::string to_string_rounded(double d, int precision) { return ss.str(); } -static void print_vote_history(std::ostringstream &stream, std::vector const &votes) -{ - if (votes.empty()) - stream << "(Awaiting votes from master node)"; - - // NOTE: Votes were stored in a ring buffer and copied naiively into the vote - // array so they may be out of order. Find the smallest entry (by height) and - // print starting from that entry. - auto it = std::min_element(votes.begin(), votes.end(), [](const auto &a, const auto &b) { return a.height < b.height; }); - size_t offset = std::distance(votes.begin(), it); - - for (size_t i = 0; i < votes.size(); i++) - { - if (i > 0) stream << ", "; - const auto& entry = votes[(offset + i) % votes.size()]; - stream << "[" << entry.height; - if (entry.is_POS and entry.POS.round > 0) - // For a typical POS round just [1234,yes]. For a backup round: [1234+3,yes] - stream << "+" << +entry.POS.round; - - stream << "," << (entry.voted ? "yes" : "NO") << "]"; - } -} - -template -static void print_participation_history(std::ostringstream &stream, std::vector const &votes) -{ - if (votes.empty()) - stream << "(Awaiting timesync data from master node)"; - - for (size_t i = 0; i < votes.size(); i++) - { - if (i > 0) stream << ", "; - stream << "["<< (votes[i].pass() ? "yes" : "NO") << "]"; +// static void print_vote_history(std::ostringstream &stream, std::vector const &votes) +// { +// if (votes.empty()) +// stream << "(Awaiting votes from master node)"; + +// // NOTE: Votes were stored in a ring buffer and copied naiively into the vote +// // array so they may be out of order. Find the smallest entry (by height) and +// // print starting from that entry. +// auto it = std::min_element(votes.begin(), votes.end(), [](const auto &a, const auto &b) { return a.height < b.height; }); +// size_t offset = std::distance(votes.begin(), it); + +// for (size_t i = 0; i < votes.size(); i++) +// { +// if (i > 0) stream << ", "; +// const auto& entry = votes[(offset + i) % votes.size()]; +// stream << "[" << entry.height; +// if (entry.is_POS and entry.POS.round > 0) +// // For a typical POS round just [1234,yes]. For a backup round: [1234+3,yes] +// stream << "+" << +entry.POS.round; + +// stream << "," << (entry.voted ? "yes" : "NO") << "]"; +// } +// } + +// template +// static void print_participation_history(std::ostringstream &stream, std::vector const &votes) +// { +// if (votes.empty()) +// stream << "(Awaiting timesync data from master node)"; + +// for (size_t i = 0; i < votes.size(); i++) +// { +// if (i > 0) stream << ", "; +// stream << "["<< (votes[i].pass() ? "yes" : "NO") << "]"; +// } +// } + +template +void print_votes(std::ostream& o, const json& elem, const std::string& key, EPrinter eprint) { + std::vector voted, missed; + if (auto it = elem.find(key); it != elem.end()) { + (*it)["voted"].get_to(voted); + (*it)["missed"].get_to(missed); + } + if (voted.empty() && missed.empty()) + o << "(Awaiting votes from master node)"; + else { + o << voted.size() << " voted"; + if (!voted.empty()) + o << " [" << tools::join_transform(" ", voted, eprint) << "]"; + if (missed.empty()) + o << ", none missed."; + else + o << ", " << missed.size() << " MISSED VOTES [" << tools::join_transform(" ", missed, eprint) << "]"; } } @@ -1652,46 +1673,37 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net const char indent1[] = " "; const char indent2[] = " "; const char indent3[] = " "; - bool is_registered = entry.total_contributed >= entry.staking_requirement; + bool is_funded = entry["funded"].get(); std::ostringstream stream; // Print Funding Status { - stream << indent1 << "[" << entry_index << "] " << "Master Node: " << entry.master_node_pubkey << " "; - stream << "v" << tools::join(".", entry.master_node_version) << "\n"; + stream << indent1 << "[" << entry_index << "] " << "Master Node: " << entry["master_node_pubkey"].get() << " "; + if (auto e = entry.find("master_node_version"); e != entry.end()) + stream << "v" << tools::join(".", entry["master_node_version"].get>()) << "\n"; + else + stream << "v(unknown)\n"; if (detailed_view) { - stream << indent2 << "Total Contributed/Staking Requirement: " << cryptonote::print_money(entry.total_contributed) << "/" << cryptonote::print_money(entry.staking_requirement) << "\n"; - stream << indent2 << "Total Reserved: " << cryptonote::print_money(entry.total_reserved) << "\n"; + stream << indent2 << "Total Contributed/Staking Requirement: " << cryptonote::print_money(entry["total_contributed"].get()) + << "/" << cryptonote::print_money(entry["staking_requirement"].get()) << "\n"; + if (auto it = entry.find("total_reserved"); it != entry.end()) + stream << indent2 << "Total Reserved: " << cryptonote::print_money(it->get()) << "\n"; } } // Print expiry information uint64_t const now = time(nullptr); { - uint64_t expiry_height = 0; - auto reg_hf = static_cast(entry.registration_hf_version); - if (reg_hf >= hf::hf11_infinite_staking) - { - expiry_height = entry.requested_unlock_height; - } - else if (reg_hf >= hf::hf10_bulletproofs) - { - expiry_height = entry.registration_height + master_nodes::staking_num_lock_blocks(nettype,entry.registration_hf_version); - expiry_height += cryptonote::old::STAKING_REQUIREMENT_LOCK_BLOCKS_EXCESS; - } - else - { - expiry_height = entry.registration_height + master_nodes::staking_num_lock_blocks(nettype,entry.registration_hf_version); - } + auto expiry_height = entry["requested_unlock_height"].get(); - stream << indent2 << "Registration: Hardfork Version: " << static_cast(entry.registration_hf_version) << "; Height: " << entry.registration_height << "; Expiry: "; + stream << indent2 << "Registration: Hardfork Version: " << entry["registration_hf_version"].get() + << "; Height: " << entry["registration_height"].get() + << "; Expiry: "; if (expiry_height == master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) - { stream << "Staking Infinitely (stake unlock not requested)\n"; - } else { uint64_t delta_height = (blockchain_height >= expiry_height) ? 0 : expiry_height - blockchain_height; @@ -1701,53 +1713,51 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net } } - if (detailed_view && is_registered) // Print reward status + if (detailed_view && is_funded) // Print reward status { - stream << indent2 << "Last Reward (Or Penalty) At (Height/TX Index): " << entry.last_reward_block_height << "/" << entry.last_reward_transaction_index << "\n"; + stream << indent2 << "Last Reward (Or Penalty) At (Height/TX Index): " << entry["last_reward_block_height"].get() << "/" << entry["last_reward_transaction_index"].get() << "\n"; } if (detailed_view) // Print operator information { - stream << indent2 << "Operator Cut (\% Of Reward): " << to_string_rounded((entry.portions_for_operator / (double)cryptonote::old::STAKING_PORTIONS) * 100.0, 2) << "%\n"; - stream << indent2 << "Operator Address: " << entry.operator_address << "\n"; + stream << indent2 << "Operator Fee: " << to_string_rounded(entry["operator_fee"].get() / 1000., 3) << "%\n"; + stream << indent2 << "Operator Address: " << entry["operator_address"].get() << "\n"; } - if (is_registered) // Print master node tests + if (is_funded) // Print master node tests { - epee::console_colors uptime_proof_color = (entry.last_uptime_proof == 0) ? epee::console_color_red : epee::console_color_green; + auto proof_time = entry.value("last_uptime_proof", uint64_t{0}); + epee::console_colors uptime_proof_color = proof_time ? epee::console_color_red : epee::console_color_green; - stream << indent2; - if (entry.last_uptime_proof == 0) - { - stream << "Last Uptime Proof Received: (Awaiting confirmation from network)"; - } - else - { - stream << "Last Uptime Proof Received: " << get_human_time_ago(entry.last_uptime_proof, time(nullptr)); - } + stream << indent2 << "Last Uptime Proof Received: " << (proof_time == 0 ? "(Awaiting confirmation from network)" : get_human_time_ago(proof_time, time(nullptr))); // // NOTE: Node Identification // stream << "\n"; stream << indent2 << "IP Address & Ports: "; - if (entry.public_ip == "0.0.0.0") + if (entry.value("public_ip", "0.0.0.0"s) == "0.0.0.0") stream << "(Awaiting confirmation from network)"; else - stream << entry.public_ip << " :" << entry.storage_port << " (storage https), :" << entry.storage_lmq_port - << " (storage omq), :" << entry.quorumnet_port << " (quorumnet)"; + stream << entry["public_ip"].get() << " :" << entry["storage_port"].get() << " (storage https), :" + << entry["storage_lmq_port"].get() << " (storage omq), :" << entry["quorumnet_port"].get() << " (quorumnet)"; stream << "\n"; - if (detailed_view) + if (detailed_view){ + auto ed_pk = entry.value("pubkey_ed25519", ""sv); stream << indent2 << "Auxiliary Public Keys:\n" - << indent3 << (entry.pubkey_ed25519.empty() ? "(not yet received)" : entry.pubkey_ed25519) << " (Ed25519)\n" - << indent3 << (entry.pubkey_ed25519.empty() ? "(not yet received)" : oxenc::to_base32z(oxenc::from_hex(entry.pubkey_ed25519)) + ".mnode") << " (Belnet)\n" - << indent3 << (entry.pubkey_x25519.empty() ? "(not yet received)" : entry.pubkey_x25519) << " (X25519)\n"; - + << indent3 << (ed_pk.empty() ? "(not yet received)"sv : ed_pk) << " (Ed25519)\n" + << indent3 << (ed_pk.empty() ? "(not yet received)"s : oxenmq::to_base32z(oxenmq::from_hex(ed_pk)) + ".mnode") << " (Belnet)\n" + << indent3 << entry.value("pubkey_x25519", "(not yet received)"sv) << " (X25519)\n"; + } // // NOTE: Storage Server Test // - auto print_reachable = [&stream, &now] (bool reachable, auto first_unreachable, auto last_unreachable, auto last_reachable) { + auto print_reachable = [&stream, &now] (const json& j, const std::string& prefix) { + auto first_unreachable = j.value(prefix + "_first_unreachable", 0), + last_unreachable = j.value(prefix + "_last_unreachable", 0), + last_reachable = j.value(prefix + "_last_reachable", 0); + if (first_unreachable == 0) { if (last_reachable == 0) stream << "Not yet tested"; @@ -1759,7 +1769,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net } } else { stream << "NO"; - if (!reachable) + if (!j.value(prefix+"_reachable", false)) stream << " - FAILING!"; stream << " (last tested " << get_human_time_ago(last_unreachable, now) << "; failing since " << get_human_time_ago(first_unreachable, now); @@ -1770,71 +1780,105 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net stream << '\n'; }; stream << indent2 << "Storage Server Reachable: "; - print_reachable(entry.storage_server_reachable, entry.storage_server_first_unreachable, entry.storage_server_last_unreachable, entry.storage_server_last_reachable); + print_reachable(entry, "storage_server"); stream << indent2 << "Belnet Reachable: "; - print_reachable(entry.belnet_reachable, entry.belnet_first_unreachable, entry.belnet_last_unreachable, entry.belnet_last_reachable); + print_reachable(entry, "belnet"); // // NOTE: Component Versions // + auto show_component_version = [] (const json& j, std::string_view name) { + if (!j.is_array() || j.front().get() == 0) + return "("s + std::string{name} + " ping not yet received)"s; + return tools::join(".", j.get>()); + }; stream << indent2 << "Storage Server / Belnet Router versions: " - << ((entry.storage_server_version[0] == 0 && entry.storage_server_version[1] == 0 && entry.storage_server_version[2] == 0) ? "(Storage server ping not yet received) " : tools::join(".", entry.storage_server_version)) << " / " << ((entry.belnet_version[0] == 0 && entry.belnet_version[1] == 0 && entry.belnet_version[2] == 0) ? "(Belnet ping not yet received)" : tools::join(".", entry.belnet_version)) << "\n"; - - - + << show_component_version(entry["storage_server_version"], "Storage Server") + << " / " + << show_component_version(entry["storage_server_version"], "Belnet") + << "\n"; // // NOTE: Print Voting History // - stream << indent2 << "Checkpoints [Height,Voted]: "; - print_vote_history(stream, entry.checkpoint_participation); - - stream << "\n" << indent2 << "POS [Height,Voted]: "; - print_vote_history(stream, entry.POS_participation); + stream << indent2 << "Checkpoints votes: "; + print_votes(stream, entry, "checkpoint_votes", [](uint64_t height) { return height; }); + + stream << '\n' << indent2 << "POS blocks: "; + print_votes>(stream, entry, "POS_votes", + [](const auto& val) { return tools::int_to_string(val.first) + (val.second ? " " + tools::int_to_string(val.second) : ""); }); + + auto print_pass_fail = [&stream, &entry](const std::string& key) { + std::pair val; + auto& [success, fail] = val; + if (auto it = entry.find(key); it != entry.end()) + it->get_to(val); + + if (!success && !fail) + stream << "(Awaiting test data)"; + else { + stream << success << " passes, "; + if (fail) + stream << fail << " FAILURES"; + else + stream << "no failures"; + } + }; - stream << "\n" << indent2 << "Timestamps [in_sync]: "; - print_participation_history(stream, entry.timestamp_participation); + stream << '\n' << indent2 << "Quorumnet tests: "; + print_pass_fail("quorumnet_tests"); - stream << "\n" << indent2 << "Timesync [responded]: "; - print_participation_history(stream, entry.timesync_status); + stream << '\n' << indent2 << "Timesync tests: "; + print_pass_fail("timesync_tests"); + stream << '\n'; } - stream << "\n"; if (detailed_view) // Print contributors { - for (size_t j = 0; j < entry.contributors.size(); ++j) + auto n_contributors = entry["contributors"].size(); + stream << indent2 << "Contributors (" << n_contributors << "):\n"; + for (auto& contributor : entry["contributors"]) { - const auto& contributor = entry.contributors[j]; - stream << indent2 << "[" << j << "] Contributor: " << contributor.address << "\n"; - stream << indent3 << "Amount / Reserved: " << cryptonote::print_money(contributor.amount) << "/" << cryptonote::print_money(contributor.reserved) << "\n"; + stream << indent3 << contributor["address"].get(); + auto amount = contributor["amount"].get(); + auto reserved = contributor.value("reserved", amount); + stream << " (" << cryptonote::print_money(amount, true); + if (reserved != amount) + stream << " / " << cryptonote::print_money(reserved, true); + if (!is_funded || n_contributors > 1) { + auto required = entry["staking_requirement"].get(); + stream << " = " << std::round(reserved / (double) required * 10000.) / 100. << "%"; + } + stream << ")\n"; } } // // NOTE: Overall status // - if (entry.active) { + if (entry["active"].get()) { stream << indent2 << "Current Status: ACTIVE\n"; - stream << indent2 << "Downtime Credits: " << entry.earned_downtime_blocks << " blocks" - << " (about " << to_string_rounded(entry.earned_downtime_blocks / (double) cryptonote::BLOCKS_PER_HOUR, 2) << " hours)"; - - if (entry.earned_downtime_blocks < master_nodes::DECOMMISSION_MINIMUM) + auto downtime = entry["earned_downtime_blocks"].get(); + stream << indent2 << "Downtime Credits: " << downtime << " blocks" + << " (about " << to_string_rounded(downtime / (double)cryptonote::BLOCKS_PER_HOUR, 2) << " hours)"; + if (downtime < master_nodes::DECOMMISSION_MINIMUM) stream << " (Note: " << master_nodes::DECOMMISSION_MINIMUM << " blocks required to enable deregistration delay)"; - } else if (is_registered) { + } else if (is_funded) { stream << indent2 << "Current Status: DECOMMISSIONED" ; - if (entry.last_decommission_reason_consensus_all || entry.last_decommission_reason_consensus_any) + auto reason_all = entry["last_decommission_reason_consensus_all"].get(); + auto reason_any = entry["last_decommission_reason_consensus_any"].get(); + if (reason_any) stream << " - "; - if (auto reasons = cryptonote::readable_reasons(entry.last_decommission_reason_consensus_all); !reasons.empty()) + if (auto reasons = cryptonote::readable_reasons(reason_all); !reasons.empty()) stream << tools::join(", ", reasons); // Add any "any" reasons that aren't in all with a (some) qualifier - if (auto reasons = cryptonote::readable_reasons(entry.last_decommission_reason_consensus_any & ~entry.last_decommission_reason_consensus_all); !reasons.empty()) { + if (auto reasons = cryptonote::readable_reasons(reason_any & ~reason_all); !reasons.empty()) { for (auto& r : reasons) r += "(some)"; - stream << (entry.last_decommission_reason_consensus_all ? ", " : "") << tools::join(", ", reasons); + stream << (reason_all ? ", " : "") << tools::join(", ", reasons); } stream << "\n"; - stream << indent2 << "Remaining Decommission Time Until DEREGISTRATION: " << entry.earned_downtime_blocks << " blocks"; - } else { + stream << indent2 << "Remaining Decommission Time Until DEREGISTRATION: " << entry["earned_downtime_blocks"].get() << " blocks"; } else { stream << indent2 << "Current Status: awaiting contributions\n"; } stream << "\n"; @@ -1842,20 +1886,22 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net buffer.append(stream.str()); } -bool rpc_command_executor::print_mn(const std::vector &args) +bool rpc_command_executor::print_mn(const std::vector &args, bool self) { - GET_MASTER_NODES::request req{}; - GET_MASTER_NODES::response res{}; + std::vector pubkeys; bool detailed_view = false; for (auto& arg : args) { if (arg == "+json") - req.include_json = true; + tools::fail_msg_writer() << "+json is no longer supported"; else if (arg == "+detail") detailed_view = true; - else - req.master_node_pubkeys.push_back(arg); + else if (self) { + tools::fail_msg_writer() << "print_mn_status takes no pubkey arguments"; + return false; + } else + pubkeys.push_back(arg); } auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); @@ -1863,9 +1909,6 @@ bool rpc_command_executor::print_mn(const std::vector &args) return false; auto& info = *maybe_info; - if (!invoke(std::move(req), res, "Failed to retrieve master node data")) - return false; - cryptonote::network_type nettype = info.value("mainnet", false) ? cryptonote::network_type::MAINNET : info.value("devnet", false) ? cryptonote::network_type::DEVNET : @@ -1873,83 +1916,89 @@ bool rpc_command_executor::print_mn(const std::vector &args) cryptonote::network_type::UNDEFINED; uint64_t curr_height = info["height"].get(); - std::vector unregistered; - std::vector registered; - registered.reserve(res.master_node_states.size()); + std::vector awaiting; + std::vector registered; + + std::string my_mn_pk; + if (!self) { + auto maybe_mns = try_running([&] { return invoke(json{{"master_node_pubkeys", pubkeys}}); }, + "Failed to retrieve master node data"); + if (!maybe_mns) + return false; + + for (auto &entry : (*maybe_mns)["master_node_states"]) + { + if (entry["total_contributed"].get() == entry["staking_requirement"].get()) + registered.push_back(std::move(entry)); + else + awaiting.push_back(std::move(entry)); + } + } else { + auto maybe_mn = try_running([&] { return invoke(); }, + "Failed to retrieve master node status"); + if (!maybe_mn) + return false; + auto& mn = (*maybe_mn)["master_node_state"]; + my_mn_pk = mn["master_node_pubkey"]; + if (mn.find("registration_height") != mn.end()) { + if (mn["total_contributed"].get() == mn["staking_requirement"].get()) + registered.push_back(std::move(mn)); + else + awaiting.push_back(std::move(mn)); + } + } - for (auto &entry : res.master_node_states) + if (awaiting.size() == 0 && registered.size() == 0) { - if (entry.total_contributed == entry.staking_requirement) - registered.push_back(&entry); + if (pubkeys.size() > 0) + tools::msg_writer() << "No master node is currently known on the network: " << tools::join(", ", pubkeys); + else if (self) + tools::msg_writer() << "master node " << my_mn_pk << " is not currently registered on the network"; else - unregistered.push_back(&entry); + tools::msg_writer() << "No master nodes are currently known on the network"; + + return true; } - std::sort(unregistered.begin(), unregistered.end(), [](auto *a, auto *b) { - uint64_t a_remaining = a->staking_requirement - a->total_reserved; - uint64_t b_remaining = b->staking_requirement - b->total_reserved; + + std::sort(awaiting.begin(), awaiting.end(), [](const json& a, const json& b) { + auto a_res = a.find("total_reserved"); + auto b_res = b.find("total_reserved"); + uint64_t total_a = (a_res == a.end() ? a["total_contributed"] : *a_res).get(); + uint64_t total_b = (b_res == b.end() ? b["total_contributed"] : *b_res).get(); + uint64_t a_remaining = a["staking_requirement"].get() - total_a; + uint64_t b_remaining = b["staking_requirement"].get() - total_b; if (b_remaining == a_remaining) - return b->portions_for_operator < a->portions_for_operator; + return b["portions_for_operator"].get() < a["portions_for_operator"].get(); return b_remaining < a_remaining; }); - std::sort(registered.begin(), registered.end(), [](auto *a, auto *b) { - return std::make_tuple(a->last_reward_block_height, a->last_reward_transaction_index, a->master_node_pubkey) - < std::make_tuple(b->last_reward_block_height, b->last_reward_transaction_index, b->master_node_pubkey); + std::sort(registered.begin(), registered.end(), [](const json& a, const json& b) { + return std::make_tuple(a["last_reward_block_height"].get(), a["last_reward_transaction_index"].get(), a["master_node_pubkey"].get()) + < std::make_tuple(b["last_reward_block_height"].get(), b["last_reward_transaction_index"].get(), b["master_node_pubkey"].get()); }); - if (req.include_json) - { - std::cout << res.as_json << std::endl; - return true; - } - - if (unregistered.size() == 0 && registered.size() == 0) - { - if (req.master_node_pubkeys.size() > 0) - { - int str_size = 0; - for (const std::string &arg : req.master_node_pubkeys) str_size += (arg.size() + 2); - - std::string buffer; - buffer.reserve(str_size); - for (size_t i = 0; i < req.master_node_pubkeys.size(); ++i) - { - buffer.append(req.master_node_pubkeys[i]); - if (i < req.master_node_pubkeys.size() - 1) buffer.append(", "); - } - - tools::msg_writer() << "No master node is currently known on the network: " << buffer; - } - else - { - tools::msg_writer() << "No master node is currently known on the network"; - } - - return true; - } - - std::string unregistered_print_data; + std::string awaiting_print_data; std::string registered_print_data; - for (size_t i = 0; i < unregistered.size(); i++) + for (size_t i = 0; i < awaiting.size(); i++) { - if (i) unregistered_print_data.append("\n"); - append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, *unregistered[i], unregistered_print_data); + if (i > 0) awaiting_print_data += '\n'; + append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, awaiting[i], awaiting_print_data); } for (size_t i = 0; i < registered.size(); i++) { - if (i) registered_print_data.append("\n"); - append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, *registered[i], registered_print_data); + if (i > 0) registered_print_data += '\n'; + append_printable_master_node_list_entry(nettype, detailed_view, curr_height, i, registered[i], registered_print_data); } - if (unregistered.size() > 0) - tools::msg_writer() << "Master Node Unregistered State [" << unregistered.size() << "]\n" << unregistered_print_data; + if (awaiting.size() > 0) + tools::msg_writer() << "master Node Awaiting State [" << awaiting.size() << "]\n" << awaiting_print_data; if (registered.size() > 0) - tools::msg_writer() << "Master Node Registration State [" << registered.size() << "]\n" << registered_print_data; + tools::msg_writer() << "master Node Registration State [" << registered.size() << "]\n" << registered_print_data; return true; } @@ -1967,19 +2016,7 @@ bool rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) bool rpc_command_executor::print_mn_status(std::vector args) { - if (args.size() > 1) - { - tools::fail_msg_writer() << "Unexpected arguments"; - return false; - } - - GET_MASTER_KEYS::response res{}; - if (!invoke({}, res, "Failed to retrieve master node keys")) - return false; - - args.push_back(std::move(res.master_node_pubkey)); - - return print_mn(args); + return print_mn(std::move(args), true); } bool rpc_command_executor::print_sr(uint64_t height) diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 3c765137ba4..479956a38de 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -228,7 +228,7 @@ class rpc_command_executor final { bool prepare_registration(bool force_registration=false); - bool print_mn(const std::vector &args); + bool print_mn(const std::vector &args, bool self = false); bool prune_blockchain(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7ba0b8781c3..103440d95ae 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -390,7 +390,7 @@ namespace cryptonote::rpc { info.response["target"] = tools::to_seconds((next_block_is_POS ? TARGET_BLOCK_TIME : old::TARGET_BLOCK_TIME_12)); // This count seems broken: blocks with no outputs (after batching) shouldn't be subtracted, and - // 0-output txes (SN state changes) arguably shouldn't be, either. + // 0-output txes (MN state changes) arguably shouldn't be, either. info.response["tx_count"] = m_core.get_blockchain_storage().get_total_transactions() - height; //without coinbase info.response["tx_pool_size"] = m_core.get_pool().get_transactions_count(); @@ -2320,42 +2320,34 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_NODE_STATUS::response core_rpc_server::invoke(GET_MASTER_NODE_STATUS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_NODE_STATUS& mns, rpc_context context) { - GET_MASTER_NODE_STATUS::response res{}; - - PERF_TIMER(on_get_master_node_status); - auto get_master_node_key_res = invoke(GET_MASTER_KEYS::request{}, context); - - GET_MASTER_NODES::request get_master_nodes_req{}; - get_master_nodes_req.include_json = req.include_json; - get_master_nodes_req.master_node_pubkeys.push_back(std::move(get_master_node_key_res.master_node_pubkey)); - - auto get_master_nodes_res = invoke(std::move(get_master_nodes_req), context); - res.status = get_master_nodes_res.status; - - if (get_master_nodes_res.master_node_states.empty()) // Started in master node but not staked, no information on the blockchain yet - { - res.master_node_state.master_node_pubkey = std::move(get_master_node_key_res.master_node_pubkey); - res.master_node_state.public_ip = epee::string_tools::get_ip_string_from_int32(m_core.mn_public_ip()); - res.master_node_state.storage_port = m_core.storage_https_port(); - res.master_node_state.storage_lmq_port = m_core.storage_omq_port(); - res.master_node_state.quorumnet_port = m_core.quorumnet_port(); - res.master_node_state.pubkey_ed25519 = std::move(get_master_node_key_res.master_node_ed25519_pubkey); - res.master_node_state.pubkey_x25519 = std::move(get_master_node_key_res.master_node_x25519_pubkey); - res.master_node_state.master_node_version = BELDEX_VERSION; - } - else - { - res.master_node_state = std::move(get_master_nodes_res.master_node_states[0]); + auto [top_height, top_hash] = m_core.get_blockchain_top(); + mns.response["height"] = top_height; + mns.response_hex["block_hash"] = top_hash; + const auto& keys = m_core.get_master_keys(); + if (!keys.pub) { + mns.response["status"] = "Not a master node"; + return; } + mns.response["status"] = STATUS_OK; - res.height = get_master_nodes_res.height; - res.block_hash = get_master_nodes_res.block_hash; - res.status = get_master_nodes_res.status; - res.as_json = get_master_nodes_res.as_json; - - return res; + auto mn_infos = m_core.get_master_node_list_state({{keys.pub}}); + if (!mn_infos.empty()) + fill_mn_response_entry(mns.response["master_node_state"] = json::object(), mns.is_bt(), {} /*all fields*/, mn_infos.front(), top_height); + else { + mns.response["master_node_state"] = json{ + {"public_ip", epee::string_tools::get_ip_string_from_int32(m_core.mn_public_ip())}, + {"storage_port", m_core.storage_https_port()}, + {"storage_lmq_port", m_core.storage_omq_port()}, + {"quorumnet_port", m_core.quorumnet_port()}, + {"master_node_version", BELDEX_VERSION} + }; + auto rhex = mns.response_hex["master_node_state"]; + rhex["master_node_pubkey"] = keys.pub; + rhex["pubkey_ed25519"] = keys.pub_ed25519; + rhex["pubkey_x25519"] = keys.pub_x25519; + } } //------------------------------------------------------------------------------------------------------------------------------ GET_COINBASE_TX_SUM::response core_rpc_server::invoke(GET_COINBASE_TX_SUM::request&& req, rpc_context context) @@ -3042,192 +3034,216 @@ namespace cryptonote::rpc { system_now + (t - steady_now))); } + static bool requested(const std::unordered_set& requested, const std::string& key) { + return requested.empty() || + (requested.count("all") + ? !requested.count("-" + key) + : requested.count(key)); + } + + template + static void set_if_requested(const std::unordered_set& reqed, Dict& dict, + const std::string& key, T&& value, More&&... more) { + if (requested(reqed, key)) + dict[key] = std::forward(value); + if constexpr (sizeof...(More) > 0) + set_if_requested(reqed, dict, std::forward(more)...); + } + //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::fill_mn_response_entry(GET_MASTER_NODES::response::entry& entry, const master_nodes::master_node_pubkey_info &mn_info, uint64_t current_height) { + void core_rpc_server::fill_mn_response_entry(json& entry, bool is_bt, const std::unordered_set& reqed, const master_nodes::master_node_pubkey_info& mn_info, uint64_t top_height) + { + auto hf_version = m_core.get_blockchain_storage().get_network_version(); + auto binary_format = is_bt ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex; + json_binary_proxy binary{entry, binary_format}; const auto &info = *mn_info.info; - entry.master_node_pubkey = tools::type_to_hex(mn_info.pubkey); - entry.registration_height = info.registration_height; - entry.requested_unlock_height = info.requested_unlock_height; - entry.last_reward_block_height = info.last_reward_block_height; - entry.last_reward_transaction_index = info.last_reward_transaction_index; - entry.active = info.is_active(); - entry.funded = info.is_fully_funded(); - entry.state_height = info.is_fully_funded() - ? (info.is_decommissioned() ? info.last_decommission_height : info.active_since_height) : info.last_reward_block_height; - auto hf_version = m_core.get_blockchain_storage().get_network_version(); - entry.earned_downtime_blocks = master_nodes::quorum_cop::calculate_decommission_credit(info, current_height, hf_version); - entry.decommission_count = info.decommission_count; - entry.last_decommission_reason_consensus_all = info.last_decommission_reason_consensus_all; - entry.last_decommission_reason_consensus_any = info.last_decommission_reason_consensus_any; - m_core.get_master_node_list().access_proof(mn_info.pubkey, [ - this, &entry, is_me = (m_core.master_node() && m_core.get_master_keys().pub == mn_info.pubkey) - ](const auto &proof) { - if (is_me) { - entry.master_node_version = BELDEX_VERSION; - entry.belnet_version = m_core.belnet_version; - entry.storage_server_version = m_core.ss_version; - entry.public_ip = epee::string_tools::get_ip_string_from_int32(m_core.mn_public_ip()); - entry.storage_port = m_core.storage_https_port(); - entry.storage_lmq_port = m_core.storage_omq_port(); - entry.quorumnet_port = m_core.quorumnet_port(); - entry.pubkey_ed25519 = tools::type_to_hex(m_core.get_master_keys().pub_ed25519); - entry.pubkey_x25519 = tools::type_to_hex(m_core.get_master_keys().pub_x25519); - } - else{ - entry.master_node_version = proof.proof->version; - entry.belnet_version = proof.proof->belnet_version; - entry.storage_server_version = proof.proof->storage_server_version; - entry.public_ip = epee::string_tools::get_ip_string_from_int32(proof.proof->public_ip); - entry.storage_port = proof.proof->storage_https_port; - entry.storage_lmq_port = proof.proof->storage_omq_port; - entry.pubkey_ed25519 = proof.proof->pubkey_ed25519 ? tools::type_to_hex(proof.proof->pubkey_ed25519) : ""; - entry.pubkey_x25519 = proof.pubkey_x25519 ? tools::type_to_hex(proof.pubkey_x25519) : ""; - entry.quorumnet_port = proof.proof->qnet_port; - } - // NOTE: Master Node Testing - entry.last_uptime_proof = proof.timestamp; - auto system_now = std::chrono::system_clock::now(); - auto steady_now = std::chrono::steady_clock::now(); - auto& netconf = m_core.get_net_config(); - entry.storage_server_reachable = !proof.ss_reachable.unreachable_for(netconf.UPTIME_PROOF_VALIDITY - netconf.UPTIME_PROOF_FREQUENCY, steady_now); - entry.storage_server_first_unreachable = reachable_to_time_t(proof.ss_reachable.first_unreachable, system_now, steady_now); - entry.storage_server_last_unreachable = reachable_to_time_t(proof.ss_reachable.last_unreachable, system_now, steady_now); - entry.storage_server_last_reachable = reachable_to_time_t(proof.ss_reachable.last_reachable, system_now, steady_now); - entry.belnet_reachable = !proof.belnet_reachable.unreachable_for(netconf.UPTIME_PROOF_VALIDITY - netconf.UPTIME_PROOF_FREQUENCY, steady_now); - entry.belnet_first_unreachable = reachable_to_time_t(proof.belnet_reachable.first_unreachable, system_now, steady_now); - entry.belnet_last_unreachable = reachable_to_time_t(proof.belnet_reachable.last_unreachable, system_now, steady_now); - entry.belnet_last_reachable = reachable_to_time_t(proof.belnet_reachable.last_reachable, system_now, steady_now); - - master_nodes::participation_history const &checkpoint_participation = proof.checkpoint_participation; - master_nodes::participation_history const &POS_participation = proof.POS_participation; - master_nodes::participation_history const ×tamp_participation = proof.timestamp_participation; - master_nodes::participation_history const ×ync_status = proof.timesync_status; - entry.checkpoint_participation = std::vector(checkpoint_participation.begin(), checkpoint_participation.end()); - entry.POS_participation = std::vector(POS_participation.begin(), POS_participation.end()); - entry.timestamp_participation = std::vector(timestamp_participation.begin(), timestamp_participation.end()); - entry.timesync_status = std::vector(timesync_status.begin(), timesync_status.end()); + set_if_requested(reqed, binary, "master_node_pubkey", mn_info.pubkey); + set_if_requested(reqed, entry, + "registration_height", info.registration_height, + "requested_unlock_height", info.requested_unlock_height, + "last_reward_block_height", info.last_reward_block_height, + "last_reward_transaction_index", info.last_reward_transaction_index, + "active", info.is_active(), + "funded", info.is_fully_funded(), + "state_height", info.is_fully_funded() + ? (info.is_decommissioned() ? info.last_decommission_height : info.active_since_height) + : info.last_reward_block_height, + "earned_downtime_blocks", master_nodes::quorum_cop::calculate_decommission_credit(info, top_height, hf_version), + "decommission_count", info.decommission_count, + "total_contributed", info.total_contributed, + "staking_requirement", info.staking_requirement, + "portions_for_operator", info.portions_for_operator, + "operator_fee", microportion(info.portions_for_operator), + "operator_address", cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*subaddress*/, info.operator_address), + "swarm_id", info.swarm_id, + "swarm", tools::int_to_string(info.swarm_id, 16), + "registration_hf_version", info.registration_hf_version + ); + + if (requested(reqed, "total_reserved") && info.total_reserved != info.total_contributed) + entry["total_reserved"] = info.total_reserved; + + if (info.last_decommission_reason_consensus_any) { + set_if_requested(reqed, entry, + "last_decommission_reason_consensus_all", info.last_decommission_reason_consensus_all, + "last_decommission_reason_consensus_any", info.last_decommission_reason_consensus_any); + + if (requested(reqed, "last_decomm_reasons")) { + auto& reasons = (entry["last_decomm_reasons"] = json{ + {"all", cryptonote::coded_reasons(info.last_decommission_reason_consensus_all)}}); + if (auto some = cryptonote::coded_reasons(info.last_decommission_reason_consensus_any & ~info.last_decommission_reason_consensus_all); + !some.empty()) + reasons["some"] = std::move(some); + } + } + + auto& netconf = m_core.get_net_config(); + // FIXME: accessing proofs one-by-one like this is kind of gross. + m_core.get_master_node_list().access_proof(mn_info.pubkey, [&](const auto& proof) { + if (proof.proof->public_ip != 0) + set_if_requested(reqed, entry, + "master_node_version", proof.proof->version, + "belnet_version", proof.proof->belnet_version, + "storage_server_version", proof.proof->storage_server_version, + "public_ip", epee::string_tools::get_ip_string_from_int32(proof.proof->public_ip), + "storage_port", proof.proof->storage_https_port, + "storage_lmq_port", proof.proof->storage_omq_port, + "quorumnet_port", proof.proof->qnet_port); + if (proof.proof->pubkey_ed25519) + set_if_requested(reqed, binary, + "pubkey_ed25519", proof.proof->pubkey_ed25519, + "pubkey_x25519", proof.pubkey_x25519); + + auto system_now = std::chrono::system_clock::now(); + auto steady_now = std::chrono::steady_clock::now(); + set_if_requested(reqed, entry, "last_uptime_proof", proof.timestamp); + if (m_core.master_node()) { + set_if_requested(reqed, entry, + "storage_server_reachable", !proof.ss_reachable.unreachable_for(netconf.UPTIME_PROOF_VALIDITY - netconf.UPTIME_PROOF_FREQUENCY, steady_now), + "belnet_reachable", !proof.belnet_reachable.unreachable_for(netconf.UPTIME_PROOF_VALIDITY - netconf.UPTIME_PROOF_FREQUENCY, steady_now)); + if (proof.ss_reachable.first_unreachable != master_nodes::NEVER && requested(reqed, "storage_server_first_unreachable")) + entry["storage_server_first_unreachable"] = reachable_to_time_t(proof.ss_reachable.first_unreachable, system_now, steady_now); + if (proof.ss_reachable.last_unreachable != master_nodes::NEVER && requested(reqed, "storage_server_last_unreachable")) + entry["storage_server_last_unreachable"] = reachable_to_time_t(proof.ss_reachable.last_unreachable, system_now, steady_now); + if (proof.ss_reachable.last_reachable != master_nodes::NEVER && requested(reqed, "storage_server_last_reachable")) + entry["storage_server_last_reachable"] = reachable_to_time_t(proof.ss_reachable.last_reachable, system_now, steady_now); + if (proof.belnet_reachable.first_unreachable != master_nodes::NEVER && requested(reqed, "belnet_first_unreachable")) + entry["belnet_first_unreachable"] = reachable_to_time_t(proof.belnet_reachable.first_unreachable, system_now, steady_now); + if (proof.belnet_reachable.last_unreachable != master_nodes::NEVER && requested(reqed, "belnet_last_unreachable")) + entry["belnet_last_unreachable"] = reachable_to_time_t(proof.belnet_reachable.last_unreachable, system_now, steady_now); + if (proof.belnet_reachable.last_reachable != master_nodes::NEVER && requested(reqed, "belnet_last_reachable")) + entry["belnet_last_reachable"] = reachable_to_time_t(proof.belnet_reachable.last_reachable, system_now, steady_now); + } + + if (requested(reqed, "checkpoint_votes") && !proof.checkpoint_participation.empty()) { + std::vector voted, missed; + for (auto& cpp : proof.checkpoint_participation) + (cpp.pass() ? voted : missed).push_back(cpp.height); + std::sort(voted.begin(), voted.end()); + std::sort(missed.begin(), missed.end()); + entry["checkpoint_votes"] = json{ + {"voted", voted}, + {"missed", missed}}; + } + if (requested(reqed, "POS_votes") && !proof.POS_participation.empty()) { + std::vector> voted, missed; + for (auto& ppp : proof.POS_participation) + (ppp.pass() ? voted : missed).emplace_back(ppp.height, ppp.round); + std::sort(voted.begin(), voted.end()); + std::sort(missed.begin(), missed.end()); + entry["POS_votes"]["voted"] = voted; + entry["POS_votes"]["missed"] = missed; + } + if (requested(reqed, "quorumnet_tests") && !proof.timestamp_participation.empty()) { + auto fails = proof.timestamp_participation.failures(); + entry["quorumnet_tests"] = json::array({proof.timestamp_participation.size() - fails, fails}); + } + if (requested(reqed, "timesync_tests") && !proof.timesync_status.empty()) { + auto fails = proof.timesync_status.failures(); + entry["timesync_tests"] = json::array({proof.timesync_status.size() - fails, fails}); + } }); - entry.contributors.reserve(info.contributors.size()); - - using namespace master_nodes; - for (master_node_info::contributor_t const &contributor : info.contributors) - { - entry.contributors.push_back({}); - auto &new_contributor = entry.contributors.back(); - new_contributor.amount = contributor.amount; - new_contributor.reserved = contributor.reserved; - new_contributor.address = cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*is_subaddress*/, contributor.address); - - new_contributor.locked_contributions.reserve(contributor.locked_contributions.size()); - for (master_node_info::contribution_t const &src : contributor.locked_contributions) - { - new_contributor.locked_contributions.push_back({}); - auto &dest = new_contributor.locked_contributions.back(); - dest.amount = src.amount; - dest.key_image = tools::type_to_hex(src.key_image); - dest.key_image_pub_key = tools::type_to_hex(src.key_image_pub_key); + if (requested(reqed, "contributors")) { + auto& contributors = (entry["contributors"] = json::array()); + for (const auto& contributor : info.contributors) { + auto& c = contributors.emplace_back(json{ + {"amount", contributor.amount}, + {"address", cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*subaddress*/, contributor.address)}}); + if (contributor.reserved != contributor.amount) + c["reserved"] = contributor.reserved; + if (requested(reqed, "locked_contributions")) { + auto& locked = (c["locked_contributions"] = json::array()); + for (const auto& src : contributor.locked_contributions) { + auto& lc = locked.emplace_back(json{{"amount", src.amount}}); + json_binary_proxy lc_binary{lc, binary_format}; + lc_binary["key_image"] = src.key_image; + lc_binary["key_image_pub_key"] = src.key_image_pub_key; + } + } } } - - entry.total_contributed = info.total_contributed; - entry.total_reserved = info.total_reserved; - entry.staking_requirement = info.staking_requirement; - entry.portions_for_operator = info.portions_for_operator; - entry.operator_address = cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*is_subaddress*/, info.operator_address); - entry.swarm_id = info.swarm_id; - std::string raw; - raw.resize(sizeof(info.swarm_id)); - oxenc::write_host_as_big(info.swarm_id, raw.data()); - entry.swarm = oxenc::to_hex(raw); - entry.registration_hf_version = info.registration_hf_version; - } - static constexpr GET_MASTER_NODES::requested_fields_t all_fields{true}; //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_NODES::response core_rpc_server::invoke(GET_MASTER_NODES::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_NODES& mns, rpc_context context) { - GET_MASTER_NODES::response res{}; - - res.status = STATUS_OK; - res.height = m_core.get_current_blockchain_height() - 1; - res.target_height = m_core.get_target_blockchain_height(); - res.block_hash = tools::type_to_hex(m_core.get_block_id_by_height(res.height)); - auto [hf, mnode_rev] = get_network_version_revision(nettype(), res.height); - res.hardfork = static_cast(hf); - res.mnode_revision = mnode_rev; - - if (!req.poll_block_hash.empty()) { - res.polling_mode = true; - if (req.poll_block_hash == res.block_hash) { - res.unchanged = true; - res.fields = req.fields.value_or(all_fields); - return res; - } - } - - std::vector pubkeys(req.master_node_pubkeys.size()); - for (size_t i = 0; i < req.master_node_pubkeys.size(); i++) - { - if (!tools::hex_to_type(req.master_node_pubkeys[i], pubkeys[i])) - throw rpc_error{ERROR_WRONG_PARAM, - "Could not convert to a public key, arg: " + std::to_string(i) - + " which is pubkey: " + req.master_node_pubkeys[i]}; - } - - auto mn_infos = m_core.get_master_node_list_state(pubkeys); - - if (req.active_only) { - const auto end = + auto& req = mns.request; + mns.response["status"] = STATUS_OK; + auto [top_height, top_hash] = m_core.get_blockchain_top(); + auto [hf, mnode_rev] = get_network_version_revision(nettype(), top_height); + set_if_requested(req.fields, mns.response, + "height", top_height, + "target_height", m_core.get_target_blockchain_height(), + "hardfork", hf, + "mnode_revision", mnode_rev); + set_if_requested(req.fields, mns.response_hex, + "block_hash", top_hash); + + if (req.poll_block_hash) { + bool unchanged = req.poll_block_hash == top_hash; + mns.response["unchanged"] = unchanged; + if (unchanged) + return; + if (!requested(req.fields, "block_hash")) + mns.response_hex["block_hash"] = top_hash; // Force it on a poll request even if it wasn't a requested field + } + + auto mn_infos = m_core.get_master_node_list_state(req.master_node_pubkeys); + + if (req.active_only) + mn_infos.erase( std::remove_if(mn_infos.begin(), mn_infos.end(), [](const master_nodes::master_node_pubkey_info& mnpk_info) { return !mnpk_info.info->is_active(); - }); - - mn_infos.erase(end, mn_infos.end()); - } - - if (req.limit != 0) { - - const auto limit = std::min(mn_infos.size(), static_cast(req.limit)); - + }), + mn_infos.end()); + + const int top_mn_index = (int) mn_infos.size() - 1; + if (req.limit < 0 || req.limit > top_mn_index) { + // We asked for -1 (no limit but shuffle) or a value >= the count, so just shuffle the entire list + std::shuffle(mn_infos.begin(), mn_infos.end(), tools::rng); + } else if (req.limit > 0) { // We need to select N random elements, in random order, from yyyyyyyy. We could (and used // to) just shuffle the entire list and return the first N, but that is quite inefficient when // the list is large and N is small. So instead this algorithm is going to select a random - // element from yyyyyyyy, swap it to position 0, so we get: [x]yyyyyyyy where one of the new - // y's used to be at element 0. Then we select a random element from the new y's (i.e. all // the elements beginning at position 1), and swap it into element 1, to get [xx]yyyyyy, then // keep repeating until our set of x's is big enough, say [xxx]yyyyy. At that point we chop // of the y's to just be left with [xxx], and only required N swaps in total. - for (size_t i = 0; i < limit; i++) + for (int i = 0; i < req.limit; i++) { - size_t j = std::uniform_int_distribution{i, mn_infos.size()-1}(tools::rng); + int j = std::uniform_int_distribution{i, top_mn_index}(tools::rng); using std::swap; if (i != j) swap(mn_infos[i], mn_infos[j]); } - mn_infos.resize(limit); + mn_infos.resize(req.limit); } - res.master_node_states.reserve(mn_infos.size()); - res.fields = req.fields.value_or(all_fields); - - if (req.include_json) - { - if (mn_infos.empty()) - res.as_json = "{}"; - else - res.as_json = cryptonote::obj_to_json_str(mn_infos); - } - - for (auto &pubkey_info : mn_infos) { - res.master_node_states.emplace_back(); - fill_mn_response_entry(res.master_node_states.back(), pubkey_info, res.height); - } - - return res; + auto& mn_states = (mns.response["master_node_states"] = json::array()); + for (auto &pubkey_info : mn_infos) + fill_mn_response_entry(mn_states.emplace_back(json::object()), mns.is_bt(), req.fields, pubkey_info, top_height); } namespace { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 549c0d44eb9..2fdbe8c0952 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -201,6 +201,8 @@ namespace cryptonote::rpc { void invoke(GET_HEIGHT& req, rpc_context context); void invoke(GET_INFO& info, rpc_context context); void invoke(BNS_RESOLVE& resolve, rpc_context context); + void invoke(GET_MASTER_NODE_STATUS& sns, rpc_context context); + void invoke(GET_MASTER_NODES& sns, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -269,8 +271,6 @@ namespace cryptonote::rpc { GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::request&& req, rpc_context context); GET_MASTER_KEYS::response invoke(GET_MASTER_KEYS::request&& req, rpc_context context); GET_MASTER_PRIVKEYS::response invoke(GET_MASTER_PRIVKEYS::request&& req, rpc_context context); - GET_MASTER_NODE_STATUS::response invoke(GET_MASTER_NODE_STATUS::request&& req, rpc_context context); - GET_MASTER_NODES::response invoke(GET_MASTER_NODES::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); STORAGE_SERVER_PING::response invoke(STORAGE_SERVER_PING::request&& req, rpc_context context); BELNET_PING::response invoke(BELNET_PING::request&& req, rpc_context context); @@ -326,7 +326,12 @@ namespace cryptonote::rpc { private: bool check_core_ready(); - void fill_mn_response_entry(GET_MASTER_NODES::response::entry& entry, const master_nodes::master_node_pubkey_info &mn_info, uint64_t current_height); + void fill_mn_response_entry( + nlohmann::json& entry, + bool is_bt, + const std::unordered_set& requested, + const master_nodes::master_node_pubkey_info& mn_info, + uint64_t top_height); //utils uint64_t get_block_reward(const block& blk); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 32726ef7ea9..da551cacaff 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -187,4 +187,39 @@ namespace cryptonote::rpc { "type", required{bns.request.type}); } + void parse_request(GET_MASTER_NODES& mns, rpc_input in) { + // Remember: key access must be in sorted order (even across get_values() calls). + get_values(in, "active_only", mns.request.active_only); + bool fields_dict = false; + if (auto* json_in = std::get_if(&in)) { + // Deprecated {"field":true, "field2":true, ...} handling: + if (auto fit = json_in->find("fields"); fit != json_in->end() && fit->is_object()) { + fields_dict = true; + for (auto& [k, v] : fit->items()) { + if (v.get()) { + if (k == "all") { + mns.request.fields.clear(); // Empty means all + break; // The old behaviour just ignored everything else if you specified all + } + mns.request.fields.insert(k); + } + } + } + } + if (!fields_dict) { + std::vector fields; + get_values(in, "fields", fields); + for (const auto& f : fields) + mns.request.fields.emplace(f); + // If the only thing given is "all" then just clear it (as a small optimization): + if (mns.request.fields.size() == 1 && *mns.request.fields.begin() == "all") + mns.request.fields.clear(); + } + + get_values(in, + "limit", mns.request.limit, + "poll_block_hash", mns.request.poll_block_hash, + "master_node_pubkeys", mns.request.master_node_pubkeys); + } + } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index d346f5cce02..0dbbaa1412e 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -10,5 +10,7 @@ namespace cryptonote::rpc { inline void parse_request(NO_ARGS&, rpc_input) {} - void parse_request(BNS_RESOLVE& bns, rpc_input); + void parse_request(BNS_RESOLVE& bns, rpc_input in); + void parse_request(GET_MASTER_NODES& mns, rpc_input in); + } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index fc8913266e1..60940dbc04f 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1075,161 +1075,161 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_PRIVKEYS::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(master_node_contribution) - KV_SERIALIZE(key_image) - KV_SERIALIZE(key_image_pub_key) - KV_SERIALIZE(amount) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(master_node_contribution) +// KV_SERIALIZE(key_image) +// KV_SERIALIZE(key_image_pub_key) +// KV_SERIALIZE(amount) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(master_node_contributor) - KV_SERIALIZE(amount) - KV_SERIALIZE(reserved) - KV_SERIALIZE(address) - KV_SERIALIZE(locked_contributions) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(master_node_contributor) +// KV_SERIALIZE(amount) +// KV_SERIALIZE(reserved) +// KV_SERIALIZE(address) +// KV_SERIALIZE(locked_contributions) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::requested_fields_t) - KV_SERIALIZE(all) - if (!this_ref.all) - { - KV_SERIALIZE(master_node_pubkey) - KV_SERIALIZE(registration_height) - KV_SERIALIZE(registration_hf_version) - KV_SERIALIZE(requested_unlock_height) - KV_SERIALIZE(last_reward_block_height) - KV_SERIALIZE(last_reward_transaction_index) - KV_SERIALIZE(active) - KV_SERIALIZE(funded) - KV_SERIALIZE(state_height) - KV_SERIALIZE(decommission_count) - KV_SERIALIZE(earned_downtime_blocks) - KV_SERIALIZE(master_node_version) - KV_SERIALIZE(belnet_version) - KV_SERIALIZE(storage_server_version) - KV_SERIALIZE(contributors) - KV_SERIALIZE(total_contributed) - KV_SERIALIZE(total_reserved) - KV_SERIALIZE(staking_requirement) - KV_SERIALIZE(portions_for_operator) - KV_SERIALIZE(swarm_id) - KV_SERIALIZE(swarm) - KV_SERIALIZE(operator_address) - KV_SERIALIZE(public_ip) - KV_SERIALIZE(storage_port) - KV_SERIALIZE(storage_lmq_port) - KV_SERIALIZE(quorumnet_port) - KV_SERIALIZE(pubkey_ed25519) - KV_SERIALIZE(pubkey_x25519) - KV_SERIALIZE(block_hash) - KV_SERIALIZE(height) - KV_SERIALIZE(target_height) - KV_SERIALIZE(hardfork) - KV_SERIALIZE(mnode_revision) - - KV_SERIALIZE(last_uptime_proof) - KV_SERIALIZE(storage_server_reachable) - KV_SERIALIZE(storage_server_first_unreachable) - KV_SERIALIZE(storage_server_last_unreachable) - KV_SERIALIZE(storage_server_last_reachable) - KV_SERIALIZE(belnet_reachable) - KV_SERIALIZE(belnet_first_unreachable) - KV_SERIALIZE(belnet_last_unreachable) - KV_SERIALIZE(belnet_last_reachable) - KV_SERIALIZE(checkpoint_participation) - KV_SERIALIZE(POS_participation) - KV_SERIALIZE(timestamp_participation) - KV_SERIALIZE(timesync_status) - } -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::requested_fields_t) +// KV_SERIALIZE(all) +// if (!this_ref.all) +// { +// KV_SERIALIZE(master_node_pubkey) +// KV_SERIALIZE(registration_height) +// KV_SERIALIZE(registration_hf_version) +// KV_SERIALIZE(requested_unlock_height) +// KV_SERIALIZE(last_reward_block_height) +// KV_SERIALIZE(last_reward_transaction_index) +// KV_SERIALIZE(active) +// KV_SERIALIZE(funded) +// KV_SERIALIZE(state_height) +// KV_SERIALIZE(decommission_count) +// KV_SERIALIZE(earned_downtime_blocks) +// KV_SERIALIZE(master_node_version) +// KV_SERIALIZE(belnet_version) +// KV_SERIALIZE(storage_server_version) +// KV_SERIALIZE(contributors) +// KV_SERIALIZE(total_contributed) +// KV_SERIALIZE(total_reserved) +// KV_SERIALIZE(staking_requirement) +// KV_SERIALIZE(portions_for_operator) +// KV_SERIALIZE(swarm_id) +// KV_SERIALIZE(swarm) +// KV_SERIALIZE(operator_address) +// KV_SERIALIZE(public_ip) +// KV_SERIALIZE(storage_port) +// KV_SERIALIZE(storage_lmq_port) +// KV_SERIALIZE(quorumnet_port) +// KV_SERIALIZE(pubkey_ed25519) +// KV_SERIALIZE(pubkey_x25519) +// KV_SERIALIZE(block_hash) +// KV_SERIALIZE(height) +// KV_SERIALIZE(target_height) +// KV_SERIALIZE(hardfork) +// KV_SERIALIZE(mnode_revision) + +// KV_SERIALIZE(last_uptime_proof) +// KV_SERIALIZE(storage_server_reachable) +// KV_SERIALIZE(storage_server_first_unreachable) +// KV_SERIALIZE(storage_server_last_unreachable) +// KV_SERIALIZE(storage_server_last_reachable) +// KV_SERIALIZE(belnet_reachable) +// KV_SERIALIZE(belnet_first_unreachable) +// KV_SERIALIZE(belnet_last_unreachable) +// KV_SERIALIZE(belnet_last_reachable) +// KV_SERIALIZE(checkpoint_participation) +// KV_SERIALIZE(POS_participation) +// KV_SERIALIZE(timestamp_participation) +// KV_SERIALIZE(timesync_status) +// } +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::request) - KV_SERIALIZE(master_node_pubkeys); - KV_SERIALIZE(include_json); - KV_SERIALIZE(limit) - KV_SERIALIZE(active_only) - KV_SERIALIZE(fields) - KV_SERIALIZE(poll_block_hash) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::response::entry) - const auto* res = stg.template get_context(); - const bool all = !is_store || !res || res->fields.all; - - #define KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(var) if (all || res->fields.var) KV_SERIALIZE(var) - - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(master_node_pubkey); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(registration_height); - if (all || res->fields.registration_hf_version) KV_SERIALIZE_ENUM(registration_hf_version); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(requested_unlock_height); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_reward_block_height); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_reward_transaction_index); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(active); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(funded); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(state_height); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(decommission_count); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(earned_downtime_blocks); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(master_node_version); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_version) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_version) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(contributors); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(total_contributed); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(total_reserved); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(staking_requirement); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(portions_for_operator); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(swarm_id); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(swarm); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(operator_address); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(public_ip); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_port); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_lmq_port); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(quorumnet_port); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(pubkey_ed25519); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(pubkey_x25519); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_uptime_proof); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_reachable); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_first_unreachable) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_last_unreachable) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_last_reachable) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_reachable); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_first_unreachable) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_last_unreachable) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_last_reachable) - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(checkpoint_participation); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(POS_participation); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(timestamp_participation); - KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(timesync_status); -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::response) - if (!unchanged) KV_SERIALIZE_DEPENDENT(master_node_states) - KV_SERIALIZE(status) - if (fields.height || fields.all) KV_SERIALIZE(height) - if (fields.target_height || fields.all) KV_SERIALIZE(target_height) - if (fields.block_hash || fields.all || (polling_mode && !unchanged)) KV_SERIALIZE(block_hash) - if (fields.hardfork || fields.all) KV_SERIALIZE(hardfork) - if (fields.mnode_revision || fields.all) KV_SERIALIZE(mnode_revision) - if (!as_json.empty()) KV_SERIALIZE(as_json) - if (polling_mode) KV_SERIALIZE(unchanged); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::request) +// KV_SERIALIZE(master_node_pubkeys); +// KV_SERIALIZE(include_json); +// KV_SERIALIZE(limit) +// KV_SERIALIZE(active_only) +// KV_SERIALIZE(fields) +// KV_SERIALIZE(poll_block_hash) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_STATUS::request) - KV_SERIALIZE(include_json); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::response::entry) +// const auto* res = stg.template get_context(); +// const bool all = !is_store || !res || res->fields.all; + +// #define KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(var) if (all || res->fields.var) KV_SERIALIZE(var) + +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(master_node_pubkey); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(registration_height); +// if (all || res->fields.registration_hf_version) KV_SERIALIZE_ENUM(registration_hf_version); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(requested_unlock_height); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_reward_block_height); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_reward_transaction_index); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(active); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(funded); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(state_height); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(decommission_count); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(earned_downtime_blocks); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(master_node_version); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_version) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_version) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(contributors); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(total_contributed); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(total_reserved); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(staking_requirement); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(portions_for_operator); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(swarm_id); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(swarm); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(operator_address); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(public_ip); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_port); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_lmq_port); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(quorumnet_port); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(pubkey_ed25519); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(pubkey_x25519); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(last_uptime_proof); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_reachable); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_first_unreachable) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_last_unreachable) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(storage_server_last_reachable) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_reachable); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_first_unreachable) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_last_unreachable) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(belnet_last_reachable) +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(checkpoint_participation); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(POS_participation); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(timestamp_participation); +// KV_SERIALIZE_ENTRY_FIELD_IF_REQUESTED(timesync_status); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_STATUS::response) - KV_SERIALIZE(master_node_state) - KV_SERIALIZE(height) - KV_SERIALIZE(block_hash) - KV_SERIALIZE(status) - KV_SERIALIZE(as_json) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODES::response) +// if (!unchanged) KV_SERIALIZE_DEPENDENT(master_node_states) +// KV_SERIALIZE(status) +// if (fields.height || fields.all) KV_SERIALIZE(height) +// if (fields.target_height || fields.all) KV_SERIALIZE(target_height) +// if (fields.block_hash || fields.all || (polling_mode && !unchanged)) KV_SERIALIZE(block_hash) +// if (fields.hardfork || fields.all) KV_SERIALIZE(hardfork) +// if (fields.mnode_revision || fields.all) KV_SERIALIZE(mnode_revision) +// if (!as_json.empty()) KV_SERIALIZE(as_json) +// if (polling_mode) KV_SERIALIZE(unchanged); +// KV_SERIALIZE_MAP_CODE_END() + + +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_STATUS::request) +// KV_SERIALIZE(include_json); +// KV_SERIALIZE_MAP_CODE_END() + + +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_STATUS::response) +// KV_SERIALIZE(master_node_state) +// KV_SERIALIZE(height) +// KV_SERIALIZE(block_hash) +// KV_SERIALIZE(status) +// KV_SERIALIZE(as_json) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(STORAGE_SERVER_PING::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index db555081c29..7023ddc42bb 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -31,6 +31,19 @@ #pragma once +// vim help for nicely wrapping/formatting comments in here: +// Global options for wrapping and indenting lists within comments with gq: +// +// set formatoptions+=n +// set formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s\\+\\\\|^\\s*[-+*]\\s\\+ +// +// cpp-specific options to properly recognize `///` as a comment when wrapping, to go in +// ~/.vim/after/ftplugin/cpp.vim: +// +// setlocal comments-=:// +// setlocal comments+=:/// +// setlocal comments+=:// + #include "crypto/crypto.h" #include "epee/string_tools.h" @@ -55,13 +68,12 @@ #include #include #include - -namespace cryptonote { +#include /// Namespace for core RPC commands. Every RPC commands gets defined here (including its name(s), /// access, and data type), and added to `core_rpc_types` list at the bottom of the file. -namespace rpc { +namespace cryptonote::rpc { using version_t = std::pair; @@ -71,7 +83,7 @@ namespace rpc { // has its own version, and that clients can just test major to see // whether they can talk to a given daemon without having to know in // advance which version they will stop working with - constexpr version_t VERSION = {4, 0}; + constexpr version_t VERSION = {4, 1}; /// Makes a version array from a packed 32-bit integer version constexpr version_t make_version(uint32_t version) @@ -351,7 +363,7 @@ namespace rpc { { std::optional old_dereg; // Will be present and set to true iff this record is an old (pre-HF12) deregistration field std::string type; // "dereg", "decom", "recom", or "ip" indicating the state change type - uint64_t height; // The voting block height for the changing service node and validators + uint64_t height; // The voting block height for the changing master node and validators uint32_t index; // The index of all tested nodes at the given height for which this state change applies std::vector voters; // The position of validators in the testing quorum who validated and voted for this state change. This typically contains just 7 required voter slots (of 10 eligible voters). std::optional> reasons; // Reasons for the decommissioning/deregistration as reported by the voting quorum. This contains any reasons that all voters agreed on, one or more of: "uptime" (missing uptime proofs), "checkpoints" (missed checkpoint votes), "POS" (missing POS votes), "storage" (storage server pings failed), "belnet" (belnet router unreachable), "timecheck" (time sync pings failed), "timesync" (time was out of sync) @@ -675,7 +687,7 @@ namespace rpc { /// \arg \c height Current length of longest chain known to daemon. /// \arg \c target_height The height of the next block in the chain. /// \arg \c immutable_height The latest height in the blockchain that can not be reorganized (i.e. - /// is backed by at least 2 Service Node, or 1 hardcoded checkpoint, 0 if N/A). Omitted if it + /// is backed by at least 2 Master Node, or 1 hardcoded checkpoint, 0 if N/A). Omitted if it /// cannot be determined (typically because the node is still syncing). /// \arg \c POS will be true if the next expected block is a POS block, false otherwise. /// \arg \c POS_ideal_timestamp For POS blocks this is the ideal timestamp of the next block, @@ -899,7 +911,7 @@ namespace rpc { std::string hash; // The hash of this block. difficulty_type difficulty; // The strength of the Beldex network based on mining power. difficulty_type cumulative_difficulty; // The cumulative strength of the Beldex network based on mining power. - uint64_t reward; // The amount of new generated in this block and rewarded to the miner, foundation and service Nodes. Note: 1 BELDEX = 1e9 atomic units. + uint64_t reward; // The amount of new generated in this block and rewarded to the miner, foundation and master Nodes. Note: 1 BELDEX = 1e9 atomic units. uint64_t miner_reward; // The amount of new generated in this block and rewarded to the miner. Note: 1 BELDEX = 1e9 atomic units. uint64_t block_size; // The block size in bytes. uint64_t block_weight; // The block weight in bytes. @@ -1945,7 +1957,7 @@ namespace rpc { struct quorum_t { - std::vector validators; // List of service node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and flash these are the participating nodes (there are no workers); for POS flash quorums these are the block signers. + std::vector validators; // List of master node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and flash these are the participating nodes (there are no workers); for POS flash quorums these are the block signers. std::vector workers; // Public key of the quorum workers. For obligations quorums these are the nodes being tested; for POS quorums this is the block producer. Checkpoint and Flash quorums do not populate this field. KV_MAP_SERIALIZABLE @@ -2030,8 +2042,8 @@ namespace rpc { }; BELDEX_RPC_DOC_INTROSPECT - // Get the service public keys of the queried daemon, encoded in hex. All three keys are used - // when running as a service node; when running as a regular node only the x25519 key is regularly + // Get the master public keys of the queried daemon, encoded in hex. All three keys are used + // when running as a master node; when running as a regular node only the x25519 key is regularly // used for some RPC and and node-to-MN communication requests. struct GET_MASTER_KEYS : RPC_COMMAND { @@ -2041,7 +2053,7 @@ namespace rpc { struct response { - std::string master_node_pubkey; // The queried daemon's service node public key. Will be empty if not running as a service node. + std::string master_node_pubkey; // The queried daemon's master node public key. Will be empty if not running as a master node. std::string master_node_ed25519_pubkey; // The daemon's ed25519 auxiliary public key. std::string master_node_x25519_pubkey; // The daemon's x25519 auxiliary public key. std::string status; // Generic RPC error code. "OK" is the success value. @@ -2051,9 +2063,9 @@ namespace rpc { }; BELDEX_RPC_DOC_INTROSPECT - // Get the service private keys of the queried daemon, encoded in hex. Do not ever share - // these keys: they would allow someone to impersonate your service node. All three keys are used - // when running as a service node; when running as a regular node only the x25519 key is regularly + // Get the master private keys of the queried daemon, encoded in hex. Do not ever share + // these keys: they would allow someone to impersonate your master node. All three keys are used + // when running as a master node; when running as a regular node only the x25519 key is regularly // used for some RPC and and node-to-MN communication requests. struct GET_MASTER_PRIVKEYS : RPC_COMMAND { @@ -2063,7 +2075,7 @@ namespace rpc { struct response { - std::string master_node_privkey; // The queried daemon's service node private key. Will be empty if not running as a service node. + std::string master_node_privkey; // The queried daemon's master node private key. Will be empty if not running as a master node. std::string master_node_ed25519_privkey; // The daemon's ed25519 private key (note that this is in sodium's format, which consists of the private and public keys concatenated together) std::string master_node_x25519_privkey; // The daemon's x25519 private key. std::string status; // Generic RPC error code. "OK" is the success value. @@ -2072,198 +2084,261 @@ namespace rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - struct master_node_contribution - { - std::string key_image; // The contribution's key image that is locked on the network. - std::string key_image_pub_key; // The contribution's key image, public key component - uint64_t amount; // The amount that is locked in this contribution. - - KV_MAP_SERIALIZABLE - }; - - BELDEX_RPC_DOC_INTROSPECT - struct master_node_contributor - { - uint64_t amount; // The total amount of locked Beldex in atomic units for this contributor. - uint64_t reserved; // The amount of Beldex in atomic units reserved by this contributor for this Master Node. - std::string address; // The wallet address for this contributor rewards are sent to and contributions came from. - std::vector locked_contributions; // Array of contributions from this contributor. - - KV_MAP_SERIALIZABLE - }; - - BELDEX_RPC_DOC_INTROSPECT - // Get information on some, all, or a random subset of Master Nodes. + /// Get information on some, all, or a random subset of Master Nodes. + struct master_node_contribution /// + { /// Output variables available are as follows (you can request which parameters are returned; see + std::string key_image; // The contribution's key image that is locked on the network. /// the request parameters description). Note that OXEN values are returned in atomic OXEN units, + std::string key_image_pub_key; // The contribution's key image, public key component /// which are nano-OXEN (i.e. 1.000000000 OXEN will be returned as 1000000000). + uint64_t amount; // The amount that is locked in this contribution. /// + + /// - \p height the height of the current top block. (Note that this is one less than the + KV_MAP_SERIALIZABLE /// "blockchain height" as would be returned by the \c get_info endpoint). + }; /// - \p target_height the target height of the blockchain; will be greater than height+1 if this + + /// node is still syncing the chain. + BELDEX_RPC_DOC_INTROSPECT /// - \p block_hash the hash of the most recent block + struct master_node_contributor /// - \p hardfork the current hardfork version of the daemon + { /// - \p snode_revision the current snode revision for non-hardfork, but mandatory, master node + uint64_t amount; // The total amount of locked Loki in atomic units for this contributor. /// updates. + uint64_t reserved; // The amount of Loki in atomic units reserved by this contributor for this Master Node. /// - \p status generic RPC error code; "OK" means the request was successful. + std::string address; // The wallet address for this contributor rewards are sent to and contributions came from. /// - \p unchanged when using poll_block_hash, this value is set to true and results are omitted if + std::vector locked_contributions; // Array of contributions from this contributor. /// the current block hash has not changed from the requested polling block hash. If block hash + + /// has changed this is set to false (and results included). When not polling this value is + KV_MAP_SERIALIZABLE /// omitted entirely. + }; /// - \p master_node_states list of information about all known master nodes; each element is a + + /// dict containing the following keys (which fields are included/omitted can be controlled via + BELDEX_RPC_DOC_INTROSPECT /// the "fields" input parameter): + // Get information on some, all, or a random subset of Master Nodes. /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). + /// - \p registration_height The height at which the registration for the Master Node arrived + /// on the blockchain. + /// - \p registration_hf_version The current hard fork at which the registration for the Service + /// Node arrived on the blockchain. + /// - \p requested_unlock_height If an unlock has been requested for this SN, this field + /// contains the height at which the Master Node registration expires and contributions will + /// be released. + /// - \p last_reward_block_height The height that determines when this master node will next + /// receive a reward. This field is somewhat misnamed for historic reasons: it is updated + /// when receiving a reward, but is also updated when a SN is activated, recommissioned, or + /// has an IP change position reset, and so does not strictly indicate when a reward was + /// received. + /// - \p last_reward_transaction_index When multiple Master Nodes register (or become + /// active/reactivated) at the same height (i.e. have the same last_reward_block_height), this + /// field contains the activating transaction position in the block which is used to break + /// ties in determining which SN is next in the reward list. + /// - \p active True if fully funded and not currently decommissioned (and so `funded && + /// !active` implicitly defines decommissioned). + /// - \p funded True if the required stakes have been submitted to activate this Master Node. + /// - \p state_height Indicates the height at which the master node entered its current state: + /// - If \p active: this is the height at which the master node last became active (i.e. + /// became fully staked, or was last recommissioned); + /// - If decommissioned (i.e. \p funded but not \p active): the decommissioning height; + /// - If awaiting contributions (i.e. not \p funded): the height at which the last + /// contribution (or registration) was processed. + /// - \p decommission_count The number of times the Master Node has been decommissioned since + /// registration + /// - \p last_decommission_reason_consensus_all The reason for the last decommission as voted by + /// the testing quorum SNs that decommissioned the node. This is a numeric bitfield made up + /// of the sum of given reasons (multiple reasons may be given for a decommission). Values + /// are included here if *all* quorum members agreed on the reasons: + /// - \c 0x01 - Missing uptime proofs + /// - \c 0x02 - Missed too many checkpoint votes + /// - \c 0x04 - Missed too many POS blocks + /// - \c 0x08 - Storage server unreachable + /// - \c 0x10 - Beldexd quorumnet unreachable for timesync checks + /// - \c 0x20 - Beldexd system clock is too far off + /// - \c 0x40 - Belnet unreachable + /// - \c 0x50 - Multi_mn_accept_range_not_met + /// - other bit values are reserved for future use. + /// - \p last_decommission_reason_consensus_any The reason for the last decommission as voted by + /// *any* SNs. Reasons are set here if *any* quorum member gave a reason, even if not all + /// quorum members agreed. Bit values are the same as \p + /// last_decommission_reason_consensus_all. + /// - \p decomm_reasons - a gentler version of the last_decommission_reason_consensus_all/_any + /// values: this is returned as a dict with two keys, \c "all" and \c "some", containing a + /// list of short string identifiers of the reasons. \c "all" contains reasons that are + /// agreed upon by all voting nodes; \c "some" contains reasons that were provided by some but + /// not all nodes (and is included only if there are at least one such value). Note that, + /// unlike \p last_decommission_reason_consensus_any, the \c "some" field only includes + /// reasons that are *not* included in \c "all". Returned values in the lists are: + /// - \p "uptime" + /// - \p "checkpoints" + /// - \p "POS" + /// - \p "storage" + /// - \p "timecheck" + /// - \p "timesync" + /// - \p "belnet" + /// - \p "multi-mn" + /// - other values are reserved for future use. + /// - \p earned_downtime_blocks The number of blocks earned towards decommissioning (if + /// currently active), or the number of blocks remaining until the master node is eligible + /// for deregistration (if currently decommissioned). + /// - \p master_node_version The three-element numeric version of the Master Node (as received + /// in the last uptime proof). Omitted if we have never received a proof. + /// - \p belnet_version The major, minor, patch version of the Master Node's belnet router + /// (as received in the last uptime proof). Omitted if we have never received a proof. + /// - \p storage_server_version The major, minor, patch version of the Master Node's storage + /// server (as received in the last uptime proof). Omitted if we have never received a proof. + /// - \p contributors Array of contributors, contributing to this Master Node. Each element is + /// a dict containing: + /// - \p amount The total amount of OXEN staked by this contributor into + /// this Master Node. + /// - \p reserved The amount of OXEN reserved by this contributor for this Master Node; this + /// field will be included only if the contributor has unfilled, reserved space in the + /// master node. + /// - \p address The wallet address of this contributor to which rewards are sent and from + /// which contributions were made. + /// - \p locked_contributions Array of contributions from this contributor; this field (unlike + /// the other fields inside \p contributors) is controlled by the "fields" input parameter. + /// Each element contains: + /// - \p key_image The contribution's key image which is locked on the network. + /// - \p key_image_pub_key The contribution's key image, public key component. + /// - \p amount The amount of OXEN that is locked in this contribution. + /// + /// - \p total_contributed The total amount of OXEN contributed to this Master Node. + /// - \p total_reserved The total amount of OXEN contributed or reserved for this Master Node. + /// Only included in the response if there are still unfilled reservations (i.e. if it is + /// greater than total_contributed). + /// - \p staking_requirement The total OXEN staking requirement in that is/was required to be + /// contributed for this Master Node. + /// - \p portions_for_operator The operator fee to take from the master node reward, as a + /// fraction of 18446744073709551612 (2^64 - 4) (that is, this number corresponds to 100%). + /// Note that some JSON parsers may silently change this value while parsing as typical values + /// do not fit into a double without loss of precision. + /// - \p operator_fee The operator fee expressed as thousandths of a percent (and rounded to the + /// nearest integer value). That is, 100000 corresponds to a 100% fee, 5456 corresponds to a + /// 5.456% fee. Note that this number is for human consumption; the actual value that matters + /// for the blockchain is the precise \p portions_for_operator value. + /// - \p swarm_id The numeric identifier of the Master Node's current swarm. Note that + /// returned values can exceed the precision available in a double value, which can result in + /// (changed) incorrect values by some JSON parsers. Consider using \p swarm instead if you + /// are not sure your JSON parser supports 64-bit values. + /// - \p swarm The swarm id, expressed in hexadecimal, such as \c "f4ffffffffffffff". + /// - \p operator_address The wallet address of the Master Node operator. + /// - \p public_ip The public ip address of the master node; omitted if we have not yet + /// received a network proof containing this information from the master node. + /// - \p storage_port The port number associated with the storage server; omitted if we have no + /// uptime proof yet. + /// - \p storage_lmq_port The port number associated with the storage server (oxenmq interface); + /// omitted if we have no uptime proof yet. + /// - \p quorumnet_port The port for direct SN-to-SN beldexd communication (oxenmq interface). + /// Omitted if we have no uptime proof yet. + /// - \p pubkey_ed25519 The master node's ed25519 public key for auxiliary services. Omitted if + /// we have no uptime proof yet. Note that for newer registrations this will be the same as + /// the \p master_node_pubkey. + /// - \p pubkey_x25519 The master node's x25519 public key for auxiliary services (mainly used + /// for \p quorumnet_port and the \p storage_lmq_port OxenMQ encrypted connections). + /// - \p last_uptime_proof The last time we received an uptime proof for this master node from + /// the network, in unix epoch time. 0 if we have never received one. + /// - \p storage_server_reachable True if this storage server is currently passing tests for the + /// purposes of SN node testing: true if the last test passed, or if it has been unreachable + /// for less than an hour; false if it has been failing tests for more than an hour (and thus + /// is considered unreachable). This field is omitted if the queried beldexd is not a master + /// node. + /// - \p storage_server_first_unreachable If the last test we received was a failure, this field + /// contains the timestamp when failures started. Will be 0 if the last result was a success, + /// and will be omitted if the node has not yet been tested since this beldexd last restarted. + /// - \p storage_server_last_unreachable The last time this master node's storage server failed + /// a ping test (regardless of whether or not it is currently failing). Will be omitted if it + /// has never failed a test since startup. + /// - \p storage_server_last_reachable The last time we received a successful ping response for + /// this storage server (whether or not it is currently failing). Will be omitted if we have + /// never received a successful ping response since startup. + /// - \p belnet_reachable Same as \p storage_server_reachable, but for belnet router testing. + /// - \p belnet_first_unreachable Same as \p storage_server_first_unreachable, but for belnet + /// router testing. + /// - \p belnet_last_unreachable Same as \p storage_server_last_unreachable, but for belnet + /// router testing. + /// - \p belnet_last_reachable Same as \p storage_server_last_reachable, but for belnet router + /// testing. + /// - \p checkpoint_votes dict containing recent received checkpoint voting information for this + /// master node. Service node tests will fail if too many recent POS blocks are missed. + /// Contains keys: + /// - \p voted list of blocks heights at which a required vote was received from this + /// master node + /// - \p missed list of block heights at which a vote from this master node was required + /// but not received. + /// - \p POS_votes dict containing recent POS blocks in which this master node was supposed + /// to have participated. Service node testing will fail if too many recent POS blocks are + /// missed. Contains keys: + /// - \p voted list of [HEIGHT,ROUND] pairs in which an expected POS participation was + /// recorded for this node. ROUND starts at 0 and increments for backup POS quorums if a + /// previous round does not broadcast a POS block for the given height in time. + /// - \p missed list of [HEIGHT,ROUND] pairs in which POS participation by this master node + /// was expected but did not occur. + /// - \p quorumnet_tests array containing the results of recent attempts to connect to the + /// remote node's quorumnet port (while conducting timesync checks). The array contains two + /// values: [SUCCESSES,FAILURES], where SUCCESSES is the number of recent successful + /// connections and FAILURES is the number of recent connection and/or request timeouts. If + /// there are two many failures then the master node will fail testing. + /// - \p timesync_tests array containing the results of recent time synchronization checks of + /// this master node. Contains [SUCCESSES,FAILURES] counts where SUCCESSES is the number of + /// recent checks where the system clock was relatively close and FAILURES is the number of + /// recent checks where we received a significantly out-of-sync timestamp response from the + /// master node. A master node fails tests if there are too many recent out-of-sync + /// responses. struct GET_MASTER_NODES : PUBLIC { static constexpr auto names() { return NAMES("get_master_nodes", "get_n_master_nodes", "get_all_master_nodes"); } - // Boolean values indicate whether corresponding fields should be included in the response - struct requested_fields_t { - bool all = false; // If set, overrides any individual requested fields. Defaults to *true* if "fields" is entirely omitted - bool master_node_pubkey; - bool registration_height; - bool registration_hf_version; - bool requested_unlock_height; - bool last_reward_block_height; - bool last_reward_transaction_index; - bool active; - bool funded; - bool state_height; - bool decommission_count; - bool last_decommission_reason_consensus_all; - bool last_decommission_reason_consensus_any; - bool earned_downtime_blocks; - - bool master_node_version; - bool belnet_version; - bool storage_server_version; - bool contributors; - bool total_contributed; - bool total_reserved; - bool staking_requirement; - bool portions_for_operator; - bool swarm_id; - bool swarm; - bool operator_address; - bool public_ip; - bool storage_port; - bool storage_lmq_port; - bool quorumnet_port; - bool pubkey_ed25519; - bool pubkey_x25519; - - bool last_uptime_proof; - bool storage_server_reachable; - bool storage_server_last_reachable; - bool storage_server_last_unreachable; - bool storage_server_first_unreachable; - bool belnet_reachable; - bool belnet_last_reachable; - bool belnet_last_unreachable; - bool belnet_first_unreachable; - bool checkpoint_participation; - bool POS_participation; - bool timestamp_participation; - bool timesync_status; - - bool block_hash; - bool height; - bool target_height; - bool hardfork; - bool mnode_revision; - KV_MAP_SERIALIZABLE - }; - - struct request - { - std::vector master_node_pubkeys; // Array of public keys of registered Master Nodes to get information about. Omit to query all Master Nodes. - bool include_json; // When set, the response's as_json member is filled out. - uint32_t limit; // If non-zero, select a random sample (in random order) of the given number of service nodes to return from the full list. - bool active_only; // If true, only include results for active (fully staked, not decommissioned) service nodes. - std::optional fields; // If omitted return all fields; otherwise return only the specified fields - - std::string poll_block_hash; // If specified this changes the behaviour to only return service node records if the block hash is *not* equal to the given hash; otherwise it omits the records and instead sets `"unchanged": true` in the response. This is primarily used to poll for new results where the requested results only change with new blocks. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - - struct entry { - std::string master_node_pubkey; // The public key of the Master Node. - uint64_t registration_height; // The height at which the registration for the Master Node arrived on the blockchain. - hf registration_hf_version; // The hard fork at which the registration for the Master Node arrived on the blockchain. - uint64_t requested_unlock_height; // The height at which contributions will be released and the Master Node expires. 0 if not requested yet. - uint64_t last_reward_block_height; // The height that determines when this service node will next receive a reward. This field is updated when receiving a reward, but is also updated when a MN is activated, recommissioned, or has an IP change position reset. - uint32_t last_reward_transaction_index; // When multiple Master Nodes register (or become active/reactivated) at the same height (i.e. have the same last_reward_block_height), this field contains the activating transaction position in the block which is used to break ties in determining which MN is next in the reward list. - bool active; // True if fully funded and not currently decommissioned (and so `active && !funded` implicitly defines decommissioned) - bool funded; // True if the required stakes have been submitted to activate this Master Node - uint64_t state_height; // If active: the state at which the service node became active (i.e. fully staked height, or last recommissioning); if decommissioned: the decommissioning height; if awaiting: the last contribution (or registration) height - uint32_t decommission_count; // The number of times the Master Node has been decommissioned since registration - uint16_t last_decommission_reason_consensus_all; // The reason for the last decommission as voted by all MNs - uint16_t last_decommission_reason_consensus_any; // The reason for the last decommission as voted by any MNs - int64_t earned_downtime_blocks; // The number of blocks earned towards decommissioning, or the number of blocks remaining until deregistration if currently decommissioned - std::array master_node_version; // The major, minor, patch version of the Master Node respectively. - std::array belnet_version; // The major, minor, patch version of the Master Node's belnet router. - std::array storage_server_version; // The major, minor, patch version of the Master Node's storage server. - std::vector contributors; // Array of contributors, contributing to this Master Node. - uint64_t total_contributed; // The total amount of Beldex in atomic units contributed to this Master Node. - uint64_t total_reserved; // The total amount of Beldex in atomic units reserved in this Master Node. - uint64_t staking_requirement; // The staking requirement in atomic units that is required to be contributed to become a Master Node. - uint64_t portions_for_operator; // The operator percentage cut to take from each reward expressed in portions, see cryptonote_config.h's STAKING_PORTIONS. - uint64_t swarm_id; // The identifier of the Master Node's current swarm. - std::string swarm; // The identifier of the Master Node's current swarm, as a 16-character hex string. - std::string operator_address; // The wallet address of the operator to which the operator cut of the staking reward is sent to. - std::string public_ip; // The public ip address of the service node - uint16_t storage_port; // The port number associated with the storage server - uint16_t storage_lmq_port; // The port number associated with the storage server (oxenmq interface) - uint16_t quorumnet_port; // The port for direct MN-to-MN communication - std::string pubkey_ed25519; // The service node's ed25519 public key for auxiliary services - std::string pubkey_x25519; // The service node's x25519 public key for auxiliary services - - // Master Node Testing - uint64_t last_uptime_proof; // The last time this Master Node's uptime proof was relayed by at least 1 Master Node other than itself in unix epoch time. - bool storage_server_reachable; // True if this storage server is currently passing tests for the purposes of MN node testing: true if the last test passed, or if it has been unreachable for less than an hour; false if it has been failing tests for more than an hour (and thus is considered unreachable). - uint64_t storage_server_first_unreachable; // If the last test we received was a failure, this field contains the timestamp when failures started. Will be 0 if the last result was a success or the node has not yet been tested. (To disinguish between these cases check storage_server_last_reachable). - uint64_t storage_server_last_unreachable; // The last time this service node's storage server failed a ping test (regardless of whether or not it is currently failing); 0 if it never failed a test since startup. - uint64_t storage_server_last_reachable; // The last time we received a successful ping response for this storage server (whether or not it is currently failing); 0 if we have never received a success since startup. - bool belnet_reachable; // True if this belnet is currently passing tests for the purposes of MN node testing: true if the last test passed, or if it has been unreachable for less than an hour; false if it has been failing tests for more than an hour (and thus is considered unreachable). - uint64_t belnet_first_unreachable; // If the last test we received was a failure, this field contains the timestamp when failures started. Will be 0 if the last result was a success or the node has not yet been tested. (To disinguish between these cases check belnet_last_reachable). - uint64_t belnet_last_unreachable; // The last time this service node's belnet failed a reachable test (regardless of whether or not it is currently failing); 0 if it never failed a test since startup. - uint64_t belnet_last_reachable; // The last time we received a successful test response for this service node's belnet router (whether or not it is currently failing); 0 if we have never received a success since startup. - - std::vector checkpoint_participation; // Of the last N checkpoints the Master Node is in a checkpointing quorum, record whether or not the Master Node voted to checkpoint a block - std::vector POS_participation; // Of the last N POS blocks the Master Node is in a POS quorum, record whether or not the Master Node voted (participated) in that block - std::vector timestamp_participation; // Of the last N timestamp messages, record whether or not the Master Node was in sync with the network - std::vector timesync_status; // Of the last N timestamp messages, record whether or not the Master Node responded - - KV_MAP_SERIALIZABLE - }; - - requested_fields_t fields; // @NoBeldexRPCDocGen Internal use only, not serialized - bool polling_mode; // @NoBeldexRPCDocGen Internal use only, not serialized - - std::vector master_node_states; // Array of service node registration information - uint64_t height; // Current block's height. - uint64_t target_height; // Blockchain's target height. - std::string block_hash; // Current block's hash. - bool unchanged; // Will be true (and `master_node_states` omitted) if you gave the current block hash to poll_block_hash - uint8_t hardfork; // Current hardfork version. - uint8_t mnode_revision; // mnode revision for non-hardfork but mandatory mnode updates - std::string status; // Generic RPC error code. "OK" is the success value. - std::string as_json; // If `include_json` is set in the request, this contains the json representation of the `entry` data structure - - KV_MAP_SERIALIZABLE + struct request_parameters { + /// Set of fields to return; listed fields apply to both the top level (such as \p "height" or + /// \p "block_hash") and to keys inside \p master_node_states. Fields should be provided as + /// a list of field names to include. For backwards compatibility when making a json request + /// field names can also be provided as a dictionary of {"field_name": true} pairs, but this + /// usage is deprecated (and not supported for bt-encoded requests). + /// + /// The special field name "all" can be used to request all available fields; this is the + /// default when no fields key are provided at all. Be careful when requesting all fields: + /// the response can be very large. + /// + /// When providing a list you may prefix a field name with a \c - to remove the field from the + /// list; this is mainly useful when following "all" to remove some fields from the returned + /// results. (There is no equivalent mode when using the deprecated dict value). + std::unordered_set fields; + + /// Array of public keys of registered master nodes to request information about. Omit to + /// query all master nodes. For a JSON request pubkeys must be specified in hex; for a + /// bt-encoded request pubkeys can be hex or bytes. + std::vector master_node_pubkeys; + + /// If true then only return active master nodes. + bool active_only = false; + + /// If specified and non-zero then only return a random selection of this number of master + /// nodes (in random order) from the result. If negative then no limiting is performed but + /// the returned result is still shuffled. + int limit = 0; + + /// If specified then only return results if the current top block hash is different than the + /// hash given here. This is intended to allow quick polling of results without needing to do + /// anything if the block (and thus SN registrations) have not changed since the last request. + crypto::hash poll_block_hash = crypto::hash::null(); + } request; - }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get information on the queried daemon's Master Node state. - struct GET_MASTER_NODE_STATUS : RPC_COMMAND + /// Retrieves information on the current daemon's Master Node state. The returned information is + /// the same as what would be returned by "get_master_nodes" when passed this master node's + /// public key. + /// + /// Inputs: none. + /// + /// Outputs: + /// - \p master_node_state - if this is a registered master node then all available fields for + /// this master node. \sa GET_MASTER_NODES for the list of fields. Note that some fields + /// (such as remote testing results) will not be available (through this call or \p + /// "get_master_nodes") because a master node is incapable of testing itself for remote + /// connectivity. If this daemon is running in master node mode but not registered then only + /// SN pubkey, ip, and port fields are returned. + /// - \p height current top block height at the time of the request (note that this is generally + /// one less than the "blockchain height"). + /// - \p block_hash current top block hash at the time of the request + /// - \p status generic RPC error code; "OK" means the request was successful. + struct GET_MASTER_NODE_STATUS : NO_ARGS { static constexpr auto names() { return NAMES("get_master_node_status"); } - - struct request - { - bool include_json; // When set, the response's as_json member is filled out. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - GET_MASTER_NODES::response::entry master_node_state; // Master node registration information - uint64_t height; // Current block's height. - std::string block_hash; // Current block's hash. - std::string status; // Generic RPC error code. "OK" is the success value. - std::string as_json; // If `include_json` is set in the request, this contains the json representation of the `entry` data structure - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -2367,7 +2442,7 @@ namespace rpc { }; BELDEX_RPC_DOC_INTROSPECT - // Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + // Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. struct GET_CHECKPOINTS : PUBLIC { static constexpr auto names() { return NAMES("get_checkpoints"); } @@ -2447,7 +2522,7 @@ namespace rpc { }; BELDEX_RPC_DOC_INTROSPECT - // Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + // Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. struct GET_MN_STATE_CHANGES : PUBLIC { static constexpr auto names() { return NAMES("get_master_nodes_state_changes"); } @@ -2480,7 +2555,7 @@ namespace rpc { BELDEX_RPC_DOC_INTROSPECT - // Reports service node peer status (success/fail) from belnet and storage server. + // Reports master node peer status (success/fail) from belnet and storage server. struct REPORT_PEER_STATUS : RPC_COMMAND { // TODO: remove the `report_peer_storage_server_status` once we require a storage server version @@ -2490,7 +2565,7 @@ namespace rpc { struct request { std::string type; // test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. - std::string pubkey; // service node pubkey + std::string pubkey; // master node pubkey bool passed; // whether the node is passing the test KV_MAP_SERIALIZABLE @@ -2643,7 +2718,7 @@ namespace rpc { /// is not registered. /// /// Technical details: the returned value is encrypted using the name itself so that neither this - /// oxend responding to the RPC request nor any other blockchain observers can (easily) obtain the + /// beldexd responding to the RPC request nor any other blockchain observers can (easily) obtain the /// name of registered addresses or the registration details. Thus, from a client's point of view, /// resolving an BNS record involves: /// @@ -2713,6 +2788,8 @@ namespace rpc { GET_HEIGHT, GET_INFO, BNS_RESOLVE, + GET_MASTER_NODES, + GET_MASTER_NODE_STATUS, // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN, GET_BLOCKS_BIN, @@ -2780,8 +2857,6 @@ namespace rpc { GET_MASTER_NODE_REGISTRATION_CMD, GET_MASTER_KEYS, GET_MASTER_PRIVKEYS, - GET_MASTER_NODES, - GET_MASTER_NODE_STATUS, STORAGE_SERVER_PING, BELNET_PING, GET_STAKING_REQUIREMENT, @@ -2798,4 +2873,4 @@ namespace rpc { FLUSH_CACHE >; -} } // namespace cryptonote::rpc +} // namespace cryptonote::rpc From 59313f8552902253ca1252dea5083f5b98a40fb4 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 17 Apr 2025 19:14:22 +0530 Subject: [PATCH 028/182] Simplify participation structures - make checkpoint and pulse participation distinct types - make the container act a little more like an stl container - replace check_participation() with a failures() method that just returns the number of failures (so the caller can decide whether that is bad or not). - As a consequence of the above, failures now trigger on N+1 failures, while previously they required N+1 failures *and* 8 total responses. There is no need to wait for all 8 as far as I can see since we will be failing no matter what the next 3 responses are. - Removed the serialization code (hopefully this doesn't break everything outside the RPC code). - Simplify/DRY the code that records participation --- src/cryptonote_core/cryptonote_core.cpp | 4 +- src/cryptonote_core/master_node_list.cpp | 44 ++------- src/cryptonote_core/master_node_list.h | 95 +++++++------------ .../master_node_quorum_cop.cpp | 18 ++-- 4 files changed, 50 insertions(+), 111 deletions(-) diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 372dbcccef6..1b1f0f9fdb9 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1688,9 +1688,7 @@ namespace cryptonote m_mn_times.add(entry); // Counts the number of times we have been out of sync - uint8_t num_mn_out_of_sync = std::count_if(m_mn_times.begin(), m_mn_times.end(), - [](const master_nodes::timesync_entry entry) { return !entry.in_sync; }); - if (num_mn_out_of_sync > (m_mn_times.array.size() * master_nodes::MAXIMUM_EXTERNAL_OUT_OF_SYNC/100)) { + if (m_mn_times.failures() > (m_mn_times.size() * master_nodes::MAXIMUM_EXTERNAL_OUT_OF_SYNC/100)) { MWARNING("master node time might be out of sync"); // If we are out of sync record the other master node as in sync m_master_node_list.record_timesync_status(pubkey, true); diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index c226c1aa788..ad09702f7a2 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -3188,57 +3188,29 @@ namespace master_nodes void master_node_list::record_checkpoint_participation(crypto::public_key const &pubkey, uint64_t height, bool participated) { std::lock_guard lock(m_mn_mutex); - if (!m_state.master_nodes_infos.count(pubkey)) - return; - - participation_entry entry = {}; - entry.height = height; - entry.voted = participated; - - auto &info = proofs[pubkey]; - info.checkpoint_participation.add(entry); + if (m_state.master_nodes_infos.count(pubkey)) + proofs[pubkey].checkpoint_participation.add({height, participated}); } void master_node_list::record_POS_participation(crypto::public_key const &pubkey, uint64_t height, uint8_t round, bool participated) { std::lock_guard lock(m_mn_mutex); - if (!m_state.master_nodes_infos.count(pubkey)) - return; - - participation_entry entry = {}; - entry.is_POS = true; - entry.height = height; - entry.voted = participated; - entry.POS.round = round; - - auto &info = proofs[pubkey]; - info.POS_participation.add(entry); + if (m_state.master_nodes_infos.count(pubkey)) + proofs[pubkey].POS_participation.add({height, round, participated}); } void master_node_list::record_timestamp_participation(crypto::public_key const &pubkey, bool participated) { std::lock_guard lock(m_mn_mutex); - if (!m_state.master_nodes_infos.count(pubkey)) - return; - - timestamp_participation_entry entry = {}; - entry.participated = participated; - - auto &info = proofs[pubkey]; - info.timestamp_participation.add(entry); + if (m_state.master_nodes_infos.count(pubkey)) + proofs[pubkey].timestamp_participation.add({participated}); } void master_node_list::record_timesync_status(crypto::public_key const &pubkey, bool synced) { std::lock_guard lock(m_mn_mutex); - if (!m_state.master_nodes_infos.count(pubkey)) - return; - - timesync_entry entry = {}; - entry.in_sync = synced; - - auto &info = proofs[pubkey]; - info.timesync_status.add(entry); + if (m_state.master_nodes_infos.count(pubkey)) + proofs[pubkey].timesync_status.add({synced}); } std::optional proof_info::reachable_stats::reachable(const std::chrono::steady_clock::time_point& now) const { diff --git a/src/cryptonote_core/master_node_list.h b/src/cryptonote_core/master_node_list.h index fee2b7abc06..26a642acc0a 100755 --- a/src/cryptonote_core/master_node_list.h +++ b/src/cryptonote_core/master_node_list.h @@ -56,89 +56,60 @@ namespace master_nodes { constexpr uint64_t INVALID_HEIGHT = static_cast(-1); - BELDEX_RPC_DOC_INTROSPECT - struct participation_entry + struct checkpoint_participation_entry { - bool is_POS = false; uint64_t height = INVALID_HEIGHT; bool voted = true; - struct - { - uint8_t round = 0; - } POS; + bool pass() const { return voted; }; + }; - bool pass() const { - return voted; - }; + struct POS_participation_entry + { + uint64_t height = INVALID_HEIGHT; + uint8_t round = 0; + bool voted = true; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(height); - KV_SERIALIZE(voted); - KV_SERIALIZE(is_POS); - if (this_ref.is_POS) - { - KV_SERIALIZE_N(POS.round, "POS_round"); - } - END_KV_SERIALIZE_MAP() + bool pass() const { return voted; } }; - struct timestamp_participation_entry { - bool participated = true; - bool pass() const { - return participated; - }; + bool participated = true; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(participated); - END_KV_SERIALIZE_MAP() + bool pass() const { return participated; }; }; struct timesync_entry { - bool in_sync = true; - bool pass() const { - return in_sync; - }; + bool in_sync = true; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(in_sync); - END_KV_SERIALIZE_MAP() + bool pass() const { return in_sync; } }; template struct participation_history { - std::array array; - size_t write_index; + std::array history; + size_t write_index = 0; void reset() { write_index = 0; } - void add(ValueType &entry) - { - size_t real_write_index = write_index % array.size(); - array[real_write_index] = entry; - write_index++; - } + void add(const ValueType& entry) { history[write_index++ % history.size()] = entry; } + void add(ValueType&& entry) { history[write_index++ % history.size()] = std::move(entry); } - bool check_participation(uint16_t threshold) - { - if (this->write_index >= Count) - { - int failed_counter = 0; - for (ValueType &entry : array) - if (!entry.pass()) failed_counter++; - - if (failed_counter > threshold) - return false; - } - return true; + // Returns the number of failures we have stored (of the last Count records). + size_t failures() const { + return std::count_if(begin(), end(), [](auto& e) { return !e.pass(); }); } + size_t passes() const { return size() - failures(); } + + bool empty() const { return write_index == 0; } + size_t size() const { return std::min(history.size(), write_index); } + constexpr size_t max_size() const noexcept { return Count; } - ValueType *begin() { return array.data(); } - ValueType *end() { return array.data() + std::min(array.size(), write_index); } - ValueType const *begin() const { return array.data(); } - ValueType const *end() const { return array.data() + std::min(array.size(), write_index); } + ValueType* begin() { return history.data(); } + ValueType* end() { return history.data() + size(); } + const ValueType* begin() const { return history.data(); } + const ValueType* end() const { return history.data() + size(); } }; inline constexpr auto NEVER = std::chrono::steady_clock::time_point::min(); @@ -147,10 +118,10 @@ namespace master_nodes { proof_info(); - participation_history POS_participation{}; - participation_history checkpoint_participation{}; - participation_history timestamp_participation{}; - participation_history timesync_status{}; + participation_history POS_participation; + participation_history checkpoint_participation; + participation_history timestamp_participation; + participation_history timesync_status; uint64_t timestamp = 0; // The actual time we last received an uptime proof (serialized) uint64_t effective_timestamp = 0; // Typically the same, but on recommissions it is set to the recommission block time to fend off instant obligation checks diff --git a/src/cryptonote_core/master_node_quorum_cop.cpp b/src/cryptonote_core/master_node_quorum_cop.cpp index f7b2cb1e0be..a22cdfec869 100755 --- a/src/cryptonote_core/master_node_quorum_cop.cpp +++ b/src/cryptonote_core/master_node_quorum_cop.cpp @@ -87,8 +87,8 @@ namespace master_nodes uint64_t timestamp = 0; decltype(std::declval().public_ips) ips{}; - master_nodes::participation_history checkpoint_participation{}; - master_nodes::participation_history POS_participation{}; + master_nodes::participation_history checkpoint_participation{}; + master_nodes::participation_history POS_participation{}; master_nodes::participation_history timestamp_participation{}; master_nodes::participation_history timesync_status{}; @@ -98,12 +98,11 @@ namespace master_nodes m_core.get_master_node_list().access_proof(pubkey, [&](const proof_info &proof) { ss_reachable = !proof.ss_reachable.unreachable_for(unreachable_threshold); - belnet_reachable = !proof.belnet_reachable.unreachable_for(unreachable_threshold); + belnet_reachable = !proof.belnet_reachable.unreachable_for(unreachable_threshold); timestamp = std::max(proof.timestamp, proof.effective_timestamp); ips = proof.public_ips; checkpoint_participation = proof.checkpoint_participation; - POS_participation = proof.POS_participation; - + POS_participation = proof.POS_participation; timestamp_participation = proof.timestamp_participation; timesync_status = proof.timesync_status; @@ -186,22 +185,21 @@ namespace master_nodes if (!info.is_decommissioned()) { - if (check_checkpoint_obligation && - !checkpoint_participation.check_participation(CHECKPOINT_MAX_MISSABLE_VOTES)) { + if (check_checkpoint_obligation && checkpoint_participation.failures() > CHECKPOINT_MAX_MISSABLE_VOTES) { LOG_PRINT_L1("Master Node: " << pubkey << ", failed checkpoint obligation check"); result.checkpoint_participation = false; } - if (!POS_participation.check_participation(POS_MAX_MISSABLE_VOTES)) { + if (POS_participation.failures() > POS_MAX_MISSABLE_VOTES) { LOG_PRINT_L1("Master Node: " << pubkey << ", failed pulse obligation check"); result.POS_participation = false; } - if (!timestamp_participation.check_participation(TIMESTAMP_MAX_MISSABLE_VOTES)) { + if (timestamp_participation.failures() > TIMESTAMP_MAX_MISSABLE_VOTES) { LOG_PRINT_L1("Master Node: " << pubkey << ", failed timestamp obligation check"); result.timestamp_participation = false; } - if (!timesync_status.check_participation(TIMESYNC_MAX_UNSYNCED_VOTES)) { + if (timesync_status.failures() > TIMESYNC_MAX_UNSYNCED_VOTES) { LOG_PRINT_L1("Master Node: " << pubkey << ", failed timesync obligation check"); result.timesync_status = false; } From 1b68c218dca74bb5fd1e685c1b0b7cea7abccf18 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 14:46:58 +0530 Subject: [PATCH 029/182] Logs update to fmt format --- src/daemon/rpc_command_executor.cpp | 79 ++++++++++------------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 07f04586344..9448e0fef19 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -43,7 +43,7 @@ #include "cryptonote_core/master_node_rules.h" #include "cryptonote_basic/hardfork.h" #include "checkpoints/checkpoints.h" -#include +#include #include #include @@ -129,19 +129,6 @@ namespace { return input_line_result::yes; } - const char *get_address_type_name(epee::net_utils::address_type address_type) - { - switch (address_type) - { - default: - case epee::net_utils::address_type::invalid: return "invalid"; - case epee::net_utils::address_type::ipv4: return "IPv4"; - case epee::net_utils::address_type::ipv6: return "IPv6"; - case epee::net_utils::address_type::i2p: return "I2P"; - case epee::net_utils::address_type::tor: return "Tor"; - } - } - void print_peer(std::string const & prefix, GET_PEER_LIST::peer const & peer, bool pruned_only, bool publicrpc_only) { if (pruned_only && peer.pruning_seed == 0) @@ -491,9 +478,9 @@ static std::string get_mining_speed(uint64_t hr) return fmt::format("{:d} H/s",hr); } -static std::ostream& print_fork_extra_info(std::ostream& o, uint64_t t, uint64_t now, uint64_t block_time) +static std::ostream& print_fork_extra_info(std::ostream& o, uint64_t t, uint64_t now, std::chrono::seconds block_time) { - uint64_t blocks_per_day = 86400 / block_time; + double blocks_per_day = 24h / block_time; if (t == now) return o << " (forking now)"; @@ -518,10 +505,6 @@ static float get_sync_percentage(uint64_t height, uint64_t target_height) return 99.9f; // to avoid 100% when not fully synced return pc; } -static float get_sync_percentage(const GET_INFO::response &ires) -{ - return get_sync_percentage(ires.height, ires.target_height); -} bool rpc_command_executor::show_status() { auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); @@ -723,42 +706,34 @@ bool rpc_command_executor::print_connections() { if (!invoke({}, res, "Failed to retrieve connection info")) return false; - tools::msg_writer() << std::setw(30) << std::left << "Remote Host" - << std::setw(8) << "Type" - << std::setw(20) << "Peer id" - << std::setw(20) << "Support Flags" - << std::setw(30) << "Recv/Sent (inactive,sec)" - << std::setw(25) << "State" - << std::setw(20) << "Livetime(sec)" - << std::setw(12) << "Down (kB/s)" - << std::setw(14) << "Down(now)" - << std::setw(10) << "Up (kB/s)" - << std::setw(13) << "Up(now)" - << std::endl; + constexpr auto hdr_fmt = "{:<30}{:<8}{:<20}{:<30}{:<25}{:<20}{:<12s}{:<14s}{:<10s}{:<13s}"sv; + constexpr auto row_fmt = "{:<30}{:<8}{:<20}{:<30}{:<25}{:<20}{:<12.1f}{:<14.1f}{:<10.1f}{:<13.1f}{}{}"sv; + tools::msg_writer() << fmt::format(hdr_fmt, + "Remote Host", "Type", "Peer id", "Recv/Sent (inactive,sec)", "State", "Livetime(sec)", + "Down (kB/sec)", "Down(now)", "Up (kB/s)", "Up(now)"); + for (auto & info : res.connections) { std::string address = info.incoming ? "INC " : "OUT "; - address += info.ip + ":" + info.port; - //std::string in_out = info.incoming ? "INC " : "OUT "; - tools::msg_writer() - //<< std::setw(30) << std::left << in_out - << std::setw(30) << std::left << address - << std::setw(8) << (get_address_type_name((epee::net_utils::address_type)info.address_type)) - << std::setw(20) << info.peer_id - << std::setw(20) << info.support_flags - << std::setw(30) << std::to_string(info.recv_count) + "(" + std::to_string(tools::to_seconds(info.recv_idle_time)) + ")/" + std::to_string(info.send_count) + "(" + std::to_string(tools::to_seconds(info.send_idle_time)) + ")" - << std::setw(25) << info.state - << std::setw(20) << std::to_string(tools::to_seconds(info.live_time)) - << std::setw(12) << info.avg_download - << std::setw(14) << info.current_download - << std::setw(10) << info.avg_upload - << std::setw(13) << info.current_upload - - << std::left << (info.localhost ? "[LOCALHOST]" : "") - << std::left << (info.local_ip ? "[LAN]" : ""); - //tools::msg_writer() << boost::format("%-25s peer_id: %-25s %s") % address % info.peer_id % in_out; - + address += info.ip; + address += ':'; + address += info.port; + tools::msg_writer() << fmt::format(row_fmt, + address, + info.address_type, + info.peer_id, + fmt::format("{}({}/{})", info.recv_count, + tools::friendly_duration(info.recv_idle_time), + tools::friendly_duration(info.send_idle_time)), + info.state, + tools::friendly_duration(info.live_time), + info.avg_upload / 1000., + info.current_download / 1000., + info.avg_upload / 1000., + info.current_upload / 1000., + info.localhost ? "[LOCALHOST]" : "", + info.local_ip ? "[LAN]" : ""); } return true; From 255dcbd289b3f46aa2c4833a69162f3a441a741c Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 18 Apr 2025 14:51:00 +0530 Subject: [PATCH 030/182] Update doc formatting style --- src/rpc/core_rpc_server_commands_defs.h | 115 ++++++++++++------------ 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 998cb5672fb..ca027c7885a 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -688,74 +688,71 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// \arg \c status General RPC status string. `"OK"` means everything looks good. - /// \arg \c height Current length of longest chain known to daemon. - /// \arg \c target_height The height of the next block in the chain. - /// \arg \c immutable_height The latest height in the blockchain that can not be reorganized (i.e. + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p height Current length of longest chain known to daemon. + /// - \p target_height The height of the next block in the chain. + /// - \p immutable_height The latest height in the blockchain that can not be reorganized (i.e. /// is backed by at least 2 Master Node, or 1 hardcoded checkpoint, 0 if N/A). Omitted if it /// cannot be determined (typically because the node is still syncing). - /// \arg \c POS will be true if the next expected block is a POS block, false otherwise. - /// \arg \c POS_ideal_timestamp For POS blocks this is the ideal timestamp of the next block, + /// - \p POS will be true if the next expected block is a POS block, false otherwise. + /// - \p POS_ideal_timestamp For POS blocks this is the ideal timestamp of the next block, /// that is, the timestamp if the network was operating with perfect 2-minute blocks since the /// POS hard fork. - /// \arg \c POS_target_timestamp For POS blocks this is the target timestamp of the next - /// block, which targets 2 minutes after the previous block but will be slightly faster/slower - /// if the previous block is behind/ahead of the ideal timestamp. - /// \arg \c difficulty Network mining difficulty; omitted when the network is expecting a POS + /// - \p POS_target_timestamp For POS blocks this is the target timestamp of the next block, + /// which targets 2 minutes after the previous block but will be slightly faster/slower if the + /// previous block is behind/ahead of the ideal timestamp. + /// - \p difficulty Network mining difficulty; omitted when the network is expecting a POS /// block. - /// \arg \c target Current target for next proof of work. - /// \arg \c tx_count Total number of non-coinbase transaction in the chain. - /// \arg \c tx_pool_size Number of transactions that have been broadcast but not included in a - /// block. - /// \arg \c mainnet Indicates whether the node is on the main network (`true`) or not (`false`). - /// \arg \c testnet Indicates that the node is on the test network (`true`). Will be omitted for - /// non-testnet. - /// \arg \c devnet Indicates that the node is on the dev network (`true`). Will be omitted for - /// non-devnet. - /// \arg \c fakechain States that the node is running in "fakechain" mode (`true`). Omitted - /// otherwise. - /// \arg \c nettype String value of the network type (mainnet, testnet, devnet, or fakechain). - /// \arg \c top_block_hash Hash of the highest block in the chain. Will be hex for JSON requests, - /// 32-byte binary value for bt requests. - /// \arg \c immutable_block_hash Hash of the highest block in the chain that can not be - /// reorganized. Hex string for json, bytes for bt. - /// \arg \c cumulative_difficulty Cumulative difficulty of all blocks in the blockchain. - /// \arg \c block_size_limit Maximum allowed block size. - /// \arg \c block_size_median Median block size of latest 100 blocks. - /// \arg \c bns_counts BNS registration counts. - /// \arg \c offline Indicates that the node is offline, if true. Omitted for online nodes. - /// \arg \c untrusted Indicates that the result was obtained using a bootstrap mode, and is therefore + /// - \p target Current target for next proof of work. + /// - \p tx_count Total number of non-coinbase transaction in the chain. + /// - \p tx_pool_size Number of transactions that have been broadcast but not included in a block. + /// - \p mainnet Indicates whether the node is on the main network (`true`) or not (`false`). + /// - \p testnet Indicates that the node is on the test network (`true`). Will be omitted for + /// non-testnet. + /// - \p devnet Indicates that the node is on the dev network (`true`). Will be omitted for + /// non-devnet. + /// - \p fakechain States that the node is running in "fakechain" mode (`true`). Omitted + /// otherwise. + /// - \p nettype String value of the network type (mainnet, testnet, devnet, or fakechain). + /// - \p top_block_hash Hash of the highest block in the chain. Will be hex for JSON requests, + /// 32-byte binary value for bt requests. + /// - \p immutable_block_hash Hash of the highest block in the chain that can not be reorganized. + /// Hex string for json, bytes for bt. + /// - \p cumulative_difficulty Cumulative difficulty of all blocks in the blockchain. + /// - \p block_size_limit Maximum allowed block size. + /// - \p block_size_median Median block size of latest 100 blocks. + /// - \p bns_counts BNS registration counts. + /// - \p offline Indicates that the node is offline, if true. Omitted for online nodes. + /// - \p untrusted Indicates that the result was obtained using a bootstrap mode, and is therefore /// not trusted (`true`). Omitted for non-bootstrap responses. - /// \arg \c database_size Current size of Blockchain data. Over public RPC this is rounded up to - /// the next-largest GB value. - /// \arg \c version Current version of this daemon, as a string. For a public node this will just - /// be the major and minor version (e.g. "9"); for an admin rpc endpoint this will return the - /// full version (e.g. "9.2.1"). - /// \arg \c status_line A short one-line summary string of the node (requires an - /// admin/unrestricted connection for most details) + /// - \p database_size Current size of Blockchain data. Over public RPC this is rounded up to the + /// next-largest GB value. + /// - \p version Current version of this daemon, as a string. For a public node this will just be + /// the major and minor version (e.g. "9"); for an admin rpc endpoint this will return the full + /// version (e.g. "9.2.1"). + /// - \p status_line A short one-line summary string of the node (requires an admin/unrestricted + /// connection for most details) /// /// If the endpoint is a restricted (i.e. admin) endpoint then the following fields are also /// included: /// - /// \arg \c alt_blocks_count Number of alternative blocks to main chain. - /// \arg \c outgoing_connections_count Number of peers that you are connected to and getting + /// - \p alt_blocks_count Number of alternative blocks to main chain. + /// - \p outgoing_connections_count Number of peers that you are connected to and getting /// information from. - /// \arg \c incoming_connections_count Number of peers connected to and pulling from your node. - /// \arg \c white_peerlist_size White Peerlist Size - /// \arg \c grey_peerlist_size Grey Peerlist Size - /// \arg \c master_node Will be true if the node is running in --master-node mode. - /// \arg \c start_time Start time of the daemon, as UNIX time. - /// \arg \c last_storage_server_ping Last ping time of the storage server (0 if never or not - /// running as a master node) - /// \arg \c last_belnet_ping Last ping time of belnet (0 if never or not running as a master - /// node) - /// \arg \c free_space Available disk space on the node. - /// \arg \c bootstrap_daemon_address Bootstrap node to give immediate usability to wallets while + /// - \p incoming_connections_count Number of peers connected to and pulling from your node. + /// - \p white_peerlist_size White Peerlist Size + /// - \p grey_peerlist_size Grey Peerlist Size + /// - \p master_node Will be true if the node is running in --master-node mode. + /// - \p start_time Start time of the daemon, as UNIX time. + /// - \p last_storage_server_ping Last ping time of the storage server (0 if never or not running + /// as a master node) + /// - \p last_belnet_ping Last ping time of belnet (0 if never or not running as a master node) + /// - \p free_space Available disk space on the node. + /// - \p bootstrap_daemon_address Bootstrap node to give immediate usability to wallets while /// syncing by proxying RPC to it. (Note: the replies may be untrustworthy). - /// \arg \c height_without_bootstrap Current length of the local chain of the daemon. Only - /// included if a bootstrap daemon is configured. - /// \arg \c was_bootstrap_ever_used States if the bootstrap node has ever been used since the daemon - /// started. Omitted if no bootstrap node is configured. + /// - \p height_without_bootstrap Current length of the local chain of the daemon. Only included + /// if a bootstrap daemon is configured. + /// - \p was_bootstrap_ever_used States if the bootstrap node has ever been used since the daemon struct GET_INFO : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_info", "getinfo"); } @@ -2709,10 +2706,10 @@ namespace cryptonote::rpc { /// /// Returned values: /// - /// \arg \c encrypted_value The encrypted BNS value, in hex. Will be omitted from the response if + /// - \p encrypted_value The encrypted BNS value, in hex. Will be omitted from the response if /// the given name_hash is not registered. - /// \arg \c nonce The nonce value used for encryption, in hex. Will be omitted if the given name - /// is not registered. + /// - \p nonce The nonce value used for encryption, in hex. Will be omitted if the given name is + /// not registered. /// /// Technical details: the returned value is encrypted using the name itself so that neither this /// beldexd responding to the RPC request nor any other blockchain observers can (easily) obtain the From e4caae9c6a36aee4c465550c9901694a80902ec1 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 18 Apr 2025 14:57:32 +0530 Subject: [PATCH 031/182] parse params of get_outputs and define parsers and return correct types --- src/rpc/core_rpc_server.cpp | 44 ++++--- src/rpc/core_rpc_server_command_parser.cpp | 23 ++++ src/rpc/core_rpc_server_command_parser.h | 11 +- src/rpc/core_rpc_server_commands_defs.h | 130 ++++++++++++--------- src/wallet/node_rpc_proxy.h | 14 +-- src/wallet/wallet2.h | 7 +- 6 files changed, 146 insertions(+), 83 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f27ea645afa..7d3da63065c 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -461,14 +461,20 @@ namespace cryptonote::rpc { { PERF_TIMER(on_get_net_stats); // No bootstrap daemon check: Only ever get stats about local server - get_net_stats.response["start_time"] = (uint64_t)m_core.get_start_time(); + get_net_stats.response["start_time"] = m_core.get_start_time(); { std::lock_guard lock{epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in}; - epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(get_net_stats.response["total_packets_in"], get_net_stats.response["total_bytes_in"]); + uint64_t total_packets_in, total_bytes_in; + epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(total_packets_in, total_bytes_in); + get_net_stats.response["total_packets_in"] = total_packets_in; + get_net_stats.response["total_bytes_in"] = total_bytes_in; } { std::lock_guard lock{epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_out}; - epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(get_net_stats.response["total_packets_out"], get_net_stats.response["total_bytes_out"]); + uint64_t total_packets_out, total_bytes_out; + epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(total_packets_out, total_bytes_out); + get_net_stats.response["total_packets_out"] = total_packets_out; + get_net_stats.response["total_bytes_out"] = total_bytes_out; } get_net_stats.response["status"] = STATUS_OK; } @@ -648,7 +654,7 @@ namespace cryptonote::rpc { if (!context.admin && get_outputs.request["outputs"].size() > GET_OUTPUTS::MAX_COUNT) { get_outputs.response["status"] = "Too many outs requested"; - return res; + return; } GET_OUTPUTS_BIN::request req_bin{}; @@ -661,13 +667,12 @@ namespace cryptonote::rpc { return; } - get_output.response["outs"] = std::vector{}; + get_outputs.response["outs"] = std::vector{}; // convert to text for (const auto &i: res_bin.outs) { - get_output.response["outs"].emplace_back(); - auto& outkey = get_output.response["outs"].back(); + auto& outkey = get_outputs.response["outs"].emplace_back(); outkey.key = tools::type_to_hex(i.key); outkey.mask = tools::type_to_hex(i.mask); outkey.unlocked = i.unlocked; @@ -676,7 +681,7 @@ namespace cryptonote::rpc { } get_outputs.response["status"] = STATUS_OK; - return res; + return; } //------------------------------------------------------------------------------------------------------------------------------ GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response core_rpc_server::invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context) @@ -1220,7 +1225,13 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(START_MINING& start_mining, rpc_context context) { PERF_TIMER(on_start_mining); - CHECK_CORE_READY(); + + if(!check_core_ready()){ + start_mining.response["status"] = STATUS_BUSY; + LOG_PRINT_L0(start_mining.response["status"]); + return; + } + cryptonote::address_parse_info info; if(!get_account_address_from_str(info, m_core.get_nettype(), start_mining.request['miner_address'])) { @@ -1510,11 +1521,9 @@ namespace cryptonote::rpc { std::vector tx_hashes; m_core.get_pool().get_transaction_hashes(tx_hashes, context.admin); - get_transaction_pool_hashes.response["tx_hashes"].reserve(tx_hashes.size()); - for (const crypto::hash &tx_hash: tx_hashes) - get_transaction_pool_hashes.response["tx_hashes"].push_back(tools::type_to_hex(tx_hash)); + get_transaction_pool_hashes.response_hex["tx_hashes"] = tx_hashes; get_transaction_pool_hashes.response["status"] = STATUS_OK; - LOG_PRINT_L0(get_transaction_pool_hashes.response["status"]); + LOG_PRINT_L0(get_transaction_pool_hashes.response["status"].get()); return; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2085,7 +2094,8 @@ namespace cryptonote::rpc { { PERF_TIMER(on_get_connections); - get_connections.response["connections"] = m_p2p.get_payload_object().get_connections(); + auto connections = m_p2p.get_payload_object().get_connections(); + get_connections.response["connections"] = connections; get_connections.response["status"] = STATUS_OK; LOG_PRINT_L0(get_connections.response["status"]); return; @@ -2564,9 +2574,9 @@ namespace cryptonote::rpc { std::vector backlog; m_core.get_pool().get_transaction_backlog(backlog); - get_transaction_pool_backlog.response["backlog"] = backlog; - get_transaction_backlog.response["status"] = STATUS_OK; - LOG_PRINT_L0(get_transaction_backlog.response["status"]); + get_transaction_pool_backlog.response["backlog"] = json::parse(backlog.begin(), backlog.end()); + get_transaction_pool_backlog.response["status"] = STATUS_OK; + LOG_PRINT_L0(get_transaction_pool_backlog.response["status"]); return; } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index da551cacaff..3e226f96c64 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -222,4 +222,27 @@ namespace cryptonote::rpc { "master_node_pubkeys", mns.request.master_node_pubkeys); } + void parse_request(START_MINING& start_mining, rpc_input in) { + } + void parse_request(STOP_MINING& stop_mining, rpc_input in) { + } + void parse_request(MINING_STATUS& mining_status, rpc_input in) { + } + void parse_request(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_input in) { + } + void parse_request(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_input in) { + } + void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in) { + } + void parse_request(GETBLOCKCOUNT& getblockcount, rpc_input in) { + } + void parse_request(STOP_DAEMON& stop_daemon, rpc_input in) { + } + void parse_request(SAVE_BC& save_bc, rpc_input in) { + } + void parse_request(GET_OUTPUTS& get_outputs, rpc_input in) { + get_values(in, "get_txid", get_outputs.request.get_txid); + get_values(in, "outputs", get_outputs.request.outputs) + } + } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 0dbbaa1412e..24a036ae76b 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -12,5 +12,14 @@ namespace cryptonote::rpc { void parse_request(BNS_RESOLVE& bns, rpc_input in); void parse_request(GET_MASTER_NODES& mns, rpc_input in); - + void parse_request(START_MINING& start_mining, rpc_input in); + void parse_request(STOP_MINING& stop_mining, rpc_input in); + void parse_request(MINING_STATUS& mining_status, rpc_input in); + void parse_request(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_input in); + void parse_request(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_input in); + void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in); + void parse_request(GETBLOCKCOUNT& getblockcount, rpc_input in); + void parse_request(STOP_DAEMON& stop_daemon, rpc_input in); + void parse_request(SAVE_BC& save_bc, rpc_input in); + void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ca027c7885a..125a6a849b5 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -209,12 +209,12 @@ namespace cryptonote::rpc { /// /// Outputs: /// - /// height -- The current blockchain height according to the queried daemon. - /// status -- Generic RPC error code. "OK" is the success value. - /// untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. - /// hash -- Hash of the block at the current height - /// immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 SN checkpoints. Omitted if not available. - /// immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. + /// - /p height -- The current blockchain height according to the queried daemon. + /// - /p status -- Generic RPC error code. "OK" is the success value. + /// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// - /p hash -- Hash of the block at the current height + /// - /p immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 SN checkpoints. Omitted if not available. + /// - /p immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. struct GET_HEIGHT : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_height", "getheight"); } @@ -564,22 +564,22 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// \p outputs Array of structure `get_outputs_out`. - /// \p get_txid Request the TXID/hash of the transaction as well. + /// - \p outputs Array of structure `get_outputs_out`. + /// - \p get_txid Request the TXID/hash of the transaction as well. /// /// Output values available from a public RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore untrusted ('true'), or when the daemon is fully synced ('false'). - /// \p outs List of outkey information. + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore untrusted ('true'), or when the daemon is fully synced ('false'). + /// - \p outs List of outkey information. /// /// Outkey Information: /// - /// \p key The public key of the output. - /// \p mask something - /// \p unlocked States if output is locked (`false`) or not (`true`). - /// \p height Block height of the output. - /// \p txid Transaction id. + /// - \p key The public key of the output. + /// - \p mask something + /// - \p unlocked States if output is locked (`false`) or not (`true`). + /// - \p height Block height of the output. + /// - \p txid Transaction id. struct GET_OUTPUTS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_outs"); } @@ -597,6 +597,12 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; + + struct request_parameters + { + bool get_txid; // If true the request will return the TXID/hash of the transaction as well. + std::vector> outputs; // Array of (Amount, Index) where amount is the amount of Beldex and index is the output index + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -633,19 +639,17 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// \p miner_address Account address to mine to. - /// \p threads_count Number of mining threads to run. - /// \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). - /// \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily inteded for testing. + /// - \p miner_address Account address to mine to. + /// - \p threads_count Number of mining threads to run. + /// - \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). + /// - \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily inteded for testing. /// /// Output values available from a restricted/admin RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. + /// - \p status General RPC status string. `"OK"` means everything looks good. struct START_MINING : LEGACY { static constexpr auto names() { return NAMES("start_mining"); } - - struct response : STATUS {}; }; //----------------------------------------------- @@ -655,7 +659,7 @@ namespace cryptonote::rpc { /// /// Output values available from a restricted/admin RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. + /// - \p status General RPC status string. `"OK"` means everything looks good. struct STOP_MINING : LEGACY { static constexpr auto names() { return NAMES("stop_mining"); } @@ -668,15 +672,15 @@ namespace cryptonote::rpc { /// /// Output values available from a restricted/admin RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p active States if mining is enabled (`true`) or disabled (`false`). - /// \p speed Mining power in hashes per seconds. - /// \p threads_count Number of running mining threads. - /// \p address Account address daemon is mining to. Empty if not mining. - /// \p pow_algorithm Current hashing algorithm name - /// \p block_target The expected time to solve per block, i.e. TARGET_BLOCK_TIME - /// \p block_reward Block reward for the current block being mined. - /// \p difficulty The difficulty for the current block being mined. + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p active States if mining is enabled (`true`) or disabled (`false`). + /// - \p speed Mining power in hashes per seconds. + /// - \p threads_count Number of running mining threads. + /// - \p address Account address daemon is mining to. Empty if not mining. + /// - \p pow_algorithm Current hashing algorithm name + /// - \p block_target The expected time to solve per block, i.e. TARGET_BLOCK_TIME + /// - \p block_reward Block reward for the current block being mined. + /// - \p difficulty The difficulty for the current block being mined. struct MINING_STATUS : LEGACY { static constexpr auto names() { return NAMES("mining_status"); } @@ -765,12 +769,12 @@ namespace cryptonote::rpc { /// /// Output values available from a restricted/admin RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p start_time something. - /// \p total_packets_in something. - /// \p total_bytes_in something. - /// \p total_packets_out something. - /// \p total_bytes_out something. + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p start_time something. + /// - \p total_packets_in something. + /// - \p total_bytes_in something. + /// - \p total_packets_out something. + /// - \p total_bytes_out something. struct GET_NET_STATS : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_net_stats"); } @@ -786,7 +790,7 @@ namespace cryptonote::rpc { /// /// Output values available from a restricted/admin RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. + /// - \p status General RPC status string. `"OK"` means everything looks good. struct SAVE_BC : LEGACY { static constexpr auto names() { return NAMES("save_bc"); } @@ -799,8 +803,8 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p count Number of blocks in logest chain seen by the node. + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p count Number of blocks in logest chain seen by the node. struct GETBLOCKCOUNT : PUBLIC { static constexpr auto names() { return NAMES("get_block_count", "getblockcount"); } @@ -1279,9 +1283,9 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p tx_hashes List of transaction hashes, - /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p tx_hashes List of transaction hashes, + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } @@ -1302,9 +1306,9 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p backlog Array of structures tx_backlog_entry (in binary form): - /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p backlog Array of structures tx_backlog_entry (in binary form): + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_BACKLOG : PUBLIC { static constexpr auto names() { return NAMES("get_txpool_backlog"); } @@ -1341,6 +1345,22 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; + // void to_json(nlohmann::json& j, const txpool_stats& txpool) { + // j = nlohmann::json{{"bytes_total", txpool.bytes_total}, + // {"bytes_min", txpool.bytes_min}, + // {"bytes_max", txpool.bytes_max}, + // {"bytes_med", txpool.bytes_med}, + // {"fee_total", txpool.fee_total}, + // {"oldest", txpool.oldest}, + // {"txs_total", txpool.txs_total}, + // {"num_failing", txpool.num_failing}, + // {"num_10m", txpool.num_10m}, + // {"num_not_relayed", txpool.num_not_relayed}, + // {"histo_98pc", txpool.histo_98pc}, + // {"histo", txpool.histo}, + // {"num_double_spends", txpool.num_double_spends}}; + // } + //----------------------------------------------- /// Get the transaction pool statistics. /// @@ -1348,9 +1368,9 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p pool_stats List of pool stats. - /// \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p pool_stats List of pool stats. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } @@ -1363,8 +1383,8 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. - /// \p connections List of all connections and their info: + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p connections List of all connections and their info: struct GET_CONNECTIONS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_connections"); } @@ -1423,7 +1443,7 @@ namespace cryptonote::rpc { /// /// Output values available from a restricted/admin RPC endpoint: /// - /// \p status General RPC status string. `"OK"` means everything looks good. + /// - \p status General RPC status string. `"OK"` means everything looks good. struct STOP_DAEMON : LEGACY { static constexpr auto names() { return NAMES("stop_daemon"); } @@ -2278,7 +2298,7 @@ namespace cryptonote::rpc { struct request_parameters { /// Set of fields to return; listed fields apply to both the top level (such as \p "height" or - /// \p "block_hash") and to keys inside \p master_node_states. Fields should be provided as + /// - \p "block_hash") and to keys inside \p master_node_states. Fields should be provided as /// a list of field names to include. For backwards compatibility when making a json request /// field names can also be provided as a dictionary of {"field_name": true} pairs, but this /// usage is deprecated (and not supported for bt-encoded requests). diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 80079360861..08eddd32c0e 100755 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -56,14 +56,14 @@ class NodeRPCProxy bool get_fee_quantization_mask(uint64_t &fee_quantization_mask) const; std::optional get_hardfork_version() const; - std::pair> get_master_nodes(std::vector pubkeys) const; - std::pair> get_all_master_nodes() const; - std::pair> get_contributed_master_nodes(const std::string& contributor) const; + std::pair get_master_nodes(std::vector pubkeys) const; + std::pair get_all_master_nodes() const; + std::pair get_contributed_master_nodes(const std::string& contributor) const; std::pair> get_master_node_blacklisted_key_images() const; std::pair> bns_owners_to_names(cryptonote::rpc::BNS_OWNERS_TO_NAMES::request const &request) const; std::pair> bns_names_to_owners(cryptonote::rpc::BNS_NAMES_TO_OWNERS::request const &request) const; - std::pair - bns_resolve(cryptonote::rpc::BNS_RESOLVE::request const &request) const; + std::pair + bns_resolve(nlohmann::json const &request) const; private: bool get_info() const; @@ -123,11 +123,11 @@ class NodeRPCProxy mutable std::mutex m_mn_cache_mutex; mutable uint64_t m_all_master_nodes_cached_height; - mutable std::vector m_all_master_nodes; + mutable nlohmann::json m_all_master_nodes; mutable uint64_t m_contributed_master_nodes_cached_height; mutable std::string m_contributed_master_nodes_cached_address; - mutable std::vector m_contributed_master_nodes; + mutable nlohmann::json m_contributed_master_nodes; mutable uint64_t m_height; mutable uint64_t m_immutable_height; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c226978d9be..4d21e7e607a 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -40,6 +40,7 @@ #include #include #include +#include #include "cryptonote_basic/account.h" #include "cryptonote_basic/account_boost_serialization.h" @@ -450,7 +451,7 @@ namespace tools crypto::hash hash; cryptonote::block block; std::vector txes; - cryptonote::rpc::GET_BLOCKS_FAST::block_output_indices o_indices; + nlohmann::json o_indices; bool error; }; @@ -816,10 +817,10 @@ namespace tools auto get_all_master_nodes() const { return m_node_rpc_proxy.get_all_master_nodes(); } auto get_master_nodes(std::vector const &pubkeys) const { return m_node_rpc_proxy.get_master_nodes(pubkeys); } auto get_master_node_blacklisted_key_images() const { return m_node_rpc_proxy.get_master_node_blacklisted_key_images(); } - std::vector list_current_stakes(); + nlohmann::json list_current_stakes(); auto bns_owners_to_names(cryptonote::rpc::BNS_OWNERS_TO_NAMES::request const &request) const { return m_node_rpc_proxy.bns_owners_to_names(request); } auto bns_names_to_owners(cryptonote::rpc::BNS_NAMES_TO_OWNERS::request const &request) const { return m_node_rpc_proxy.bns_names_to_owners(request); } - auto resolve(cryptonote::rpc::BNS_RESOLVE::request const &request) const { return m_node_rpc_proxy.bns_resolve(request); } + auto resolve(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_resolve(request); } struct bns_detail { From 7f53d592aa38b700b00eb11e02c35ab808a21549 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 15:15:32 +0530 Subject: [PATCH 032/182] RPC update: get_connections - add documentation - change download/upload speed values to B/s instead of KiB/s - return times as milliseconds instead of seconds, and rename those fields from ..._time to ..._ms. - Code cleanup of state strings - remove completely unused peer state "idle" --- src/cryptonote_basic/connection_context.h | 40 ++++------ .../cryptonote_protocol_defs.cpp | 74 +++++++++---------- .../cryptonote_protocol_defs.h | 4 +- .../cryptonote_protocol_handler.inl | 8 +- src/daemon/rpc_command_executor.cpp | 51 +++++++------ src/p2p/net_node.cpp | 2 +- src/rpc/core_rpc_server.cpp | 33 ++++++++- src/rpc/core_rpc_server_commands_defs.h | 35 +++++++-- 8 files changed, 141 insertions(+), 106 deletions(-) diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index d0c0fff7923..3a2d97fade5 100755 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -36,6 +36,7 @@ #include "epee/copyable_atomic.h" #include "crypto/hash.h" +using namespace std::literals; namespace cryptonote { @@ -46,7 +47,6 @@ namespace cryptonote state_before_handshake = 0, //default state state_synchronizing, state_standby, - state_idle, state_normal }; @@ -67,41 +67,27 @@ namespace cryptonote //size_t m_score{0}; TODO: add score calculations }; - inline std::string get_protocol_state_string(cryptonote_connection_context::state s) + constexpr std::string_view get_protocol_state_string(cryptonote_connection_context::state s) { switch (s) { - case cryptonote_connection_context::state_before_handshake: - return "before_handshake"; - case cryptonote_connection_context::state_synchronizing: - return "synchronizing"; - case cryptonote_connection_context::state_standby: - return "standby"; - case cryptonote_connection_context::state_idle: - return "idle"; - case cryptonote_connection_context::state_normal: - return "normal"; - default: - return "unknown"; + case cryptonote_connection_context::state_before_handshake: return "before_handshake"sv; + case cryptonote_connection_context::state_synchronizing: return "synchronizing"sv; + case cryptonote_connection_context::state_standby: return "standby"sv; + case cryptonote_connection_context::state_normal: return "normal"sv; + default: return "unknown"sv; } } - inline char get_protocol_state_char(cryptonote_connection_context::state s) + constexpr char get_protocol_state_char(cryptonote_connection_context::state s) { switch (s) { - case cryptonote_connection_context::state_before_handshake: - return 'h'; - case cryptonote_connection_context::state_synchronizing: - return 's'; - case cryptonote_connection_context::state_standby: - return 'w'; - case cryptonote_connection_context::state_idle: - return 'i'; - case cryptonote_connection_context::state_normal: - return 'n'; - default: - return 'u'; + case cryptonote_connection_context::state_before_handshake: return 'h'; + case cryptonote_connection_context::state_synchronizing: return 's'; + case cryptonote_connection_context::state_standby: return 'w'; + case cryptonote_connection_context::state_normal: return 'n'; + default: return 'u'; } } diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.cpp b/src/cryptonote_protocol/cryptonote_protocol_defs.cpp index 32638bbed70..5bf65468efd 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.cpp @@ -2,43 +2,43 @@ namespace cryptonote { -KV_SERIALIZE_MAP_CODE_BEGIN(connection_info) - KV_SERIALIZE(incoming) - KV_SERIALIZE(localhost) - KV_SERIALIZE(local_ip) - KV_SERIALIZE(address) - KV_SERIALIZE(host) - KV_SERIALIZE(ip) - KV_SERIALIZE(port) - KV_SERIALIZE(rpc_port) - KV_SERIALIZE(peer_id) - KV_SERIALIZE(recv_count) - uint64_t recv_idle_time, send_idle_time, live_time; - if (is_store) { - recv_idle_time = std::chrono::duration_cast(this_ref.recv_idle_time).count(); - send_idle_time = std::chrono::duration_cast(this_ref.send_idle_time).count(); - live_time = std::chrono::duration_cast(this_ref.live_time).count(); - } - KV_SERIALIZE_VALUE(recv_idle_time) - KV_SERIALIZE(send_count) - KV_SERIALIZE_VALUE(send_idle_time) - KV_SERIALIZE(state) - KV_SERIALIZE_VALUE(live_time) - if constexpr (!is_store) { - this_ref.recv_idle_time = std::chrono::seconds{recv_idle_time}; - this_ref.send_idle_time = std::chrono::seconds{send_idle_time}; - this_ref.live_time = std::chrono::seconds{live_time}; - } - KV_SERIALIZE(avg_download) - KV_SERIALIZE(current_download) - KV_SERIALIZE(avg_upload) - KV_SERIALIZE(current_upload) - KV_SERIALIZE(support_flags) - KV_SERIALIZE(connection_id) - KV_SERIALIZE(height) - KV_SERIALIZE(pruning_seed) - KV_SERIALIZE(address_type) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(connection_info) +// KV_SERIALIZE(incoming) +// KV_SERIALIZE(localhost) +// KV_SERIALIZE(local_ip) +// KV_SERIALIZE(address) +// KV_SERIALIZE(host) +// KV_SERIALIZE(ip) +// KV_SERIALIZE(port) +// KV_SERIALIZE(rpc_port) +// KV_SERIALIZE(peer_id) +// KV_SERIALIZE(recv_count) +// uint64_t recv_idle_time, send_idle_time, live_time; +// if (is_store) { +// recv_idle_time = std::chrono::duration_cast(this_ref.recv_idle_time).count(); +// send_idle_time = std::chrono::duration_cast(this_ref.send_idle_time).count(); +// live_time = std::chrono::duration_cast(this_ref.live_time).count(); +// } +// KV_SERIALIZE_VALUE(recv_idle_time) +// KV_SERIALIZE(send_count) +// KV_SERIALIZE_VALUE(send_idle_time) +// KV_SERIALIZE(state) +// KV_SERIALIZE_VALUE(live_time) +// if constexpr (!is_store) { +// this_ref.recv_idle_time = std::chrono::seconds{recv_idle_time}; +// this_ref.send_idle_time = std::chrono::seconds{send_idle_time}; +// this_ref.live_time = std::chrono::seconds{live_time}; +// } +// KV_SERIALIZE(avg_download) +// KV_SERIALIZE(current_download) +// KV_SERIALIZE(avg_upload) +// KV_SERIALIZE(current_upload) +// KV_SERIALIZE(support_flags) +// KV_SERIALIZE(connection_id) +// KV_SERIALIZE(height) +// KV_SERIALIZE(pruning_seed) +// KV_SERIALIZE(address_type) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(serializable_flash_metadata) KV_SERIALIZE_VAL_POD_AS_BLOB_N(tx_hash, "#") diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index beddd3ad1ce..cd3f6112e73 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -50,7 +50,7 @@ namespace cryptonote #define BC_COMMANDS_POOL_BASE 2000 /************************************************************************/ - /* P2P connection info, serializable to json */ + /* P2P connection info */ /************************************************************************/ struct connection_info { @@ -92,8 +92,6 @@ namespace cryptonote uint32_t pruning_seed; uint8_t address_type; - - KV_MAP_SERIALIZABLE }; /************************************************************************/ diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 34e72a9ba94..d254dfb5f33 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -313,12 +313,12 @@ namespace cryptonote else { - cnx.avg_download = cntxt.m_recv_cnt / connection_time.count() / 1024; - cnx.avg_upload = cntxt.m_send_cnt / connection_time.count() / 1024; + cnx.avg_download = cntxt.m_recv_cnt / connection_time.count(); + cnx.avg_upload = cntxt.m_send_cnt / connection_time.count(); } - cnx.current_download = cntxt.m_current_speed_down / 1024; - cnx.current_upload = cntxt.m_current_speed_up / 1024; + cnx.current_download = cntxt.m_current_speed_down; + cnx.current_upload = cntxt.m_current_speed_up; cnx.connection_id = tools::type_to_hex(cntxt.m_connection_id); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 9448e0fef19..66a59e72da3 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -701,39 +701,38 @@ bool rpc_command_executor::mining_status() { } bool rpc_command_executor::print_connections() { - GET_CONNECTIONS::response res{}; - - if (!invoke({}, res, "Failed to retrieve connection info")) + auto maybe_conns = try_running([this] { return invoke(); }, "Failed to retrieve connection info"); + if (!maybe_conns) return false; + auto& conns = *maybe_conns; - constexpr auto hdr_fmt = "{:<30}{:<8}{:<20}{:<30}{:<25}{:<20}{:<12s}{:<14s}{:<10s}{:<13s}"sv; - constexpr auto row_fmt = "{:<30}{:<8}{:<20}{:<30}{:<25}{:<20}{:<12.1f}{:<14.1f}{:<10.1f}{:<13.1f}{}{}"sv; - tools::msg_writer() << fmt::format(hdr_fmt, - "Remote Host", "Type", "Peer id", "Recv/Sent (inactive,sec)", "State", "Livetime(sec)", - "Down (kB/sec)", "Down(now)", "Up (kB/s)", "Up(now)"); - + constexpr auto hdr_fmt = "{:<30}{:<8}{:<20}{:<30}{:<25}{:<20}{:<12s}{:<14s}{:<10s}{:<13s}"sv; + constexpr auto row_fmt = "{:<30}{:<8}{:<20}{:<30}{:<25}{:<20}{:<12.1f}{:<14.1f}{:<10.1f}{:<13.1f}{}{}"sv; + tools::msg_writer() << fmt::format(hdr_fmt, + "Remote Host", "Type", "Peer id", "Recv/Sent (inactive,sec)", "State", "Livetime(sec)", + "Down (kB/sec)", "Down(now)", "Up (kB/s)", "Up(now)"); - for (auto & info : res.connections) + for (auto& info : conns) { - std::string address = info.incoming ? "INC " : "OUT "; - address += info.ip; + std::string address = info["incoming"].get() ? "INC " : "OUT "; + address += info["ip"].get(); address += ':'; - address += info.port; + address += tools::int_to_string(info["port"].get()); tools::msg_writer() << fmt::format(row_fmt, address, - info.address_type, - info.peer_id, - fmt::format("{}({}/{})", info.recv_count, - tools::friendly_duration(info.recv_idle_time), - tools::friendly_duration(info.send_idle_time)), - info.state, - tools::friendly_duration(info.live_time), - info.avg_upload / 1000., - info.current_download / 1000., - info.avg_upload / 1000., - info.current_upload / 1000., - info.localhost ? "[LOCALHOST]" : "", - info.local_ip ? "[LAN]" : ""); + info["address_type"].get(), + info["peer_id"].get(), + fmt::format("{}({}/{})", info["recv_count"].get(), + tools::friendly_duration(1ms * info["recv_idle_ms"].get()), + tools::friendly_duration(1ms * info["send_idle_ms"].get())), + info["state"].get(), + tools::friendly_duration(1ms * info["live_ms"].get()), + info["avg_download"].get() / 1000., + info["current_download"].get() / 1000., + info["avg_upload"].get() / 1000., + info["current_upload"].get() / 1000., + (info.value("localhost", false) ? "[LOCALHOST]" : ""), + (info.value("local_ip", false) ? "[LAN]" : "")); } return true; diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index d9cc0902059..6c55f1fc72e 100755 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -73,7 +73,7 @@ namespace if (!address) { MERROR( - "Failed to parse " << epee::net_utils::zone_to_string(T::get_zone()) << " address \"" << value << "\": " << address.error().message() + "Failed to parse " << T::get_zone() << " address \"" << value << "\": " << address.error().message() ); return {}; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7d3da63065c..49271761349 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2095,10 +2095,37 @@ namespace cryptonote::rpc { PERF_TIMER(on_get_connections); auto connections = m_p2p.get_payload_object().get_connections(); - get_connections.response["connections"] = connections; + auto& c = get_connections.response["connections"]; + c = json::array(); + for (auto& ci : connections) { + json info{ + {"incoming", ci.incoming}, + {"ip", ci.ip}, + {"address_type", ci.address_type}, + {"peer_id", ci.peer_id}, + {"recv_count", ci.recv_count}, + {"recv_idle_ms", ci.recv_idle_time.count()}, + {"send_count", ci.send_count}, + {"send_idle_ms", ci.send_idle_time.count()}, + {"state", ci.state}, + {"live_ms", ci.live_time.count()}, + {"avg_download", ci.avg_download}, + {"current_download", ci.current_download}, + {"avg_upload", ci.avg_upload}, + {"current_upload", ci.current_upload}, + {"support_flags", ci.support_flags}, + {"connection_id", ci.connection_id}, + {"height", ci.height}, + }; + if (ci.ip != ci.host) info["host"] = ci.host; + if (ci.localhost) info["localhost"] = true; + if (ci.local_ip) info["local_ip"] = true; + if (uint16_t port; tools::parse_int(ci.port, port) && port > 0) info["port"] = port; + if (ci.rpc_port > 0) info["rpc_port"] = ci.rpc_port; + if (ci.pruning_seed) info["pruning_seed"] = ci.pruning_seed; + c.push_back(std::move(info)); + } get_connections.response["status"] = STATUS_OK; - LOG_PRINT_L0(get_connections.response["status"]); - return; } //------------------------------------------------------------------------------------------------------------------------------ HARD_FORK_INFO::response core_rpc_server::invoke(HARD_FORK_INFO::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 125a6a849b5..bb1d18bb522 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1376,16 +1376,41 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } }; - //----------------------------------------------- - /// Retrieve information about incoming and outgoing connections to your node. + /// Retrieve information about incoming and outgoing P2P connections to your node. /// /// Inputs: none /// /// Output values available from a public RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p connections List of all connections and their info: - struct GET_CONNECTIONS : RPC_COMMAND + /// - \p connections List of all connections and their info; each element is a dict containing: + /// - \p incoming bool of whether this connection was established by the remote to us (true) or + /// by us to the remove (false). + /// - \p ip address of the remote peer + /// - \p port the remote port of the peer connection + /// - \p address_type - 1/2/3/4 for ipv4/ipv6/i2p/tor, respectively. + /// - \p peer_id a string that uniquely identifies a peer node + /// - \p recv_count number of bytes of data received from this peer + /// - \p recv_idle_ms number of milliseconds since we last received data from this peer + /// - \p send_count number of bytes of data send to this peer + /// - \p send_idle_ms number of milliseconds since we last sent data to this peer + /// - \p state returns the current state of the connection with this peer as a string, one of: + /// - \c before_handshake - the connection is still being established/negotiated + /// - \c synchronizing - we are synchronizing the blockchain with this peer + /// - \c standby - the peer is available for synchronizing but we are not currently using it + /// - \c normal - this is a regular, synchronized peer + /// - \p live_ms - number of milliseconds since this connection was initiated + /// - \p avg_download - the average download speed from this peer in bytes per second + /// - \p current_download - the current (i.e. average over a very recent period) download speed from this peer in bytes per second. + /// - \p avg_upload - the average upload speed to this peer in bytes per second + /// - \p current_upload - the current upload speed to this peer in bytes per second + /// - \p connection_id - a unique random string identifying this connection + /// - \p height - the height of the peer + /// - \p host - the hostname for this peer; only included if != \p ip + /// - \p localhost - set to true if the peer is a localhost connection; omitted otherwise. + /// - \p local_ip - set to true if the peer is a non-public, local network connection; omitted + /// otherwise. + struct GET_CONNECTIONS : NO_ARGS { static constexpr auto names() { return NAMES("get_connections"); } }; @@ -2799,6 +2824,7 @@ namespace cryptonote::rpc { /// ::response. The ::request has to be unique (for overload resolution); /// ::response does not. using core_rpc_types = tools::type_list< + GET_CONNECTIONS, GET_HEIGHT, GET_INFO, BNS_RESOLVE, @@ -2844,7 +2870,6 @@ namespace cryptonote::rpc { SET_LOG_LEVEL, SET_LOG_CATEGORIES, GET_TRANSACTION_POOL, - GET_CONNECTIONS, GET_BLOCK_HEADERS_RANGE, SET_BOOTSTRAP_DAEMON, GET_LIMIT, From 3971211bd57e04dd07aa265a3dc030d15f1177cf Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 18 Apr 2025 15:43:38 +0530 Subject: [PATCH 033/182] Add support for loading from various container types --- src/rpc/core_rpc_server_command_parser.cpp | 90 ++++++++++++++++------ 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 3e226f96c64..69b4c16fb36 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -1,9 +1,11 @@ #include "core_rpc_server_command_parser.h" +#include "oxenc/bt_serialize.h" #include #include #include #include +#include namespace cryptonote::rpc { using nlohmann::json; @@ -54,32 +56,51 @@ namespace cryptonote::rpc { return it != end && it.key() == name; } + // List types that are expandable; for these we emplace_back for each element of the input + template constexpr bool is_expandable_list = false; + template constexpr bool is_expandable_list> = true; + // Don't currently need these, but they will work fine if uncommented: + // template constexpr bool is_expandable_list> = true; + // template constexpr bool is_expandable_list> = true; + // template constexpr bool is_expandable_list> = true; + + // Fixed size elements: tuples, pairs, and std::array's; we accept list input as long as the + // list length matches exactly. + template constexpr bool is_tuple_like = false; + template constexpr bool is_tuple_like> = true; + template constexpr bool is_tuple_like> = true; + template constexpr bool is_tuple_like> = true; + + template + void load_tuple_values(oxenc::bt_list_consumer&, TupleLike&, std::index_sequence); + // Consumes the next value from the dict consumer into `val` - template - void load_value(oxenc::bt_dict_consumer& d, T& val) { + template + || std::is_same_v, + int> = 0> + void load_value(BTConsumer& c, T& val) { if constexpr (std::is_integral_v) - val = d.consume_integer(); + val = c.template consume_integer(); else if constexpr (std::is_same_v || std::is_same_v) - val = d.consume_string_view(); + val = c.consume_string_view(); else if constexpr (is_binary_parameter) - load_binary_parameter(d.consume_string_view(), true /*allow raw*/, val); - else if constexpr (is_binary_vector) { + load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + else if constexpr (is_expandable_list) { + auto lc = c.consume_list_consumer(); val.clear(); - auto lc = d.consume_list_consumer(); while (!lc.is_finished()) - load_binary_parameter(lc.consume_string_view(), true /*allow raw*/, val.emplace_back()); + load_value(lc, val.emplace_back()); } - else if constexpr (std::is_same_v) - val = std::chrono::system_clock::time_point{std::chrono::seconds{d.consume_integer()}}; - else if constexpr (std::is_same_v> || std::is_same_v>) { - val.clear(); - auto lc = d.consume_list_consumer(); - while (!lc.is_finished()) - val.emplace_back(lc.consume_string_view()); + else if constexpr (is_tuple_like) { + auto lc = c.consume_list_consumer(); + load_tuple_values(lc, val, std::make_index_sequence>{}); } else static_assert(std::is_same_v, "Unsupported load_value type"); } + // Copies the next value from the json range into `val`, and advances the iterator. Throws // on unconvertible values. template @@ -120,19 +141,20 @@ namespace cryptonote::rpc { val = i; } else if constexpr (std::is_same_v || std::is_same_v) { val = e.get(); - } else if constexpr (is_binary_parameter || - is_binary_vector || - std::is_same_v> || - std::is_same_v>) { - val = e.get(); - } else if constexpr (std::is_same_v) { - val = std::chrono::system_clock::time_point{std::chrono::seconds{e.get()}}; + } else if constexpr (is_binary_parameter || is_expandable_list || is_tuple_like) { + try { e.get_to(val); } + catch (const std::exception& e) { throw std::domain_error{"Invalid values in '" + key + "'"};} } else { static_assert(std::is_same_v, "Unsupported load type"); } ++r.first; } + template + void load_tuple_values(oxenc::bt_list_consumer& c, TupleLike& val, std::index_sequence) { + (load_value(c, std::get(val)), ...); + } + // Gets the next value from a json object iterator or bt_dict_consumer. Leaves the iterator at // the next value, i.e. found + 1 if found, or the next greater value if not found. (NB: // nlohmann::json objects are backed by an *ordered* map and so both nlohmann iterators and @@ -241,8 +263,28 @@ namespace cryptonote::rpc { void parse_request(SAVE_BC& save_bc, rpc_input in) { } void parse_request(GET_OUTPUTS& get_outputs, rpc_input in) { - get_values(in, "get_txid", get_outputs.request.get_txid); - get_values(in, "outputs", get_outputs.request.outputs) + get_values(in, + "as_tuple", get_outputs.request.as_tuple, + "get_txid", get_outputs.request.get_txid); + + // "outputs" is trickier: for backwards compatibility we need to accept json of: + // [{"amount":0,"index":i1}, ...] + // but that is incredibly wasteful and so we also want the more efficient (and we only accept + // this for bt, since we don't have backwards compat to worry about): + // [i1, i2, ...] + bool legacy_outputs = false; + if (auto* json_in = std::get_if(&in)) { + if (auto outputs = json_in->find("outputs"); + outputs != json_in->end() && !outputs->empty() && outputs->is_array() && outputs->front().is_object()) { + legacy_outputs = true; + auto& reqoi = get_outputs.request.output_indices; + reqoi.reserve(outputs->size()); + for (auto& o : *outputs) + reqoi.push_back(o["index"].get()); + } + } + if (!legacy_outputs) + get_values(in, "outputs", get_outputs.request.output_indices); } } \ No newline at end of file From fb51a23e6a69ae1b3cbd0792ce50181857c3116f Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 18 Apr 2025 15:44:04 +0530 Subject: [PATCH 034/182] get_outputs - finish, backwards compat, add results as tuple --- src/rpc/core_rpc_server.cpp | 51 ++++++++++++++++--------- src/rpc/core_rpc_server_commands_defs.h | 43 +++++++++------------ src/rpc/rpc_binary.h | 4 +- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 49271761349..3a9c53bff7e 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -116,7 +116,7 @@ namespace cryptonote::rpc { if constexpr (!std::is_base_of_v) { static_assert(!FIXME_has_nested_response_v); cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> std::string { - RPC rpc; + RPC rpc{}; try { if (auto body = request.body_view()) { if (body->front() == 'd') { // Looks like a bt dict @@ -652,32 +652,49 @@ namespace cryptonote::rpc { //if (use_bootstrap_daemon_if_necessary(req, res)) //return; - if (!context.admin && get_outputs.request["outputs"].size() > GET_OUTPUTS::MAX_COUNT) { + if (!context.admin && get_outputs.request.output_indices.size() > GET_OUTPUTS::MAX_COUNT) { get_outputs.response["status"] = "Too many outs requested"; return; } + // This is nasty. WTF are core methods taking *local rpc* types? + // FIXME: make core methods take something sensible, like a std::vector. (We really + // don't need the pair since amount is also 0 for Beldex since the beginning of the chain; only in + // ancient Monero blocks was it non-zero). GET_OUTPUTS_BIN::request req_bin{}; - req_bin.outputs = get_outputs.request["outputs"]; - req_bin.get_txid = get_outputs.request["get_txid"]; + req_bin.get_txid = get_outputs.request.get_txid; + req_bin.outputs.reserve(get_outputs.request.output_indices.size()); + for (auto oi : get_outputs.request.output_indices) + req_bin.outputs.push_back({0, oi}); + GET_OUTPUTS_BIN::response res_bin{}; - if (!m_core.get_outs(req_bin, res_bin)) - { + if (!m_core.get_outs(req_bin, res_bin)){ get_outputs.response["status"] = "Failed"; return; } - get_outputs.response["outs"] = std::vector{}; - - // convert to text - for (const auto &i: res_bin.outs) - { - auto& outkey = get_outputs.response["outs"].emplace_back(); - outkey.key = tools::type_to_hex(i.key); - outkey.mask = tools::type_to_hex(i.mask); - outkey.unlocked = i.unlocked; - outkey.height = i.height; - outkey.txid = tools::type_to_hex(i.txid); + auto& outs = (get_outputs.response["outs"] = json::array()); + if (!get_outputs.request.as_tuple) { + for (auto& outkey : res_bin.outs) { + outs.push_back(json{ + {"key", std::move(outkey.key)}, + {"mask", std::move(outkey.mask)}, + {"unlocked", outkey.unlocked}, + {"height", outkey.height} + }); + if (get_outputs.request.get_txid) + outs.back()["txid"] = std::move(outkey.txid); + } + } else { + for (auto& outkey : res_bin.outs) { + outs.push_back(json::array({ + std::move(outkey.key), + std::move(outkey.mask), + outkey.unlocked, + outkey.height})); + if (get_outputs.request.get_txid) + outs.back().push_back(std::move(outkey.txid)); + } } get_outputs.response["status"] = STATUS_OK; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index bb1d18bb522..197345c9e0a 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -564,22 +564,25 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p outputs Array of structure `get_outputs_out`. - /// - \p get_txid Request the TXID/hash of the transaction as well. + /// - \p outputs Array of output indices. For backwards compatibility these may also be passed as + /// an array of {"amount":0,"index":n} dicts. + /// - \p get_txid Request the TXID (i.e. hash) of the transaction as well. + /// - \p as_tuple Requests the returned outs variable as a tuple of values rather than a dict. /// /// Output values available from a public RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore untrusted ('true'), or when the daemon is fully synced ('false'). - /// - \p outs List of outkey information. - /// - /// Outkey Information: - /// - /// - \p key The public key of the output. - /// - \p mask something - /// - \p unlocked States if output is locked (`false`) or not (`true`). - /// - \p height Block height of the output. - /// - \p txid Transaction id. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore + /// untrusted ('true'), or when the daemon is fully synced ('false'). + /// - \p outs List of outkey information; if `as_tuple` is not set then these are dicts containing + /// keys: + /// - \p key The public key of the output. + /// - \p mask + /// - \p unlocked States if output is locked (`false`) or not (`true`). + /// - \p height Block height of the output. + /// - \p txid Transaction id; only present if requested via the `get_txid` parameter. + /// Otherwise, when `as_tuple` is set, these are 4- or 5-element arrays (depending on whether + /// `get_txid` is desired) containing the values in the order listed above. struct GET_OUTPUTS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_outs"); } @@ -587,21 +590,11 @@ namespace cryptonote::rpc { /// Maximum outputs that may be requested in a single request (unless admin) static constexpr size_t MAX_COUNT = 5000; - struct outkey - { - std::string key; // The public key of the output. - std::string mask; - bool unlocked; // States if output is locked (`false`) or not (`true`). - uint64_t height; // Block height of the output. - std::string txid; // Transaction id. - - KV_MAP_SERIALIZABLE - }; - struct request_parameters { - bool get_txid; // If true the request will return the TXID/hash of the transaction as well. - std::vector> outputs; // Array of (Amount, Index) where amount is the amount of Beldex and index is the output index + bool get_txid = false; // If true the request will return the TXID/hash of the transaction as well. + bool as_tuple = false; + std::vector output_indices; } request; }; diff --git a/src/rpc/rpc_binary.h b/src/rpc/rpc_binary.h index b4576a2809d..3a4f1327ffc 100644 --- a/src/rpc/rpc_binary.h +++ b/src/rpc/rpc_binary.h @@ -1,6 +1,7 @@ #pragma once -#include +#include "ringct/rctTypes.h" +#include "crypto/crypto.h" #include #include @@ -17,6 +18,7 @@ namespace cryptonote::rpc { template <> inline constexpr bool is_binary_parameter = true; template <> inline constexpr bool is_binary_parameter = true; template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; template inline constexpr bool is_binary_vector = false; From e02f17d5ef73e077beeb70ab805daa3e29aa91f3 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 15:45:52 +0530 Subject: [PATCH 035/182] RPC overhaul: SYNC_INFO --- src/daemon/rpc_command_executor.cpp | 101 +++++++++++++--------- src/rpc/core_rpc_server.cpp | 89 ++++++++++--------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 42 ++++----- src/rpc/core_rpc_server_commands_defs.h | 68 +++++++-------- 5 files changed, 159 insertions(+), 143 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 66a59e72da3..3696947b9cd 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1529,54 +1529,71 @@ bool rpc_command_executor::relay_tx(const std::string &txid) bool rpc_command_executor::sync_info() { - SYNC_INFO::response res{}; - - if (!invoke({}, res, "Failed to retrieve synchronization info")) - return false; - - uint64_t target = res.target_height < res.height ? res.height : res.target_height; - tools::success_msg_writer() << "Height: " << res.height << ", target: " << target << " (" << (100.0 * res.height / target) << "%)"; - uint64_t current_download = 0; - for (const auto &p: res.peers) - current_download += p.info.current_download; - tools::success_msg_writer() << "Downloading at " << current_download << " kB/s"; - if (res.next_needed_pruning_seed) - tools::success_msg_writer() << "Next needed pruning seed: " << res.next_needed_pruning_seed; - - tools::success_msg_writer() << std::to_string(res.peers.size()) << " peers"; - for (const auto &p: res.peers) - { - std::string address = epee::string_tools::pad_string(p.info.address, 24); - uint64_t nblocks = 0, size = 0; - for (const auto &s: res.spans) - if (s.connection_id == p.info.connection_id) - nblocks += s.nblocks, size += s.size; - tools::success_msg_writer() << address << " " << p.info.peer_id << " " << - epee::string_tools::pad_string(p.info.state, 16) << " " << - epee::string_tools::pad_string(epee::string_tools::to_string_hex(p.info.pruning_seed), 8) << " " << p.info.height << " " << - p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued"; + auto maybe_sync = try_running([this] { return invoke(); }, "Failed to retrieve sync info"); + if (!maybe_sync) + return false; + auto& sync = *maybe_sync; + + uint64_t height = sync["height"].get(); + uint64_t target = std::max(sync.value("target_height", height), height); + tools::success_msg_writer() << "Height: " << height << ", target: " << target << " (" << (100.0 * height / target) << "%)"; + auto& spans = sync["spans"]; + auto& peers = sync["peers"]; + uint64_t current_download = 0; + for (const auto& p: peers) + current_download += p["current_download"].get(); + tools::success_msg_writer() << "Downloading at " << current_download/1000.0 << " kB/s"; + if (auto nnps = sync.value("next_needed_pruning_seed", 0)) + tools::success_msg_writer() << "Next needed pruning seed: " << nnps; + + tools::success_msg_writer() << std::to_string(peers.size()) << " peers"; + for (const auto& [cid, p]: peers.items()) + { + std::string address = epee::string_tools::pad_string(p["ip"].get() + ":" + std::to_string(p["port"].get()), 24); + uint64_t nblocks = 0, size = 0; + for (const auto& s: spans) { + if (s["connection_id"] == cid) { + nblocks += s["nblocks"].get(); + size += s["size"].get(); + } } + tools::success_msg_writer() << address << " " << p["peer_id"].get() << " " << + epee::string_tools::pad_string(p["state"].get(), 16) << " " << + //epee::string_tools::pad_string(epee::string_tools::to_string_hex(p.info.pruning_seed), 8) << " " << + p["height"].get() << " " << + p["current_download"].get() / 1000. << " kB/s, " << + nblocks << " blocks / " << size/1'000'000. << " MB queued"; + } + - uint64_t total_size = 0; - for (const auto &s: res.spans) - total_size += s.size; - tools::success_msg_writer() << std::to_string(res.spans.size()) << " spans, " << total_size/1e6 << " MB"; - tools::success_msg_writer() << res.overview; - for (const auto &s: res.spans) + uint64_t total_size = 0; + for (const auto& s: spans) + total_size += s["size"].get(); + tools::success_msg_writer() << std::to_string(spans.size()) << " spans, " << total_size/1e6 << " MB"; + if (auto overview = sync["overview"].get(); overview != "[]"sv) + tools::success_msg_writer() << overview; + for (const auto& s: spans) + { + auto& c = peers[s["connection_id"].get_ref()]; + std::string address = "(unknown)"; + if (c.is_object()) + address = c["ip"].get() + ":" + std::to_string(c["port"].get()); + address = epee::string_tools::pad_string(std::move(address), 24); + //std::string pruning_seed = epee::string_tools::to_string_hex(tools::get_pruning_seed(s.start_block_height, std::numeric_limits::max(), CRYPTONOTE_PRUNING_LOG_STRIPES)); + auto size = s["size"].get(); + auto start = s["start_block_height"].get(); + auto nblocks = s["nblocks"].get(); { - std::string address = epee::string_tools::pad_string(s.remote_address, 24); - std::string pruning_seed = epee::string_tools::to_string_hex(tools::get_pruning_seed(s.start_block_height, std::numeric_limits::max(), cryptonote::PRUNING_LOG_STRIPES)); - if (s.size == 0) - { - tools::success_msg_writer() << address << " " << s.nblocks << "/" << pruning_seed << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ") -"; - } + auto writer = tools::success_msg_writer(); + writer << address << " " << nblocks << /*"/" << pruning_seed <<*/ " (" << start << " - " << (start + nblocks - 1); + if (size == 0) + writer << ") -"; else - { - tools::success_msg_writer() << address << " " << s.nblocks << "/" << pruning_seed << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ", " << (uint64_t)(s.size/1e3) << " kB) " << (unsigned)(s.rate/1e3) << " kB/s (" << s.speed/100.0f << ")"; - } + writer << ", " << size/1000. << " kB) " << s["rate"].get() / 1000. << " kB/s (" << s["speed"].get() / 100. << ")"; } + } - return true; + return true; } static std::string to_string_rounded(double d, int precision) { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 3a9c53bff7e..1de05e0b881 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2106,16 +2106,9 @@ namespace cryptonote::rpc { res.status = STATUS_OK; return res; } - //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GET_CONNECTIONS& get_connections, rpc_context context) - { - PERF_TIMER(on_get_connections); - auto connections = m_p2p.get_payload_object().get_connections(); - auto& c = get_connections.response["connections"]; - c = json::array(); - for (auto& ci : connections) { - json info{ + static json json_connection_info(const connection_info& ci) { + json info{ {"incoming", ci.incoming}, {"ip", ci.ip}, {"address_type", ci.address_type}, @@ -2133,15 +2126,27 @@ namespace cryptonote::rpc { {"support_flags", ci.support_flags}, {"connection_id", ci.connection_id}, {"height", ci.height}, - }; - if (ci.ip != ci.host) info["host"] = ci.host; - if (ci.localhost) info["localhost"] = true; - if (ci.local_ip) info["local_ip"] = true; - if (uint16_t port; tools::parse_int(ci.port, port) && port > 0) info["port"] = port; - if (ci.rpc_port > 0) info["rpc_port"] = ci.rpc_port; - if (ci.pruning_seed) info["pruning_seed"] = ci.pruning_seed; - c.push_back(std::move(info)); - } + }; + if (ci.ip != ci.host) info["host"] = ci.host; + if (ci.localhost) info["localhost"] = true; + if (ci.local_ip) info["local_ip"] = true; + if (uint16_t port; tools::parse_int(ci.port, port) && port > 0) info["port"] = port; + // Included for completeness, but undocumented as neither of these are currently actually used + // or support on Beldex: + if (ci.rpc_port > 0) info["rpc_port"] = ci.rpc_port; + if (ci.pruning_seed) info["pruning_seed"] = ci.pruning_seed; + return info; + } + + //------------------------------------------------------------------------------------------------------------------------------ + void core_rpc_server::invoke(GET_CONNECTIONS& get_connections, rpc_context context) + { + PERF_TIMER(on_get_connections); + auto& c = get_connections.response["connections"]; + c = json::array(); + for (auto& ci : m_p2p.get_payload_object().get_connections()) + c.push_back(json_connection_info(ci)); + get_connections.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2579,34 +2584,38 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - SYNC_INFO::response core_rpc_server::invoke(SYNC_INFO::request&& req, rpc_context context) + void core_rpc_server::invoke(SYNC_INFO& sync, rpc_context context) { - SYNC_INFO::response res{}; - PERF_TIMER(on_sync_info); auto [top_height, top_hash] = m_core.get_blockchain_top(); - res.height = top_height + 1; // turn top block height into blockchain height - res.target_height = m_core.get_target_blockchain_height(); - res.next_needed_pruning_seed = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second; - - for (const auto &c: m_p2p.get_payload_object().get_connections()) - res.peers.push_back({c}); - const cryptonote::block_queue &block_queue = m_p2p.get_payload_object().get_block_queue(); - block_queue.foreach([&](const cryptonote::block_queue::span &span) { - const std::string span_connection_id = tools::type_to_hex(span.connection_id); - uint32_t speed = (uint32_t)(100.0f * block_queue.get_speed(span.connection_id) + 0.5f); - std::string address = ""; - for (const auto &c: m_p2p.get_payload_object().get_connections()) - if (c.connection_id == span_connection_id) - address = c.address; - res.spans.push_back({span.start_block_height, span.nblocks, span_connection_id, (uint32_t)(span.rate + 0.5f), speed, span.size, address}); - return true; + sync.response["height"] = top_height + 1; // turn top block height into blockchain height + if (auto target_height = m_core.get_target_blockchain_height(); target_height > top_height + 1) + sync.response["target_height"] = target_height; + // Don't put this into the response until it actually does something on Oxen: + if (false) + sync.response["next_needed_pruning_seed"] = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second; + + auto& peers = sync.response["peers"]; + peers = json{}; + for (auto& ci : m_p2p.get_payload_object().get_connections()) + peers[ci.connection_id] = json_connection_info(ci); + const auto& block_queue = m_p2p.get_payload_object().get_block_queue(); + auto spans = json::array(); + block_queue.foreach([&spans, &block_queue](const auto& span) { + uint32_t speed = (uint32_t)(100.0f * block_queue.get_speed(span.connection_id) + 0.5f); + spans.push_back(json{ + {"start_block_height", span.start_block_height}, + {"nblocks", span.nblocks}, + {"connection_id", tools::type_to_hex(span.connection_id)}, + {"rate", std::lround(span.rate)}, + {"speed", speed}, + {"size", span.size}}); + return true; }); - res.overview = block_queue.get_overview(res.height); + sync.response["overview"] = block_queue.get_overview(top_height + 1); - res.status = STATUS_OK; - return res; + sync.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 99f6d428184..b2777ccc4ff 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -213,6 +213,7 @@ namespace cryptonote::rpc { void invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context); void invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context); void invoke(GET_CONNECTIONS& get_connections, rpc_context context); + void invoke(SYNC_INFO& sync, rpc_context context); void invoke(GET_MASTER_NODE_STATUS& sns, rpc_context context); void invoke(GET_MASTER_NODES& sns, rpc_context context); @@ -263,7 +264,6 @@ namespace cryptonote::rpc { GET_BASE_FEE_ESTIMATE::response invoke(GET_BASE_FEE_ESTIMATE::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); - SYNC_INFO::response invoke(SYNC_INFO::request&& req, rpc_context context); PRUNE_BLOCKCHAIN::response invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 37646bdb38a..35d2369edc1 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -879,31 +879,31 @@ KV_SERIALIZE_MAP_CODE_BEGIN(RELAY_TX::request) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::peer) - KV_SERIALIZE(info) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::peer) +// KV_SERIALIZE(info) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::span) - KV_SERIALIZE(start_block_height) - KV_SERIALIZE(nblocks) - KV_SERIALIZE(connection_id) - KV_SERIALIZE(rate) - KV_SERIALIZE(speed) - KV_SERIALIZE(size) - KV_SERIALIZE(remote_address) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::span) +// KV_SERIALIZE(start_block_height) +// KV_SERIALIZE(nblocks) +// KV_SERIALIZE(connection_id) +// KV_SERIALIZE(rate) +// KV_SERIALIZE(speed) +// KV_SERIALIZE(size) +// KV_SERIALIZE(remote_address) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::response) - KV_SERIALIZE(status) - KV_SERIALIZE(height) - KV_SERIALIZE(target_height) - KV_SERIALIZE(next_needed_pruning_seed) - KV_SERIALIZE(peers) - KV_SERIALIZE(spans) - KV_SERIALIZE(overview) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(height) +// KV_SERIALIZE(target_height) +// KV_SERIALIZE(next_needed_pruning_seed) +// KV_SERIALIZE(peers) +// KV_SERIALIZE(spans) +// KV_SERIALIZE(overview) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 197345c9e0a..ff101d9c40c 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1827,46 +1827,36 @@ namespace cryptonote::rpc { struct response : STATUS {}; }; - BELDEX_RPC_DOC_INTROSPECT - // Get synchronisation information. - struct SYNC_INFO : RPC_COMMAND + /// Get node synchronisation information. This returns information on the node's syncing "spans" + /// which are block segments being downloaded from peers while syncing; spans are generally + /// downloaded out of order from multiple peers, and so these spans reflect in-progress downloaded + /// blocks that have not yet been added to the block chain: typically because the spans is not yet + /// complete, or because the span is for future blocks that are dependent on intermediate blocks + /// (likely in another span) being added to the chain first. + /// + /// Inputs: none + /// + /// Output values available from an admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p height Current block height + /// - \p target_height If the node is currently syncing then this is the target height the node + /// wants to reach. If fully synced then this field is omitted. + /// - \p peers dict of connection information about peers. The key is the peer connection_id; the + /// value is identical to the values of the \p connections field of GET_CONNECTIONS. \sa + /// GET_CONNECTIONS. + /// - \p span array of span information of current in progress synchronization. Element element + /// contains: + /// - \p start_block_height Block height of the first block in the span + /// - \p nblocks the number of blocks in the span + /// - \p connection_id the connection_id of the connection from which we are downloading the span + /// - \p rate the most recent connection speed measurement + /// - \p speed the average connection speed over recent downloaded blocks + /// - \p size total number of block and transaction data stored in the span + /// - \p overview a string containing a one-line ascii-art depiction of the current sync status + struct SYNC_INFO : NO_ARGS { static constexpr auto names() { return NAMES("sync_info"); } - - struct request : EMPTY {}; - - struct peer - { - connection_info info; // Structure of connection info, as defined in get_connections. - - KV_MAP_SERIALIZABLE - }; - - struct span - { - uint64_t start_block_height; // Block height of the first block in that span. - uint64_t nblocks; // Number of blocks in that span. - std::string connection_id; // Id of connection. - uint32_t rate; // Connection rate. - uint32_t speed; // Connection speed. - uint64_t size; // Total number of bytes in that span's blocks (including txes). - std::string remote_address; // Peer address the node is downloading (or has downloaded) than span from. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - uint64_t height; // Block height. - uint64_t target_height; // Target height the node is syncing from (optional, absent if node is fully synced). - uint32_t next_needed_pruning_seed; - std::list peers; // Array of Peer structure - std::list spans; // Array of Span Structure. - std::string overview; - - KV_MAP_SERIALIZABLE - }; }; struct output_distribution_data @@ -2826,6 +2816,7 @@ namespace cryptonote::rpc { STOP_MINING, SAVE_BC, STOP_DAEMON, + SYNC_INFO, GETBLOCKCOUNT, MINING_STATUS, GET_TRANSACTION_POOL_HASHES, @@ -2880,7 +2871,6 @@ namespace cryptonote::rpc { GET_BASE_FEE_ESTIMATE, GET_ALTERNATE_CHAINS, RELAY_TX, - SYNC_INFO, GET_OUTPUT_DISTRIBUTION, POP_BLOCKS, PRUNE_BLOCKCHAIN, From 66fc88ac3e8a525fd6ae4785d6575220cb156556 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 16:17:07 +0530 Subject: [PATCH 036/182] Push RPC response serialization down into handlers Rather than serializing in the `invoke` call, we now return a variant (bt, json, or string) from invoke and let the RPC server deal with it. This significantly simplifies the JSON RPC interface because it lets us move json objects around, which is particularly useful for building a json_rpc response and lets us avoid the vector of strings hack that was needed for epee json serialization. This also lets the RPC caller decide what it wants to accept -- so the OMQ RPC can deal with anything (bt, json, binary), while the http json_rpc handler only accepts json, and the http/legacy binary handlers accepts json or strings, but not bt_value. This also fixes a but in the json_rpc handler that failed when given an "id" value that wasn't a string (such as a number). --- src/rpc/core_rpc_server.cpp | 8 +++---- src/rpc/core_rpc_server.h | 3 ++- src/rpc/http_server.cpp | 48 +++++++++++-------------------------- src/rpc/lmq_server.cpp | 13 +++++++++- 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 1de05e0b881..ffb77cafb3d 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -115,7 +115,7 @@ namespace cryptonote::rpc { cmd->is_legacy = std::is_base_of_v; if constexpr (!std::is_base_of_v) { static_assert(!FIXME_has_nested_response_v); - cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> std::string { + cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { RPC rpc{}; try { if (auto body = request.body_view()) { @@ -141,14 +141,14 @@ namespace cryptonote::rpc { rpc.response = json::object(); if (rpc.is_bt()) - return bt_serialize(json_to_bt(std::move(rpc.response))); + return json_to_bt(std::move(rpc.response)); else - return rpc.response.dump(); + return std::move(rpc.response); }; } else { // Legacy binary request; these still use epee serialization, and should be considered // deprecated (tentatively to be removed in Oxen 11). - cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> std::string { + cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { typename RPC::request req{}; std::string_view data; if (auto body = request.body_view()) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b2777ccc4ff..c5f0a7e2527 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -141,9 +141,10 @@ namespace cryptonote::rpc { /// Stores an RPC command callback. These are set up in core_rpc_server.cpp. struct rpc_command { + using result_type = std::variant; // Called with the incoming command data; returns the response body if all goes well, // otherwise throws an exception. - std::string(*invoke)(rpc_request&&, core_rpc_server&); + result_type(*invoke)(rpc_request&&, core_rpc_server&); bool is_public; // callable via restricted RPC bool is_binary; // only callable at /name (for HTTP RPC), and binary data, not JSON. bool is_legacy; // callable at /name (for HTTP RPC), even though it is JSON (for backwards compat). diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index b525d9693f5..7beaeb7bfc4 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -246,9 +246,8 @@ namespace cryptonote::rpc { } }; - // Queues a response for the HTTP thread to handle; the response can be in multiple string pieces - // to be concatenated together. - void queue_response(std::shared_ptr data, std::vector body) + // Queues a response for the HTTP thread to handle + void queue_response(std::shared_ptr data, std::string body) { auto& http = data->http; data->replied = true; @@ -262,24 +261,12 @@ namespace cryptonote::rpc { if (data->http.closing()) res.writeHeader("Connection", "close"); for (const auto& [name, value] : data->extra_headers) res.writeHeader(name, value); - - for (const auto& piece : body) - res.write(piece); - - res.end(); + res.end(body); if (data->http.closing()) res.close(); }); }); } - // Wrapper around the above that takes a single string - void queue_response(std::shared_ptr data, std::string body) - { - std::vector b; - b.push_back(std::move(body)); - queue_response(std::move(data), std::move(b)); - } - void invoke_txpool_hashes_bin(std::shared_ptr data); // Invokes the actual RPC request; this is called (via lokimq) from some random LMQ worker thread, @@ -301,21 +288,20 @@ namespace cryptonote::rpc { if (time_logging) start = std::chrono::steady_clock::now(); - std::vector result; - result.reserve(data.jsonrpc ? 3 : 1); - if (data.jsonrpc) - { - result.emplace_back(R"({"jsonrpc":"2.0","id":)"); - result.back() += data.jsonrpc_id; - result.back() += R"(,"result":)"; - } - int json_error = -32603; std::string json_message = "Internal error"; std::string http_message; + std::string result; try { - result.push_back(data.call->invoke(std::move(data.request), data.core_rpc)); + auto r = data.call->invoke(std::move(data.request), data.core_rpc); + if (data.jsonrpc) + result = nlohmann::json{{"jsonrpc", "2.0"}, {"id", data.jsonrpc_id}, {"result", var::get(std::move(r))}}.dump(); + else if (auto* json = std::get_if(&r)) + result = json->dump(); + else + result = var::get(std::move(r)); + // And throw if we get back a bt_value because we don't accept that at all json_error = 0; } catch (const parse_error& e) { // This isn't really WARNable as it's the client fault; log at info level instead. @@ -344,17 +330,11 @@ namespace cryptonote::rpc { return; } - if (data.jsonrpc) - result.emplace_back("}\n"); - std::string call_duration; if (time_logging) call_duration = " in " + tools::friendly_duration(std::chrono::steady_clock::now() - start); - if (LOG_ENABLED(Info)) { - size_t bytes = 0; - for (const auto& r : result) bytes += r.size(); - MINFO("HTTP RPC " << data.uri << " [" << data.request.context.remote << "] OK (" << bytes << " bytes)" << call_duration); - } + if (LOG_ENABLED(Info)) + MINFO("HTTP RPC " << data.uri << " [" << data.request.context.remote << "] OK (" << result.size() << " bytes)" << call_duration); queue_response(std::move(dataptr), std::move(result)); } diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index ae3af7e1638..2eebd83bde8 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -204,7 +204,18 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog request.body = m.data[0]; try { - m.send_reply(LMQ_OK, call.invoke(std::move(request), rpc_)); + auto result = std::visit([](auto&& v) -> std::string { + using T = decltype(v); + if constexpr (std::is_same_v) + return bt_serialize(std::move(v)); + else if constexpr (std::is_same_v) + return v.dump(); + else { + static_assert(std::is_same_v); + return std::move(v); + } + }, call.invoke(std::move(request), rpc_)); + m.send_reply(LMQ_OK, std::move(result)); return; } catch (const parse_error& e) { // This isn't really WARNable as it's the client fault; log at info level instead. From 09e77919834535284e17bcf0eac923f754b54777 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 16:20:47 +0530 Subject: [PATCH 037/182] Append newline after printing CLI command error --- contrib/epee/include/epee/console_handler.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/epee/include/epee/console_handler.h b/contrib/epee/include/epee/console_handler.h index 50354d844c4..8767b66e1d1 100755 --- a/contrib/epee/include/epee/console_handler.h +++ b/contrib/epee/include/epee/console_handler.h @@ -529,8 +529,7 @@ namespace epee catch (const std::exception &e) { rdln::suspend_readline pause_readline; - std::cout << "Command errored: " << cmd.front() << ", " << e.what(); - } + std::cout << "Command errored: " << cmd.front() << ", " << e.what() << "\n"; } return false; } From 1cd7e5a0890d3edb62f111e86080b0b7bae54f68 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 16:28:50 +0530 Subject: [PATCH 038/182] Update log to fmt format --- src/daemon/rpc_command_executor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 3696947b9cd..194a9c3e2fd 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -731,8 +731,8 @@ bool rpc_command_executor::print_connections() { info["current_download"].get() / 1000., info["avg_upload"].get() / 1000., info["current_upload"].get() / 1000., - (info.value("localhost", false) ? "[LOCALHOST]" : ""), - (info.value("local_ip", false) ? "[LAN]" : "")); + info.value("localhost", false) ? "[LOCALHOST]" : "", + info.value("local_ip", false) ? "[LAN]" : ""); } return true; From d31ce6874e6eb09efc55ecdacfade3216616be7b Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 17:24:15 +0530 Subject: [PATCH 039/182] Move tx pool stats struct out of rpc code It makes no sense for the definition to be there (except that it was for epee serialization purposes), so move it into tx_memory_pool where it belongs, and convert it to json on the fly for the rpc method that wants it. --- src/cryptonote_core/tx_pool.cpp | 27 +++++++++------- src/cryptonote_core/tx_pool.h | 24 ++++++++++++-- src/rpc/core_rpc_server.cpp | 26 +++++++++++----- src/rpc/core_rpc_server_commands_defs.cpp | 38 +++++++++++------------ src/rpc/core_rpc_server_commands_defs.h | 38 ----------------------- 5 files changed, 76 insertions(+), 77 deletions(-) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 8e3b2df38ca..1dc44b79263 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1208,12 +1208,13 @@ namespace cryptonote }, false, include_unrelayed_txes); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_stats(struct rpc::txpool_stats& stats, bool include_unrelayed_txes) const + tx_memory_pool::tx_stats tx_memory_pool::get_transaction_stats(bool include_unrelayed_txes) const { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); + tx_stats stats; const uint64_t now = time(NULL); - std::map agebytes; + std::map> agebytes; stats.txs_total = m_blockchain.get_txpool_tx_count(include_unrelayed_txes); std::vector weights; weights.reserve(stats.txs_total); @@ -1234,8 +1235,8 @@ namespace cryptonote if (meta.last_failed_height) stats.num_failing++; uint64_t age = now - meta.receive_time + (now == meta.receive_time); - agebytes[age].txs++; - agebytes[age].bytes += meta.weight; + agebytes[age].first++; + agebytes[age].second += meta.weight; if (meta.double_spend_seen) ++stats.num_double_spends; return true; @@ -1246,7 +1247,7 @@ namespace cryptonote /* looking for 98th percentile */ size_t end = stats.txs_total * 0.02; uint64_t delta, factor; - std::map::iterator it, i2; + decltype(agebytes.begin()) it; if (end) { /* If enough txs, spread the first 98% of results across @@ -1259,7 +1260,7 @@ namespace cryptonote */ do { --it; - cumulative_num += it->second.txs; + cumulative_num += it->second.first; } while (it != agebytes.begin() && cumulative_num < end); stats.histo_98pc = it->first; factor = 9; @@ -1278,18 +1279,22 @@ namespace cryptonote } if (!delta) delta = 1; - for (i2 = agebytes.begin(); i2 != it; i2++) + auto i2 = agebytes.begin(); + for (; i2 != it; i2++) { size_t i = (i2->first * factor - 1) / delta; - stats.histo[i].txs += i2->second.txs; - stats.histo[i].bytes += i2->second.bytes; + stats.histo[i].first += i2->second.first; + stats.histo[i].second += i2->second.second; } for (; i2 != agebytes.end(); i2++) { - stats.histo[factor].txs += i2->second.txs; - stats.histo[factor].bytes += i2->second.bytes; + auto& h = stats.histo[factor]; + h.first += i2->second.first; + h.second += i2->second.second; } } + + return stats; } //------------------------------------------------------------------ //TODO: investigate whether boolean return is appropriate diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 32d91a236d5..20cd34e6dbf 100755 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -418,14 +418,34 @@ namespace cryptonote */ void get_transaction_backlog(std::vector& backlog, bool include_unrelayed_txes = true) const; + /// Return type of get_transaction_stats() + struct tx_stats + { + uint64_t bytes_total; ///< Total size of all transactions in pool. + uint32_t bytes_min; ///< Min transaction size in pool. + uint32_t bytes_max; ///< Max transaction size in pool. + uint32_t bytes_med; ///< Median transaction size in pool. + uint64_t fee_total; ///< Total fee's in pool in atomic units. + uint64_t oldest; ///< Unix time of the oldest transaction in the pool. + uint32_t txs_total; ///< Total number of transactions. + uint32_t num_failing; ///< Bumber of failing transactions. + uint32_t num_10m; ///< Number of transactions in pool for more than 10 minutes. + uint32_t num_not_relayed; ///< Number of non-relayed transactions. + uint64_t histo_98pc; ///< the time 98% of txes are "younger" than. + std::vector> histo; ///< List of txpool histo [number of txes, size in bytes] pairs. + uint32_t num_double_spends; ///< Number of double spend transactions. + + tx_stats(): bytes_total(0), bytes_min(0), bytes_max(0), bytes_med(0), fee_total(0), oldest(0), txs_total(0), num_failing(0), num_10m(0), num_not_relayed(0), histo_98pc(0), num_double_spends(0) {} + }; + /** * @brief get a summary statistics of all transaction hashes in the pool * - * @param stats return-by-reference the pool statistics * @param include_unrelayed_txes include unrelayed txes in the result * + * @return txpool_stats struct of pool statistics */ - void get_transaction_stats(struct rpc::txpool_stats& stats, bool include_unrelayed_txes = true) const; + tx_stats get_transaction_stats(bool include_unrelayed_txes = true) const; /** * @brief get information about all transactions and key images in the pool diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ffb77cafb3d..427f748607b 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1544,19 +1544,31 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context) + void core_rpc_server::invoke(GET_TRANSACTION_POOL_STATS& stats, rpc_context context) { PERF_TIMER(on_get_transaction_pool_stats); //TODO handle bootstrap daemon // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - rpc::txpool_stats stats; - m_core.get_pool().get_transaction_stats(stats, context.admin); - get_transaction_pool_stats.response["pool_stats"] = stats; - get_transaction_pool_stats.response["status"] = STATUS_OK; - LOG_PRINT_L0(get_transaction_pool_stats.response["status"]); - return; + auto txpool = m_core.get_pool().get_transaction_stats(context.admin); + stats.response["pool_stats"] = json{ + {"bytes_total", txpool.bytes_total}, + {"bytes_min", txpool.bytes_min}, + {"bytes_max", txpool.bytes_max}, + {"bytes_med", txpool.bytes_med}, + {"fee_total", txpool.fee_total}, + {"oldest", txpool.oldest}, + {"txs_total", txpool.txs_total}, + {"num_failing", txpool.num_failing}, + {"num_10m", txpool.num_10m}, + {"num_not_relayed", txpool.num_not_relayed}, + {"histo_98pc", txpool.histo_98pc}, + {"histo", txpool.histo}, + {"num_double_spends", txpool.num_double_spends}}; + + stats.response["status"] = STATUS_OK; + } //------------------------------------------------------------------------------------------------------------------------------ SET_BOOTSTRAP_DAEMON::response core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 35d2369edc1..4ee0869b2d7 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -635,27 +635,27 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(txpool_histo) - KV_SERIALIZE(txs) - KV_SERIALIZE(bytes) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(txpool_histo) +// KV_SERIALIZE(txs) +// KV_SERIALIZE(bytes) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(txpool_stats) - KV_SERIALIZE(bytes_total) - KV_SERIALIZE(bytes_min) - KV_SERIALIZE(bytes_max) - KV_SERIALIZE(bytes_med) - KV_SERIALIZE(fee_total) - KV_SERIALIZE(oldest) - KV_SERIALIZE(txs_total) - KV_SERIALIZE(num_failing) - KV_SERIALIZE(num_10m) - KV_SERIALIZE(num_not_relayed) - KV_SERIALIZE(histo_98pc) - KV_SERIALIZE(histo) - KV_SERIALIZE(num_double_spends) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(txpool_stats) +// KV_SERIALIZE(bytes_total) +// KV_SERIALIZE(bytes_min) +// KV_SERIALIZE(bytes_max) +// KV_SERIALIZE(bytes_med) +// KV_SERIALIZE(fee_total) +// KV_SERIALIZE(oldest) +// KV_SERIALIZE(txs_total) +// KV_SERIALIZE(num_failing) +// KV_SERIALIZE(num_10m) +// KV_SERIALIZE(num_not_relayed) +// KV_SERIALIZE(histo_98pc) +// KV_SERIALIZE(histo) +// KV_SERIALIZE(num_double_spends) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_STATS::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ff101d9c40c..5eb45d0ffe6 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1316,44 +1316,6 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; - BELDEX_RPC_DOC_INTROSPECT - struct txpool_stats - { - uint64_t bytes_total; // Total size of all transactions in pool. - uint32_t bytes_min; // Min transaction size in pool. - uint32_t bytes_max; // Max transaction size in pool. - uint32_t bytes_med; // Median transaction size in pool. - uint64_t fee_total; // Total fee's in pool in atomic units. - uint64_t oldest; // Unix time of the oldest transaction in the pool. - uint32_t txs_total; // Total number of transactions. - uint32_t num_failing; // Bumber of failing transactions. - uint32_t num_10m; // Number of transactions in pool for more than 10 minutes. - uint32_t num_not_relayed; // Number of non-relayed transactions. - uint64_t histo_98pc; // the time 98% of txes are "younger" than. - std::vector histo; // List of txpool histo. - uint32_t num_double_spends; // Number of double spend transactions. - - txpool_stats(): bytes_total(0), bytes_min(0), bytes_max(0), bytes_med(0), fee_total(0), oldest(0), txs_total(0), num_failing(0), num_10m(0), num_not_relayed(0), histo_98pc(0), num_double_spends(0) {} - - KV_MAP_SERIALIZABLE - }; - - // void to_json(nlohmann::json& j, const txpool_stats& txpool) { - // j = nlohmann::json{{"bytes_total", txpool.bytes_total}, - // {"bytes_min", txpool.bytes_min}, - // {"bytes_max", txpool.bytes_max}, - // {"bytes_med", txpool.bytes_med}, - // {"fee_total", txpool.fee_total}, - // {"oldest", txpool.oldest}, - // {"txs_total", txpool.txs_total}, - // {"num_failing", txpool.num_failing}, - // {"num_10m", txpool.num_10m}, - // {"num_not_relayed", txpool.num_not_relayed}, - // {"histo_98pc", txpool.histo_98pc}, - // {"histo", txpool.histo}, - // {"num_double_spends", txpool.num_double_spends}}; - // } - //----------------------------------------------- /// Get the transaction pool statistics. /// From a42a75729e9ffb032ec2d93e7ec2ea58bee89bf4 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 18 Apr 2025 17:44:40 +0530 Subject: [PATCH 040/182] date submodule add and Eviscerate epee timing garpage --- .gitmodules | 3 + contrib/epee/include/epee/misc_os_dependent.h | 112 ---------------- contrib/epee/include/epee/net/levin_base.h | 1 - .../epee/net/levin_protocol_handler_async.h | 80 +++--------- contrib/epee/include/epee/profile_tools.h | 121 ------------------ .../epee/storages/levin_abstract_invoke2.h | 2 +- contrib/epee/src/CMakeLists.txt | 1 + contrib/epee/src/mlog.cpp | 22 +--- external/CMakeLists.txt | 1 + external/date | 1 + src/blockchain_db/blockchain_db.cpp | 37 +++--- src/blockchain_db/blockchain_db.h | 10 +- src/blockchain_db/lmdb/db_lmdb.cpp | 48 +++---- src/blockchain_utilities/CMakeLists.txt | 2 +- src/blockchain_utilities/blockchain_stats.cpp | 28 ++-- src/common/CMakeLists.txt | 1 + src/common/perf_timer.cpp | 60 ++++----- src/common/perf_timer.h | 11 +- src/common/util.cpp | 15 +-- src/common/util.h | 2 +- src/cryptonote_basic/miner.cpp | 1 - src/cryptonote_core/blockchain.cpp | 118 ++++++++--------- src/cryptonote_core/blockchain.h | 4 +- src/cryptonote_core/master_node_list.cpp | 12 +- .../cryptonote_protocol_handler.h | 2 +- .../cryptonote_protocol_handler.inl | 43 ++++--- src/daemon/rpc_command_executor.cpp | 17 +-- 27 files changed, 212 insertions(+), 543 deletions(-) delete mode 100755 contrib/epee/include/epee/misc_os_dependent.h delete mode 100755 contrib/epee/include/epee/profile_tools.h create mode 160000 external/date diff --git a/.gitmodules b/.gitmodules index 4f5f191779b..f5ffd522011 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "external/nlohmann-json"] path = external/nlohmann-json url = https://github.com/nlohmann/json.git +[submodule "external/date"] + path = external/date + url = https://github.com/HowardHinnant/date.git diff --git a/contrib/epee/include/epee/misc_os_dependent.h b/contrib/epee/include/epee/misc_os_dependent.h deleted file mode 100755 index 7ad63ad2a25..00000000000 --- a/contrib/epee/include/epee/misc_os_dependent.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * 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. -// * Neither the name of the Andrey N. Sabelnikov 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 OWNER 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. -// -#ifdef _WIN32 -#include -#endif - -#ifdef WIN32 - #ifndef WIN32_LEAN_AND_MEAN - #define WIN32_LEAN_AND_MEAN - #endif - - //#ifdef _WIN32_WINNT - // #undef _WIN32_WINNT - // #define _WIN32_WINNT 0x0600 - //#endif - - -#include -#endif - -#ifdef __MACH__ -#include -#include -#endif - -#include -#include - -#pragma once -namespace epee -{ -namespace misc_utils -{ - - inline uint64_t get_ns_count() - { -#if defined(_MSC_VER) - return ::GetTickCount64() * 1000000; -#elif defined(WIN32) - static LARGE_INTEGER pcfreq = {0}; - LARGE_INTEGER ticks; - if (!pcfreq.QuadPart) - QueryPerformanceFrequency(&pcfreq); - QueryPerformanceCounter(&ticks); - ticks.QuadPart *= 1000000000; /* we want nsec */ - return ticks.QuadPart / pcfreq.QuadPart; -#elif defined(__MACH__) - clock_serv_t cclock; - mach_timespec_t mts; - - host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - - return ((uint64_t)mts.tv_sec * 1000000000) + (mts.tv_nsec); -#else - struct timespec ts; - if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { - return 0; - } - return ((uint64_t)ts.tv_sec * 1000000000) + (ts.tv_nsec); -#endif - } - - inline uint64_t get_tick_count() - { - return get_ns_count() / 1000000; - } - - - inline std::string get_thread_string_id() - { -#if defined(_WIN32) - return boost::lexical_cast(GetCurrentThreadId()); -#elif defined(__GNUC__) - return boost::lexical_cast(pthread_self()); -#endif - } - - inline bool get_gmt_time(time_t t, struct tm &tm) - { -#ifdef _WIN32 - return gmtime_s(&tm, &t); -#else - return gmtime_r(&t, &tm); -#endif - } -} -} diff --git a/contrib/epee/include/epee/net/levin_base.h b/contrib/epee/include/epee/net/levin_base.h index 98ba2f96003..6de37462258 100755 --- a/contrib/epee/include/epee/net/levin_base.h +++ b/contrib/epee/include/epee/net/levin_base.h @@ -72,7 +72,6 @@ namespace levin #pragma pack(pop) -#define LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED 0 #define LEVIN_DEFAULT_MAX_PACKET_SIZE 100000000 //100MB by default #define LEVIN_PACKET_REQUEST 0x00000001 diff --git a/contrib/epee/include/epee/net/levin_protocol_handler_async.h b/contrib/epee/include/epee/net/levin_protocol_handler_async.h index a069ac6a2f8..2d10e60dece 100755 --- a/contrib/epee/include/epee/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/epee/net/levin_protocol_handler_async.h @@ -32,11 +32,11 @@ #include #include +#include #include "levin_base.h" #include "buffer.h" #include "../scope_leaver.h" -#include "../misc_os_dependent.h" #include "../int-util.h" #include @@ -90,11 +90,11 @@ class async_protocol_handler_config public: typedef t_connection_context connection_context; uint64_t m_max_packet_size; - uint64_t m_invoke_timeout; + std::chrono::nanoseconds m_invoke_timeout; int invoke(int command, const epee::span in_buff, std::string& buff_out, boost::uuids::uuid connection_id); template - int invoke_async(int command, const epee::span in_buff, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED); + int invoke_async(int command, const epee::span in_buff, boost::uuids::uuid connection_id, const callback_t &cb, std::chrono::nanoseconds timeout = 0s); int notify(int command, const epee::span in_buff, boost::uuids::uuid connection_id); int send(epee::shared_sv message, const boost::uuids::uuid& connection_id); @@ -110,7 +110,7 @@ class async_protocol_handler_config size_t get_in_connections_count(); void set_handler(levin_commands_handler* handler, void (*destroy)(levin_commands_handler*) = NULL); - async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE), m_invoke_timeout(LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE), m_invoke_timeout(0ns) {} ~async_protocol_handler_config() { set_handler(NULL, NULL); } void del_out_connections(size_t count); @@ -136,11 +136,6 @@ class async_protocol_handler if(!m_pservice_endpoint->do_send(shared_sv{std::move(data)})) return false; - MDEBUG(m_connection_context << "LEVIN_PACKET_SENT. [len=" << head.m_cb - << ", flags" << head.m_flags - << ", r?=" << head.m_have_to_return_data - <<", cmd = " << head.m_command - << ", ver=" << head.m_protocol_version); return true; } @@ -189,19 +184,17 @@ class async_protocol_handler template struct anvoke_handler: invoke_response_handler_base { - anvoke_handler(const callback_t& cb, uint64_t timeout, async_protocol_handler& con, int command) + anvoke_handler(const callback_t& cb, std::chrono::milliseconds timeout, async_protocol_handler& con, int command) :m_cb(cb), m_timeout(timeout), m_con(con), m_timer(con.m_pservice_endpoint->get_io_service()), m_timer_started(false), m_cancel_timer_called(false), m_timer_cancelled(false), m_command(command) { if(m_con.start_outer_call()) { - MDEBUG(con.get_context_ref() << "anvoke_handler, timeout: " << timeout); m_timer.expires_from_now(std::chrono::milliseconds(timeout)); m_timer.async_wait([&con, command, cb, timeout](const boost::system::error_code& ec) { if(ec == boost::asio::error::operation_aborted) return; - MINFO(con.get_context_ref() << "Timeout on invoke operation happened, command: " << command << " timeout: " << timeout); epee::span fake; cb(LEVIN_ERROR_CONNECTION_TIMEDOUT, fake, con.get_context_ref()); con.close(); @@ -218,7 +211,7 @@ class async_protocol_handler bool m_timer_started; bool m_cancel_timer_called; bool m_timer_cancelled; - uint64_t m_timeout; + std::chrono::milliseconds m_timeout; int m_command; virtual bool handle(int res, const epee::span buff, typename async_protocol_handler::connection_context& context) { @@ -257,15 +250,13 @@ class async_protocol_handler if (!m_cancel_timer_called && m_timer.cancel(ignored_ec) > 0) { callback_t& cb = m_cb; - uint64_t timeout = m_timeout; async_protocol_handler& con = m_con; int command = m_command; - m_timer.expires_from_now(std::chrono::milliseconds(m_timeout)); - m_timer.async_wait([&con, cb, command, timeout](const boost::system::error_code& ec) + m_timer.expires_from_now(m_timeout); + m_timer.async_wait([&con, cb, command, timeout=m_timeout](const boost::system::error_code& ec) { if(ec == boost::asio::error::operation_aborted) return; - MINFO(con.get_context_ref() << "Timeout on invoke operation happened, command: " << command << " timeout: " << timeout); epee::span fake; cb(LEVIN_ERROR_CONNECTION_TIMEDOUT, fake, con.get_context_ref()); con.close(); @@ -278,12 +269,12 @@ class async_protocol_handler std::list > m_invoke_response_handlers; template - bool add_invoke_response_handler(const callback_t &cb, uint64_t timeout, async_protocol_handler& con, int command) + bool add_invoke_response_handler(const callback_t &cb, std::chrono::nanoseconds timeout_ns, async_protocol_handler& con, int command) { + auto timeout = std::chrono::duration_cast(timeout_ns); std::lock_guard lock{m_invoke_response_handlers_lock}; if (m_protocol_released) { - MERROR("Adding response handler to a released object"); return false; } std::shared_ptr handler(std::make_shared>(cb, timeout, con, command)); @@ -326,20 +317,14 @@ class async_protocol_handler { std::this_thread::sleep_for(100ms); } - CHECK_AND_ASSERT_MES_NO_RET(0 == m_wait_count, "Failed to wait for operation completion. m_wait_count = " << m_wait_count); - - MTRACE(m_connection_context << "~async_protocol_handler()"); - } catch (...) { /* ignore */ } } bool start_outer_call() { - MTRACE(m_connection_context << "[levin_protocol] -->> start_outer_call"); if(!m_pservice_endpoint->add_ref()) { - MERROR(m_connection_context << "[levin_protocol] -->> start_outer_call failed"); return false; } m_wait_count++; @@ -347,7 +332,6 @@ class async_protocol_handler } bool finish_outer_call() { - MTRACE(m_connection_context << "[levin_protocol] <<-- finish_outer_call"); m_wait_count--; m_pservice_endpoint->release(); return true; @@ -404,7 +388,6 @@ class async_protocol_handler if(!m_config.m_pcommands_handler) { - MERROR(m_connection_context << "Commands handler not set!"); return false; } @@ -415,9 +398,6 @@ class async_protocol_handler // flipped to subtraction; prevent overflow since m_max_packet_size is variable and public if(cb > m_config.m_max_packet_size - m_cache_in_buffer.size() - m_fragment_buffer.size()) { - MWARNING(m_connection_context << "Maximum packet size exceed!, m_max_packet_size = " << m_config.m_max_packet_size - << ", packet received " << m_cache_in_buffer.size() + cb - << ", connection will be closed."); return false; } @@ -440,7 +420,6 @@ class async_protocol_handler //async call scenario std::shared_ptr response_handler = m_invoke_response_handlers.front(); response_handler->reset_timer(); - MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb); } } break; @@ -468,7 +447,6 @@ class async_protocol_handler if (m_fragment_buffer.size() < sizeof(bucket_head2)) { - MERROR(m_connection_context << "Fragmented data too small for levin header"); return false; } @@ -480,12 +458,6 @@ class async_protocol_handler bool is_response = (m_oponent_protocol_ver == LEVIN_PROTOCOL_VER_1 && m_current_head.m_flags&LEVIN_PACKET_RESPONSE); - MDEBUG(m_connection_context << "LEVIN_PACKET_RECEIVED. [len=" << m_current_head.m_cb - << ", flags" << m_current_head.m_flags - << ", r?=" << m_current_head.m_have_to_return_data - <<", cmd = " << m_current_head.m_command - << ", v=" << m_current_head.m_protocol_version); - if(is_response) {//response to some invoke @@ -508,7 +480,6 @@ class async_protocol_handler //use sync call scenario if(m_wait_count == 0 && m_close_called == 0) { - MERROR(m_connection_context << "no active invoke when response came, wtf?"); return false; }else { @@ -534,12 +505,6 @@ class async_protocol_handler if(!m_pservice_endpoint->do_send(shared_sv{std::move(return_buff)})) return false; - - MDEBUG(m_connection_context << "LEVIN_PACKET_SENT. [len=" << head.m_cb - << ", flags" << head.m_flags - << ", r?=" << head.m_have_to_return_data - <<", cmd = " << head.m_command - << ", ver=" << head.m_protocol_version); } else m_config.m_pcommands_handler->notify(m_current_head.m_command, buff_to_invoke, m_connection_context); @@ -558,7 +523,6 @@ class async_protocol_handler { if(m_cache_in_buffer.size() >= sizeof(uint64_t) && *((uint64_t*)m_cache_in_buffer.span(8).data()) != SWAP64LE(LEVIN_SIGNATURE)) { - MWARNING(m_connection_context << "Signature mismatch, connection will be closed"); return false; } is_continue = false; @@ -578,7 +542,6 @@ class async_protocol_handler #endif if(LEVIN_SIGNATURE != phead.m_signature) { - LOG_ERROR_CC(m_connection_context, "Signature mismatch, connection will be closed"); return false; } m_current_head = phead; @@ -588,15 +551,11 @@ class async_protocol_handler m_oponent_protocol_ver = m_current_head.m_protocol_version; if(m_current_head.m_cb > m_config.m_max_packet_size) { - LOG_ERROR_CC(m_connection_context, "Maximum packet size exceed!, m_max_packet_size = " << m_config.m_max_packet_size - << ", packet header received " << m_current_head.m_cb - << ", connection will be closed."); return false; } } break; default: - LOG_ERROR_CC(m_connection_context, "Undefined state in levin_server_impl::connection_handler, m_state=" << m_state); return false; } } @@ -615,12 +574,12 @@ class async_protocol_handler } template - bool async_invoke(int command, const epee::span in_buff, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + bool async_invoke(int command, const epee::span in_buff, const callback_t &cb, std::chrono::nanoseconds timeout = 0ns) { auto scope_exit_handler = misc_utils::create_scope_leave_handler( [this] { return finish_outer_call(); }); - if(timeout == LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + if(timeout == 0ns) timeout = m_config.m_invoke_timeout; int err_code = LEVIN_OK; @@ -646,7 +605,6 @@ class async_protocol_handler if(!send_message(command, in_buff, LEVIN_PACKET_REQUEST, true)) { - LOG_ERROR_CC(m_connection_context, "Failed to do_send"); err_code = LEVIN_ERROR_CONNECTION; break; } @@ -687,11 +645,10 @@ class async_protocol_handler if (!send_message(command, in_buff, LEVIN_PACKET_REQUEST, true)) { - LOG_ERROR_CC(m_connection_context, "Failed to send request"); return LEVIN_ERROR_CONNECTION; } - uint64_t ticks_start = misc_utils::get_tick_count(); + auto start = std::chrono::steady_clock::now(); size_t prev_size = 0; while(!m_invoke_buf_ready && !m_deletion_initiated && !m_protocol_released) @@ -699,11 +656,10 @@ class async_protocol_handler if(m_cache_in_buffer.size() - prev_size >= MIN_BYTES_WANTED) { prev_size = m_cache_in_buffer.size(); - ticks_start = misc_utils::get_tick_count(); + start = std::chrono::steady_clock::now(); } - if(misc_utils::get_tick_count() - ticks_start > m_config.m_invoke_timeout) + if (std::chrono::steady_clock::now() > start + m_config.m_invoke_timeout) { - MWARNING(m_connection_context << "invoke timeout (" << m_config.m_invoke_timeout << "), closing connection "); close(); return LEVIN_ERROR_CONNECTION_TIMEDOUT; } @@ -736,7 +692,6 @@ class async_protocol_handler if (!send_message(command, in_buff, LEVIN_PACKET_REQUEST, false)) { - LOG_ERROR_CC(m_connection_context, "Failed to send notify message"); return -1; } @@ -760,11 +715,9 @@ class async_protocol_handler const std::size_t length = message.view.size(); if (!m_pservice_endpoint->do_send(std::move(message))) { - LOG_ERROR_CC(m_connection_context, "Failed to send message, dropping it"); return -1; } - MDEBUG(m_connection_context << "LEVIN_PACKET_SENT. [len=" << (length - sizeof(bucket_head2)) << ", r?=0]"); return 1; } //------------------------------------------------------------------------------------------ @@ -810,7 +763,6 @@ void async_protocol_handler_config::delete_connections(siz } catch (const std::out_of_range &e) { - MWARNING("Connection not found in m_connects, continuing"); } --count; } @@ -866,7 +818,7 @@ int async_protocol_handler_config::invoke(int command, con } //------------------------------------------------------------------------------------------ template template -int async_protocol_handler_config::invoke_async(int command, const epee::span in_buff, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout) +int async_protocol_handler_config::invoke_async(int command, const epee::span in_buff, boost::uuids::uuid connection_id, const callback_t &cb, std::chrono::nanoseconds timeout) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); diff --git a/contrib/epee/include/epee/profile_tools.h b/contrib/epee/include/epee/profile_tools.h deleted file mode 100755 index b7cfe05c96f..00000000000 --- a/contrib/epee/include/epee/profile_tools.h +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * 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. -// * Neither the name of the Andrey N. Sabelnikov 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 OWNER 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. -// - - -#ifndef _PROFILE_TOOLS_H_ -#define _PROFILE_TOOLS_H_ - -#include -#include "misc_os_dependent.h" - -namespace epee -{ - -#ifdef ENABLE_PROFILING -#define PROFILE_FUNC(immortal_ptr_str) static profile_tools::local_call_account lcl_acc(immortal_ptr_str); \ - profile_tools::call_frame cf(lcl_acc); - -#define PROFILE_FUNC_SECOND(immortal_ptr_str) static profile_tools::local_call_account lcl_acc2(immortal_ptr_str); \ - profile_tools::call_frame cf2(lcl_acc2); - -#define PROFILE_FUNC_THIRD(immortal_ptr_str) static profile_tools::local_call_account lcl_acc3(immortal_ptr_str); \ - profile_tools::call_frame cf3(lcl_acc3); - -#define PROFILE_FUNC_ACC(acc) \ - profile_tools::call_frame cf(acc); - - -#else -#define PROFILE_FUNC(immortal_ptr_str) -#define PROFILE_FUNC_SECOND(immortal_ptr_str) -#define PROFILE_FUNC_THIRD(immortal_ptr_str) -#endif - -#define START_WAY_POINTS() uint64_t _____way_point_time = epee::misc_utils::get_tick_count(); -#define WAY_POINT(name) {uint64_t delta = epee::misc_utils::get_tick_count()-_____way_point_time; MDEBUG("Way point " << name << ": " << delta);_____way_point_time = misc_utils::get_tick_count();} -#define WAY_POINT2(name, avrg_obj) {uint64_t delta = epee::misc_utils::get_tick_count()-_____way_point_time; avrg_obj.push(delta); MDEBUG("Way point " << name << ": " << delta);_____way_point_time = misc_utils::get_tick_count();} - - -#define TIME_MEASURE_START(var_name) uint64_t var_name = epee::misc_utils::get_tick_count(); -#define TIME_MEASURE_PAUSE(var_name) var_name = epee::misc_utils::get_tick_count() - var_name; -#define TIME_MEASURE_RESTART(var_name) var_name = epee::misc_utils::get_tick_count() - var_name; -#define TIME_MEASURE_FINISH(var_name) var_name = epee::misc_utils::get_tick_count() - var_name; - -#define TIME_MEASURE_NS_START(var_name) uint64_t var_name = epee::misc_utils::get_ns_count(); -#define TIME_MEASURE_NS_PAUSE(var_name) var_name = epee::misc_utils::get_ns_count() - var_name; -#define TIME_MEASURE_NS_RESTART(var_name) var_name = epee::misc_utils::get_ns_count() - var_name; -#define TIME_MEASURE_NS_FINISH(var_name) var_name = epee::misc_utils::get_ns_count() - var_name; - -namespace profile_tools -{ - struct local_call_account - { - local_call_account(const char* pstr):m_count_of_call(0), m_summary_time_used(0),m_pname(pstr) - {} - ~local_call_account() - { - MINFO("PROFILE "<( - std::chrono::duration_cast(elapsed).count()); - - //::QueryPerformanceCounter((LARGE_INTEGER *)&ret_time); - //m_call_time = (ret_time-m_call_time)/1000; - m_cc.m_summary_time_used += useconds_used; - } - - private: - local_call_account& m_cc; - std::chrono::steady_clock::time_point m_call_time; - }; - - -} -} - - -#endif //_PROFILE_TOOLS_H_ diff --git a/contrib/epee/include/epee/storages/levin_abstract_invoke2.h b/contrib/epee/include/epee/storages/levin_abstract_invoke2.h index cd3ac4cbf94..c3bfa228e2d 100755 --- a/contrib/epee/include/epee/storages/levin_abstract_invoke2.h +++ b/contrib/epee/include/epee/storages/levin_abstract_invoke2.h @@ -108,7 +108,7 @@ namespace epee } template - bool async_invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + bool async_invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, std::chrono::nanoseconds inv_timeout = 0ns) { typename serialization::portable_storage stg; const_cast(out_struct).store(stg);//TODO: add true const support to searilzation diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 7770bcc7896..3ea86391e50 100755 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -69,4 +69,5 @@ target_link_libraries(epee PRIVATE filesystem Boost::thread + date::date extra) diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index b2ac4cc83b7..6ca05d03521 100755 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -28,6 +28,8 @@ #ifndef _MLOG_H_ #define _MLOG_H_ +#include +#include #ifdef _WIN32 #include #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING @@ -39,7 +41,6 @@ #include #include #include "epee/string_tools.h" -#include "epee/misc_os_dependent.h" #include "epee/misc_log_ex.h" #ifndef USE_GHC_FILESYSTEM @@ -67,23 +68,6 @@ namespace fs = ghc::filesystem; using namespace epee; -static std::string generate_log_filename(const char *base) -{ - std::string filename(base); - static unsigned int fallback_counter = 0; - char tmp[200]; - struct tm tm; - time_t now = time(NULL); - if (!epee::misc_utils::get_gmt_time(now, tm)) - snprintf(tmp, sizeof(tmp), "part-%u", ++fallback_counter); - else - strftime(tmp, sizeof(tmp), "%Y-%m-%d-%H-%M-%S", &tm); - tmp[sizeof(tmp) - 1] = 0; - filename += "-"; - filename += tmp; - return filename; -} - std::string mlog_get_default_log_path(const char *default_filename) { std::string process_name = epee::string_tools::get_current_module_name(); @@ -179,7 +163,7 @@ void mlog_configure(const std::string &filename_base, bool console, const std::s el::Loggers::addFlag(el::LoggingFlag::ColoredTerminalOutput); el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck); el::Helpers::installPreRollOutCallback([filename_base, max_log_files](const char *name, size_t){ - std::string rname = generate_log_filename(filename_base.c_str()); + std::string rname = filename_base + "-" + date::format("%Y-%m-%d-%H-%M-%S", std::chrono::system_clock::now()); int ret = rename(name, rname.c_str()); if (ret < 0) { diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 9a271534a5f..e9205cf8f21 100755 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -63,6 +63,7 @@ add_subdirectory(db_drivers) add_subdirectory(easylogging++ easyloggingpp) add_subdirectory(randomx EXCLUDE_FROM_ALL) add_subdirectory(fmt) +add_subdirectory(date EXCLUDE_FROM_ALL) set(JSON_MultipleHeaders ON CACHE BOOL "") # Allows multi-header nlohmann use add_subdirectory(nlohmann-json) diff --git a/external/date b/external/date new file mode 160000 index 00000000000..5bdb7e6f31f --- /dev/null +++ b/external/date @@ -0,0 +1 @@ +Subproject commit 5bdb7e6f31fac909c090a46dbd9fea27b6e609a4 diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 6f623b7d32e..26b8e5eaed8 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -27,6 +27,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "common/string_util.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_core/master_node_rules.h" #include "checkpoints/checkpoints.h" @@ -38,6 +39,7 @@ #include "common/hex.h" #include "lmdb/db_lmdb.h" +#include #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "blockchain.db" @@ -185,16 +187,15 @@ uint64_t BlockchainDB::add_block( const std::pair& blck if (blk.tx_hashes.size() != txs.size()) throw std::runtime_error("Inconsistent tx/hashes sizes"); - TIME_MEASURE_START(time1); + auto started = std::chrono::steady_clock::now(); crypto::hash blk_hash = get_block_hash(blk); - TIME_MEASURE_FINISH(time1); - time_blk_hash += time1; + time_blk_hash += std::chrono::steady_clock::now() - started; uint64_t prev_height = height(); // call out to add the transactions - time1 = epee::misc_utils::get_tick_count(); + started = std::chrono::steady_clock::now(); uint64_t num_rct_outs = 0; add_transaction(blk_hash, std::make_pair(blk.miner_tx, tx_to_blob(blk.miner_tx))); @@ -214,14 +215,12 @@ uint64_t BlockchainDB::add_block( const std::pair& blck } ++tx_i; } - TIME_MEASURE_FINISH(time1); - time_add_transaction += time1; + time_add_transaction += std::chrono::steady_clock::now() - started; // call out to subclass implementation to add the block & metadata - time1 = epee::misc_utils::get_tick_count(); + started = std::chrono::steady_clock::now(); add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, blk_hash); - TIME_MEASURE_FINISH(time1); - time_add_block1 += time1; + time_add_block1 += std::chrono::steady_clock::now() - started; ++num_calls; @@ -321,11 +320,11 @@ transaction BlockchainDB::get_pruned_tx(const crypto::hash& h) const void BlockchainDB::reset_stats() { num_calls = 0; - time_blk_hash = 0; - time_tx_exists = 0; - time_add_block1 = 0; - time_add_transaction = 0; - time_commit1 = 0; + time_blk_hash = 0ns; + time_tx_exists = 0ns; + time_add_block1 = 0ns; + time_add_transaction = 0ns; + time_commit1 = 0ns; } void BlockchainDB::show_stats() @@ -333,11 +332,11 @@ void BlockchainDB::show_stats() LOG_PRINT_L1("\n" << "*********************************\n" << "num_calls: " << num_calls << "\n" - << "time_blk_hash: " << time_blk_hash << "ms\n" - << "time_tx_exists: " << time_tx_exists << "ms\n" - << "time_add_block1: " << time_add_block1 << "ms\n" - << "time_add_transaction: " << time_add_transaction << "ms\n" - << "time_commit1: " << time_commit1 << "ms\n" + << "time_blk_hash: " << tools::friendly_duration(time_blk_hash) << "\n" + << "time_tx_exists: " << tools::friendly_duration(time_tx_exists) << "\n" + << "time_add_block1: " << tools::friendly_duration(time_add_block1) << "\n" + << "time_add_transaction: " << tools::friendly_duration(time_add_transaction) << "\n" + << "time_commit1: " << tools::friendly_duration(time_commit1) << "\n" << "*********************************\n" ); } diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 6dad31078ab..d0d50e6957a 100755 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -524,9 +524,9 @@ class BlockchainDB void remove_transaction(const crypto::hash& tx_hash); uint64_t num_calls = 0; //!< a performance metric - uint64_t time_blk_hash = 0; //!< a performance metric - uint64_t time_add_block1 = 0; //!< a performance metric - uint64_t time_add_transaction = 0; //!< a performance metric + std::chrono::nanoseconds time_blk_hash = 0ns; //!< a performance metric + std::chrono::nanoseconds time_add_block1 = 0ns; //!< a performance metric + std::chrono::nanoseconds time_add_transaction = 0ns; //!< a performance metric protected: @@ -544,8 +544,8 @@ class BlockchainDB */ void add_transaction(const crypto::hash& blk_hash, const std::pair& tx, const crypto::hash* tx_hash_ptr = NULL, const crypto::hash* tx_prunable_hash_ptr = NULL); - mutable uint64_t time_tx_exists = 0; //!< a performance metric - uint64_t time_commit1 = 0; //!< a performance metric + mutable std::chrono::nanoseconds time_tx_exists = 0ns; //!< a performance metric + std::chrono::nanoseconds time_commit1 = 0ns; //!< a performance metric bool m_auto_remove_logs = true; //!< whether or not to automatically remove old logs public: diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index fb854026b54..ce05175a910 100755 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -31,11 +31,13 @@ #include #include #include +#include #include #include #include #include +#include "common/string_util.h" #include "cryptonote_basic/hardfork.h" #include "epee/string_tools.h" #include "common/file.h" @@ -2058,7 +2060,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) throw0(DB_ERROR("Pruning seed not in range")); check_open(); - TIME_MEASURE_START(t); + auto t = std::chrono::steady_clock::now(); size_t n_total_records = 0, n_prunable_records = 0, n_pruned_records = 0, commit_counter = 0; uint64_t n_bytes = 0; @@ -2083,7 +2085,6 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) if (mode != prune_mode_prune) { txn.abort(); - TIME_MEASURE_FINISH(t); MDEBUG("Pruning not enabled, nothing to do"); return true; } @@ -2321,10 +2322,8 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) txn.commit(); - TIME_MEASURE_FINISH(t); - MINFO((mode == prune_mode_check ? "Checked" : "Pruned") << " blockchain in " << - t << " ms: " << (n_bytes/1024.0f/1024.0f) << " MB (" << db_bytes/1024.0f/1024.0f << " MB) pruned in " << + tools::friendly_duration(std::chrono::steady_clock::now() - t) << ": " << (n_bytes/1024.0f/1024.0f) << " MB (" << db_bytes/1024.0f/1024.0f << " MB) pruned in " << n_pruned_records << " records (" << pages0 - pages1 << "/" << pages0 << " " << db_stats.ms_psize << " byte pages), " << n_prunable_records << "/" << n_total_records << " pruned records"); return true; @@ -3048,15 +3047,14 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h) const MDB_val_set(key, h); bool tx_found = false; - TIME_MEASURE_START(time1); + auto time1 = std::chrono::steady_clock::now(); auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &key, MDB_GET_BOTH); if (get_result == 0) tx_found = true; else if (get_result != MDB_NOTFOUND) throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction index from hash ") + tools::type_to_hex(h) + ": ", get_result).c_str())); - TIME_MEASURE_FINISH(time1); - time_tx_exists += time1; + time_tx_exists += std::chrono::steady_clock::now() - time1; if (! tx_found) { @@ -3076,10 +3074,9 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h, uint64_t& tx_id) const MDB_val_set(v, h); - TIME_MEASURE_START(time1); + auto time1 = std::chrono::steady_clock::now(); auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); - TIME_MEASURE_FINISH(time1); - time_tx_exists += time1; + time_tx_exists += std::chrono::steady_clock::now() - time1; if (!get_result) { txindex *tip = (txindex *)v.mv_data; tx_id = tip->data.tx_id; @@ -3725,10 +3722,9 @@ void BlockchainLMDB::batch_commit() check_open(); - TIME_MEASURE_START(time1); + auto time1 = std::chrono::steady_clock::now(); m_write_txn->commit(); - TIME_MEASURE_FINISH(time1); - time_commit1 += time1; + time_commit1 += std::chrono::steady_clock::now() - time1; m_write_txn = nullptr; delete m_write_batch_txn; @@ -3758,12 +3754,11 @@ void BlockchainLMDB::batch_stop() throw1(DB_ERROR("batch transaction owned by other thread")); check_open(); - TIME_MEASURE_START(time1); + auto time1 = std::chrono::steady_clock::now(); try { m_write_txn->commit(); - TIME_MEASURE_FINISH(time1); - time_commit1 += time1; + time_commit1 += std::chrono::steady_clock::now() - time1; cleanup_batch(); } catch (const std::exception &e) @@ -3898,16 +3893,15 @@ void BlockchainLMDB::block_wtxn_stop() throw0(DB_ERROR_TXN_START((std::string("Attempted to stop write txn from the wrong thread in ")+__FUNCTION__).c_str())); { if (! m_batch_active) - { - TIME_MEASURE_START(time1); + { + auto time1 = std::chrono::steady_clock::now(); m_write_txn->commit(); - TIME_MEASURE_FINISH(time1); - time_commit1 += time1; + time_commit1 += std::chrono::steady_clock::now() - time1; delete m_write_txn; m_write_txn = nullptr; memset(&m_wcursors, 0, sizeof(m_wcursors)); - } + } } } @@ -4242,7 +4236,7 @@ void BlockchainLMDB::get_output_key(const epee::span &amounts, c throw0(DB_ERROR("Invalid sizes of amounts and offets")); LOG_PRINT_L3("BlockchainLMDB::" << __func__); - TIME_MEASURE_START(db3); + auto db3 = std::chrono::steady_clock::now(); check_open(); outputs.clear(); outputs.reserve(offsets.size()); @@ -4285,8 +4279,7 @@ void BlockchainLMDB::get_output_key(const epee::span &amounts, c } } - TIME_MEASURE_FINISH(db3); - LOG_PRINT_L3("db3: " << db3); + LOG_PRINT_L3("db3: " << tools::friendly_duration(std::chrono::steady_clock::now() - db3)); } void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std::vector &offsets, std::vector &indices) const @@ -4316,13 +4309,12 @@ void BlockchainLMDB::get_output_tx_and_index(const uint64_t& amount, const std:: tx_indices.push_back(okp->output_id); } - TIME_MEASURE_START(db3); + auto db3 = std::chrono::steady_clock::now(); if(tx_indices.size() > 0) { get_output_tx_and_index_from_global(tx_indices, indices); } - TIME_MEASURE_FINISH(db3); - LOG_PRINT_L3("db3: " << db3); + LOG_PRINT_L3("db3: " << tools::friendly_duration(std::chrono::steady_clock::now() - db3)); } std::map> BlockchainLMDB::get_output_histogram(const std::vector &amounts, bool unlocked, uint64_t recent_cutoff, uint64_t min_count,cryptonote::network_type nettype) const diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index d57d6a9658b..09fe1e931e8 100755 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -87,7 +87,7 @@ target_link_libraries(blockchain_depth PRIVATE blockchain_tools_common_libs) beldex_add_executable(blockchain_stats "beldex-blockchain-stats" blockchain_stats.cpp ) -target_link_libraries(blockchain_stats PRIVATE blockchain_tools_common_libs) +target_link_libraries(blockchain_stats PRIVATE blockchain_tools_common_libs date::date) # TODO(beldex): Blockchain pruning not supported in Beldex yet # beldex_add_executable(blockchain_prune_known_spent_data "beldex-blockchain-prune-known-spent-data" diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 4de46d3a6d9..ee0a9591cd6 100755 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -27,6 +27,9 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include +#include + #include "common/command_line.h" #include "common/varint.h" #include "common/signal_handler.h" @@ -35,7 +38,6 @@ #include "blockchain_objects.h" #include "blockchain_db/blockchain_db.h" #include "version.h" -#include "epee/misc_os_dependent.h" #include "cryptonote_core/uptime_proof.h" #undef BELDEX_DEFAULT_LOG_CATEGORY @@ -193,7 +195,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' } std::cout << "\n"; - struct tm prevtm = {0}, currtm; + std::optional prev_ts; uint64_t prevsz = 0, currsz = 0; uint64_t prevtxs = 0, currtxs = 0; uint64_t currblks = 0; @@ -214,20 +216,20 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' LOG_PRINT_L0("Bad block from db"); return 1; } - time_t tt = blk.timestamp; - char timebuf[64]; - epee::misc_utils::get_gmt_time(tt, currtm); - if (!prevtm.tm_year) - prevtm = currtm; + auto ts = std::chrono::system_clock::from_time_t(blk.timestamp); + using namespace date; + year_month_day curr_date{floor(ts)}; + if (!prev_ts) + prev_ts = ts; + year_month_day prev_date{floor(*prev_ts)}; // catch change of day - if (currtm.tm_mday > prevtm.tm_mday || (currtm.tm_mday == 1 && prevtm.tm_mday > 27)) + if (curr_date.day() > prev_date.day() || (curr_date.day() == day{1} && prev_date.day() > day{27})) { // check for timestamp fudging around month ends - if (prevtm.tm_mday == 1 && currtm.tm_mday > 27) + if (curr_date.day() == day{1} && prev_date.day() > day{27}) goto skip; - strftime(timebuf, sizeof(timebuf), "%Y-%m-%d", &prevtm); - prevtm = currtm; - std::cout << timebuf << "\t" << currblks << "\t" << h << "\t" << currtxs << "\t" << prevtxs + currtxs << "\t" << currsz << "\t" << prevsz + currsz; + prev_ts = ts; + std::cout << format("%Y-%m-%d", prev_date) << "\t" << currblks << "\t" << h << "\t" << currtxs << "\t" << prevtxs + currtxs << "\t" << currsz << "\t" << prevsz + currsz; prevsz += currsz; currsz = 0; currblks = 0; @@ -277,7 +279,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' currsz += bd.size(); currtxs++; if (do_hours) - txhr[currtm.tm_hour]++; + txhr[hh_mm_ss{ts - floor(ts)}.hours().count()]++; if (do_inputs) { io = tx.vin.size(); if (io < minins) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index de2da321c39..218bfe8d827 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -66,6 +66,7 @@ target_link_libraries(common oxenmq::oxenmq filesystem fmt::fmt + date::date PRIVATE sodium extra) diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp index 4be3ac9a091..e768c856406 100755 --- a/src/common/perf_timer.cpp +++ b/src/common/perf_timer.cpp @@ -27,19 +27,17 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include #include -#include "epee/misc_os_dependent.h" #include "perf_timer.h" +using namespace std::literals; + #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "perf" #define PERF_LOG_ALWAYS(level, cat, x) \ el::base::Writer(level, __FILE__, __LINE__, ELPP_FUNC, el::base::DispatchAction::FileOnlyLog).construct(cat) << x -#define PERF_LOG(level, cat, x) \ - do { \ - if (ELPP->vRegistry()->allowed(level, cat)) PERF_LOG_ALWAYS(level, cat, x); \ - } while(0) namespace tools { @@ -59,12 +57,10 @@ void set_performance_timer_log_level(el::Level level) performance_timer_log_level = level; } -PerformanceTimer::PerformanceTimer(bool paused): started(true), paused(paused) +PerformanceTimer::PerformanceTimer(bool paused): started(true) { - if (paused) - ticks = 0; - else - ticks = epee::misc_utils::get_ns_count(); + if (!paused) + since = std::chrono::steady_clock::now(); } LoggingPerformanceTimer::LoggingPerformanceTimer(const std::string &s, const std::string &cat, uint64_t unit, el::Level l): PerformanceTimer(), name(s), cat(cat), unit(unit), level(l) @@ -79,12 +75,12 @@ LoggingPerformanceTimer::LoggingPerformanceTimer(const std::string &s, const std } else { - LoggingPerformanceTimer *pt = performance_timers->back(); - if (!pt->started && !pt->paused) + LoggingPerformanceTimer* pt = performance_timers->back(); + if (!pt->started && pt->since) { if (log) { - size_t size = 0; for (const auto *tmp: *performance_timers) if (!tmp->paused) ++size; + size_t size = 0; for (const auto *tmp: *performance_timers) if (tmp->since) ++size; PERF_LOG_ALWAYS(pt->level, cat.c_str(), "PERF " << std::string((size-1) * 2, ' ') << " " << pt->name); } pt->started = true; @@ -101,47 +97,45 @@ LoggingPerformanceTimer::~LoggingPerformanceTimer() if (log) { char s[12]; - snprintf(s, sizeof(s), "%8llu ", (unsigned long long)(ticks / (1000000000 / unit))); - size_t size = 0; for (const auto *tmp: *performance_timers) if (!tmp->paused || tmp==this) ++size; + snprintf(s, sizeof(s), "%8llu ", static_cast(elapsed.count() / (1000000000 / unit))); + size_t size = 0; for (const auto *tmp: *performance_timers) if (tmp->since || tmp==this) ++size; PERF_LOG_ALWAYS(level, cat.c_str(), "PERF " << s << std::string(size * 2, ' ') << " " << name); } if (performance_timers->empty()) { delete performance_timers; - performance_timers = NULL; + performance_timers = nullptr; } } void PerformanceTimer::pause() { - if (paused) + if (!since) return; - ticks = epee::misc_utils::get_ns_count() - ticks; - paused = true; + auto now = std::chrono::steady_clock::now(); + elapsed += now - *since; + since.reset(); } void PerformanceTimer::resume() { - if (!paused) - return; - ticks = epee::misc_utils::get_ns_count() - ticks; - paused = false; + if (!since) + since = std::chrono::steady_clock::now(); } void PerformanceTimer::reset() { - if (paused) - ticks = 0; - else - ticks = epee::misc_utils::get_ns_count(); + elapsed = 0ns; + if (since) + since = std::chrono::steady_clock::now(); } -uint64_t PerformanceTimer::value() const +std::chrono::nanoseconds PerformanceTimer::value() const { - uint64_t v = ticks; - if (!paused) - v = epee::misc_utils::get_ns_count() - v; - return v; + auto ret = elapsed; + if (since) + ret += std::chrono::steady_clock::now() - *since; + return ret; } -} +} // namespace tools diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index 22049bf027d..f0e0c71b1b1 100755 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -29,6 +29,7 @@ #pragma once +#include #include #include #include @@ -46,15 +47,13 @@ class PerformanceTimer void pause(); void resume(); void reset(); - uint64_t value() const; - operator uint64_t() const { return value(); } - float milliseconds() const { return value() / 1.0e6; } - float seconds() const { return milliseconds() / 1000.f; } + std::chrono::nanoseconds value() const; + std::chrono::duration seconds() const { return value(); } protected: - uint64_t ticks; + std::optional since; + std::chrono::nanoseconds elapsed; bool started; - bool paused; }; class LoggingPerformanceTimer: public PerformanceTimer diff --git a/src/common/util.cpp b/src/common/util.cpp index ec58dbabf5b..6a7396c5181 100755 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -29,15 +29,17 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include #include #include #include +#include + #include "epee/string_tools.h" #include "epee/wipeable_string.h" #include "crypto/crypto.h" #include "util.h" -#include "epee/misc_os_dependent.h" #include "epee/readline_buffer.h" #include "epee/misc_log_ex.h" #include "string_util.h" @@ -192,16 +194,11 @@ namespace tools } #endif - std::string get_human_readable_timestamp(uint64_t ts) + std::string get_human_readable_timestamp(std::time_t t) { - char buffer[64]; - if (ts < 1234567890) + if (t < 1234567890) return ""; - time_t tt = ts; - struct tm tm; - epee::misc_utils::get_gmt_time(tt, tm); - strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S UTC", &tm); - return std::string(buffer); + return date::format("%Y-%m-%d %H:%M:%S UTC", std::chrono::system_clock::from_time_t(t)); } std::string get_human_readable_timespan(std::chrono::seconds seconds) diff --git a/src/common/util.h b/src/common/util.h index c010b8d6072..1a3a3a706e0 100755 --- a/src/common/util.h +++ b/src/common/util.h @@ -65,7 +65,7 @@ namespace tools std::string input_line_win(); #endif - std::string get_human_readable_timestamp(uint64_t ts); + std::string get_human_readable_timestamp(std::time_t ts); std::string get_human_readable_timespan(std::chrono::seconds seconds); std::string get_human_readable_bytes(uint64_t bytes); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 84f4277d684..ae4e91eaa95 100755 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -33,7 +33,6 @@ #include #include #include "cryptonote_basic/cryptonote_format_utils.h" -#include "epee/misc_os_dependent.h" #include "common/command_line.h" #include "common/util.h" #include "common/file.h" diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 400a4b5945d..ede4846748c 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -36,6 +36,7 @@ #include "common/rules.h" #include "common/hex.h" +#include "common/string_util.h" #include "common/median.h" #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -48,7 +49,6 @@ #include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_config.h" #include "cryptonote_basic/miner.h" -#include "epee/profile_tools.h" #include "epee/int-util.h" #include "epee/string_tools.h" #include "common/threadpool.h" @@ -607,7 +607,7 @@ bool Blockchain::store_blockchain() // lock because the rpc_thread command handler also calls this std::unique_lock lock{*m_db}; - TIME_MEASURE_START(save); + auto save = std::chrono::steady_clock::now(); // TODO: make sure sync(if this throws that it is not simply ignored higher // up the call stack try @@ -625,9 +625,8 @@ bool Blockchain::store_blockchain() throw; } - TIME_MEASURE_FINISH(save); if(m_show_time_stats) - MINFO("Blockchain stored OK, took: " << save << " ms"); + MINFO("Blockchain stored OK, took: " << tools::friendly_duration(std::chrono::steady_clock::now() - save)); return true; } //------------------------------------------------------------------ @@ -691,7 +690,7 @@ void Blockchain::pop_blocks(uint64_t nblocks) { if (nblocks >= blocks_expected_per_day && (i != 0 && (i % blocks_per_update == 0))) { - MGINFO("... popping blocks " << (++progress * PERCENT_PER_PROGRESS_UPDATE) << "% completed, height: " << (blockchain_height - i) << " (" << timer.seconds() << "s)"); + MGINFO("... popping blocks " << (++progress * PERCENT_PER_PROGRESS_UPDATE) << "% completed, height: " << (blockchain_height - i) << " (" << tools::friendly_duration(timer.value()) << "s)"); timer.reset(); } @@ -3001,15 +3000,15 @@ void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx) // check if we're doing per-block checkpointing if (m_db->height() < m_blocks_hash_check.size()) { - TIME_MEASURE_START(a); + auto a = std::chrono::steady_clock::now(); m_blocks_txs_check.push_back(get_transaction_hash(tx)); - TIME_MEASURE_FINISH(a); if(m_show_time_stats) { size_t ring_size = 0; if (!tx.vin.empty() && std::holds_alternative(tx.vin[0])) ring_size = var::get(tx.vin[0]).key_offsets.size(); - MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a); + MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << + tools::friendly_duration(std::chrono::steady_clock::now() - a)); } } #endif @@ -3038,16 +3037,16 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh } #endif - TIME_MEASURE_START(a); + auto a = std::chrono::steady_clock::now(); bool res = check_tx_inputs(tx, tvc, &max_used_block_height, key_image_conflicts); - TIME_MEASURE_FINISH(a); if(m_show_time_stats) { size_t ring_size = 0; if (!tx.vin.empty() && std::holds_alternative(tx.vin[0])) ring_size = var::get(tx.vin[0]).key_offsets.size(); MINFO("HASH: " << get_transaction_hash(tx) << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << - " H: " << max_used_block_height << " ms: " << a + m_fake_scan_time << " B: " << get_object_blobsize(tx) << " W: " << get_transaction_weight(tx)); + " H: " << max_used_block_height << " time: " << tools::friendly_duration(std::chrono::steady_clock::now() - a + m_fake_scan_time) << + " B: " << get_object_blobsize(tx) << " W: " << get_transaction_weight(tx)); } if (!res) return false; @@ -4239,22 +4238,21 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& { LOG_PRINT_L3("Blockchain::" << __func__); - TIME_MEASURE_START(block_processing_time); + auto block_processing_start = std::chrono::steady_clock::now(); std::unique_lock lock{*this}; db_rtxn_guard rtxn_guard(m_db); - TIME_MEASURE_START(t1); + auto t1 = std::chrono::steady_clock::now(); if (!basic_block_checks(bl, false /*alt_block*/)) { bvc.m_verifivation_failed = true; return false; } - TIME_MEASURE_FINISH(t1); + auto t1_elapsed = std::chrono::steady_clock::now() - t1; struct { - uint64_t verify_pow_time; - uint64_t difficulty_calc_time; + std::chrono::nanoseconds verify_pow_time; block_pow_verified blk_pow = {}; } miner = {}; @@ -4270,12 +4268,9 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& } else // check proof of work { - miner.difficulty_calc_time = epee::misc_utils::get_tick_count(); - miner.difficulty_calc_time = epee::misc_utils::get_tick_count() - miner.difficulty_calc_time; - - miner.verify_pow_time = epee::misc_utils::get_tick_count(); + auto verify_pow_start = std::chrono::steady_clock::now(); miner.blk_pow = verify_block_pow(bl, current_diffic, chain_height, false /*alt_block*/); - miner.verify_pow_time = epee::misc_utils::get_tick_count() - miner.verify_pow_time; + miner.verify_pow_time = std::chrono::steady_clock::now() - verify_pow_start; if (!miner.blk_pow.valid) { @@ -4294,10 +4289,10 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& key_images_container keys; uint64_t fee_summary = 0; - uint64_t t_checktx = 0; - uint64_t t_exists = 0; - uint64_t t_pool = 0; - uint64_t t_dblspnd = 0; + auto t_checktx = 0ns; + auto t_exists = 0ns; + auto t_pool = 0ns; + auto t_dblspnd = 0ns; // XXX old code adds miner tx here @@ -4313,7 +4308,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& size_t tx_weight = 0; uint64_t fee = 0; bool relayed = false, do_not_relay = false, double_spend_seen = false; - TIME_MEASURE_START(aa); + auto aa = std::chrono::steady_clock::now(); // XXX old code does not check whether tx exists if (m_db->tx_exists(tx_id)) @@ -4324,9 +4319,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& return false; } - TIME_MEASURE_FINISH(aa); - t_exists += aa; - TIME_MEASURE_START(bb); + auto bb = std::chrono::steady_clock::now(); + t_exists += bb - aa; // get transaction with hash from tx_pool if(!m_tx_pool.take_tx(tx_id, tx_tmp, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen)) @@ -4337,14 +4331,13 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& return false; } - TIME_MEASURE_FINISH(bb); - t_pool += bb; + auto dd = std::chrono::steady_clock::now(); + t_pool += dd - bb; // add the transaction to the temp list of transactions, so we can either // store the list of transactions all at once or return the ones we've // taken from the tx_pool back to it if the block fails verification. txs.push_back(std::make_pair(std::move(tx_tmp), std::move(txblob))); transaction &tx = txs.back().first; - TIME_MEASURE_START(dd); // FIXME: the storage should not be responsible for validation. // If it does any, it is merely a sanity check. @@ -4359,9 +4352,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& // break; // } - TIME_MEASURE_FINISH(dd); - t_dblspnd += dd; - TIME_MEASURE_START(cc); + auto cc = std::chrono::steady_clock::now(); + t_dblspnd += cc - dd; #if defined(PER_BLOCK_CHECKPOINT) if (!miner.blk_pow.per_block_checkpointed) @@ -4398,15 +4390,14 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& } } #endif - TIME_MEASURE_FINISH(cc); - t_checktx += cc; + t_checktx += std::chrono::steady_clock::now() - cc; fee_summary += fee; cumulative_block_weight += tx_weight; } m_blocks_txs_check.clear(); - TIME_MEASURE_START(vmt); + auto vmt = std::chrono::steady_clock::now(); uint64_t base_reward = 0; uint64_t already_generated_coins = chain_height ? m_db->get_block_already_generated_coins(chain_height - 1) : 0; if(!validate_miner_transaction(bl, cumulative_block_weight, fee_summary, base_reward, already_generated_coins, get_network_version())) @@ -4417,7 +4408,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& return false; } - TIME_MEASURE_FINISH(vmt); + auto vmt_elapsed = std::chrono::steady_clock::now() - vmt; // populate various metadata about the block to be stored alongside it. size_t block_weight = cumulative_block_weight; difficulty_type cumulative_difficulty = current_diffic; @@ -4430,12 +4421,12 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& if(chain_height) cumulative_difficulty += m_db->get_block_cumulative_difficulty(chain_height - 1); - TIME_MEASURE_FINISH(block_processing_time); + auto block_processing_time = std::chrono::steady_clock::now() - block_processing_start; if(miner.blk_pow.precomputed) block_processing_time += m_fake_pow_calc_time; rtxn_guard.stop(); - TIME_MEASURE_START(addblock); + auto addblock = std::chrono::steady_clock::now(); uint64_t new_height = 0; if (!bvc.m_verifivation_failed) { @@ -4517,7 +4508,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& } } - TIME_MEASURE_FINISH(addblock); + auto addblock_elapsed = std::chrono::steady_clock::now() - addblock; // do this after updating the hard fork state since the weight limit may change due to fork if (!update_next_cumulative_weight_limit()) @@ -4536,7 +4527,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& "\n\tblock reward: " << print_money(fee_after_penalty + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_after_penalty) << ")" ", coinbase_weight: " << coinbase_weight << ", cumulative weight: " << cumulative_block_weight << - ", " << block_processing_time << "ms"); + ", " << tools::friendly_duration(block_processing_time)); } else { @@ -4548,16 +4539,21 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& "\n\tblock reward: " << print_money(fee_after_penalty + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_after_penalty) << ")" ", coinbase_weight: " << coinbase_weight << ", cumulative weight: " << cumulative_block_weight << - ", " << block_processing_time << "(" << miner.difficulty_calc_time << "/" << miner.verify_pow_time << ")ms"); + ", " << tools::friendly_duration(block_processing_time) << "(" << tools::friendly_duration(miner.verify_pow_time) << ")"); } if(m_show_time_stats) { MINFO("Height: " << new_height << " coinbase weight: " << coinbase_weight << " cumm: " - << cumulative_block_weight << " p/t: " << block_processing_time << " (" - << miner.difficulty_calc_time << "/" << miner.verify_pow_time << "/" - << t1 << "/" << t_exists << "/" << t_pool - << "/" << t_checktx << "/" << t_dblspnd << "/" << vmt << "/" << addblock << ")ms"); + << cumulative_block_weight << " p/t: " << tools::friendly_duration(block_processing_time) << " (" + << "/" << tools::friendly_duration(miner.verify_pow_time) + << "/" << tools::friendly_duration(t1_elapsed) + << "/" << tools::friendly_duration(t_exists) + << "/" << tools::friendly_duration(t_pool) + << "/" << tools::friendly_duration(t_checktx) + << "/" << tools::friendly_duration(t_dblspnd) + << "/" << tools::friendly_duration(vmt_elapsed) + << "/" << tools::friendly_duration(addblock_elapsed) << ")"); } @@ -4853,8 +4849,6 @@ bool Blockchain::get_checkpoint(uint64_t height, checkpoint_t &checkpoint) const //------------------------------------------------------------------ void Blockchain::block_longhash_worker(uint64_t height, const epee::span &blocks, std::unordered_map &map) const { - TIME_MEASURE_START(t); - for (const auto & block : blocks) { if (m_cancel) @@ -4863,8 +4857,6 @@ void Blockchain::block_longhash_worker(uint64_t height, const epee::span &blocks_entry, std::vector &blocks) { MTRACE("Blockchain::" << __func__); - TIME_MEASURE_START(prepare); + auto prepare = std::chrono::steady_clock::now(); uint64_t bytes = 0; size_t total_txs = 0; blocks.clear(); @@ -5263,18 +5253,18 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector 1 && threads > 1 && m_show_time_stats) - MDEBUG("Prepare blocks took: " << prepare << " ms"); + MDEBUG("Prepare blocks took: " << tools::friendly_duration(prepare_elapsed)); - TIME_MEASURE_START(scantable); + auto scantable = std::chrono::steady_clock::now(); // [input] stores all unique amounts found std::vector < uint64_t > amounts; @@ -5445,12 +5435,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector 0) { - m_fake_scan_time = scantable / total_txs; + auto scantable_elapsed = std::chrono::steady_clock::now() - scantable; + m_fake_scan_time = scantable_elapsed / total_txs; if(m_show_time_stats) - MDEBUG("Prepare scantable took: " << scantable << " ms"); + MDEBUG("Prepare scantable took: " << tools::friendly_duration(scantable_elapsed)); } return true; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 56d30bfc60e..aaa8e29b152 100755 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1087,8 +1087,8 @@ namespace cryptonote bool m_db_sync_on_blocks; uint64_t m_db_sync_threshold; uint64_t m_max_prepare_blocks_threads; - uint64_t m_fake_pow_calc_time; - uint64_t m_fake_scan_time; + std::chrono::nanoseconds m_fake_pow_calc_time; + std::chrono::nanoseconds m_fake_scan_time; uint64_t m_sync_counter; uint64_t m_bytes_to_sync; diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index ad09702f7a2..17f820a73b7 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -34,6 +34,7 @@ #include #include #include +#include extern "C" { #include @@ -52,7 +53,6 @@ extern "C" { #include "common/random.h" #include "common/lock.h" #include "common/hex.h" -#include "epee/misc_os_dependent.h" #include "blockchain.h" #include "master_node_quorum_cop.h" @@ -3759,14 +3759,8 @@ namespace master_nodes if (make_friendly) { stream << "\n\n"; - time_t tt = exp_timestamp; - - struct tm tm; - epee::misc_utils::get_gmt_time(tt, tm); - - char buffer[128]; - strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M:%S %p UTC", &tm); - stream << tr("This registration expires at ") << buffer << tr(".\n"); + auto exp = std::chrono::system_clock::from_time_t(exp_timestamp); + stream << tr("This registration expires at ") << date::format("%Y-%m-%d %I:%M:%S %p UTC", exp) << tr(".\n"); stream << tr("This should be in about 2 weeks, if it isn't, check this computer's clock.\n"); stream << tr("Please submit your registration into the blockchain before this time or it will be invalid."); } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 5e473089bf4..b5216672308 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -186,7 +186,7 @@ namespace cryptonote tools::periodic_task m_sync_search_checker{101s}; std::atomic m_max_out_peers; tools::PerformanceTimer m_sync_timer, m_add_timer; - uint64_t m_last_add_end_time; + std::optional m_last_add_end_time; uint64_t m_sync_spans_downloaded, m_sync_old_spans_downloaded, m_sync_bad_spans_downloaded; uint64_t m_sync_download_chain_size, m_sync_download_objects_size; size_t m_block_download_max_size; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index d254dfb5f33..a5bf6df4271 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -42,6 +42,7 @@ #include #include +#include "common/string_util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/verification_context.h" @@ -106,7 +107,7 @@ namespace cryptonote m_sync_timer.reset(); m_add_timer.pause(); m_add_timer.reset(); - m_last_add_end_time = 0; + m_last_add_end_time = std::nullopt; m_sync_spans_downloaded = 0; m_sync_old_spans_downloaded = 0; m_sync_bad_spans_downloaded = 0; @@ -511,7 +512,7 @@ namespace cryptonote m_sync_timer.reset(); m_add_timer.pause(); m_add_timer.reset(); - m_last_add_end_time = 0; + m_last_add_end_time = std::nullopt; m_sync_spans_downloaded = 0; m_sync_old_spans_downloaded = 0; m_sync_bad_spans_downloaded = 0; @@ -1424,7 +1425,7 @@ namespace cryptonote { m_add_timer.pause(); m_core.resume_mine(); - if (!starting) m_last_add_end_time = epee::misc_utils::get_ns_count(); + if (!starting) m_last_add_end_time = std::chrono::steady_clock::now(); }; while (1) @@ -1522,8 +1523,8 @@ namespace cryptonote starting = false; if (m_last_add_end_time) { - const uint64_t ns = epee::misc_utils::get_ns_count() - m_last_add_end_time; - MINFO("Restarting adding block after idle for " << ns/1e9 << " seconds"); + auto elapsed = std::chrono::steady_clock::now() - *m_last_add_end_time; + MINFO("Restarting adding block after idle for " << tools::friendly_duration(elapsed)); } } @@ -1552,7 +1553,8 @@ namespace cryptonote return 1; } - uint64_t block_process_time_full = 0, transactions_process_time_full = 0; + auto block_process_time_full = 0ns; + auto transactions_process_time_full = 0ns; size_t num_txs = 0, blockidx = 0; for(const block_complete_entry& block_entry: blocks) { @@ -1560,7 +1562,7 @@ namespace cryptonote return 1; // process transactions - TIME_MEASURE_START(transactions_process_time); + auto transactions_process_start = std::chrono::steady_clock::now(); num_txs += block_entry.txs.size(); auto parsed_txs = m_core.handle_incoming_txs(block_entry.txs, tx_pool_options::from_block()); @@ -1568,7 +1570,7 @@ namespace cryptonote { if (parsed_txs[i].tvc.m_verifivation_failed) { - if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ + if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f) -> bool{ cryptonote::transaction tx; parse_and_validate_tx_from_blob(block_entry.txs[i], tx); // must succeed if we got here LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_BLOCKS, tx_id = " @@ -1582,8 +1584,7 @@ namespace cryptonote return 1; } } - TIME_MEASURE_FINISH(transactions_process_time); - transactions_process_time_full += transactions_process_time; + transactions_process_time_full += std::chrono::steady_clock::now() - transactions_process_start; // // NOTE: Checkpoint parsing @@ -1607,7 +1608,7 @@ namespace cryptonote // process block - TIME_MEASURE_START(block_process_time); + auto block_process_start = std::chrono::steady_clock::now(); block_verification_context bvc{}; m_core.handle_incoming_block(block_entry.block, pblocks.empty() ? NULL : &pblocks[blockidx], bvc, checkpoint, false); // <--- process block @@ -1630,14 +1631,16 @@ namespace cryptonote return 1; } - TIME_MEASURE_FINISH(block_process_time); - block_process_time_full += block_process_time; + block_process_time_full += std::chrono::steady_clock::now() - block_process_start; ++blockidx; } // each download block remove_spans = true; - MDEBUG(context << "Block process time (" << blocks.size() << " blocks, " << num_txs << " txs): " << block_process_time_full + transactions_process_time_full << " (" << transactions_process_time_full << "/" << block_process_time_full << ") ms"); + MDEBUG(context << "Block process time (" << blocks.size() << " blocks, " << num_txs << " txs): " << + tools::friendly_duration(block_process_time_full + transactions_process_time_full) << " (" << + tools::friendly_duration(transactions_process_time_full) << "+" << + tools::friendly_duration(block_process_time_full) << ")"); } const uint64_t current_blockchain_height = m_core.get_current_blockchain_height(); @@ -2125,7 +2128,7 @@ skip: // if this has gone on for too long, drop incoming connection to guard against some wedge state if (!context.m_is_income) { - std::chrono::nanoseconds ns{epee::misc_utils::get_ns_count() - m_last_add_end_time}; + auto ns = std::chrono::steady_clock::now() - m_last_add_end_time.value_or(std::chrono::steady_clock::time_point::min()); if (ns >= DROP_ON_SYNC_WEDGE_THRESHOLD) { MDEBUG(context << "Block addition seems to have wedged, dropping connection"); @@ -2393,12 +2396,12 @@ skip: m_sync_timer.pause(); if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info")) { - const uint64_t sync_time = m_sync_timer.value(); - const uint64_t add_time = m_add_timer.value(); - if (sync_time && add_time) + const auto sync_time = m_sync_timer.value(); + const auto add_time = m_add_timer.value(); + if (sync_time > 0ns && add_time > 0ns) { - MCLOG_YELLOW(el::Level::Info, "sync-info", "Sync time: " << sync_time/1e9/60 << " min, idle time " << - (100.f * (1.0f - add_time / (float)sync_time)) << "%" << ", " << + MCLOG_YELLOW(el::Level::Info, "sync-info", "Sync time: " << tools::friendly_duration(sync_time) << " min, idle time " << + (100.f * (1.0f - add_time / sync_time)) << "%" << ", " << (10 * m_sync_download_objects_size / 1024 / 1024) / 10.f << " + " << (10 * m_sync_download_chain_size / 1024 / 1024) / 10.f << " MB downloaded, " << 100.0f * m_sync_old_spans_downloaded / m_sync_spans_downloaded << "% old spans, " << diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 194a9c3e2fd..c7879847050 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -196,17 +196,6 @@ namespace { return get_human_time_ago(std::chrono::seconds{now - t}, abbreviate); } - char const *get_date_time(time_t t) - { - static char buf[128]; - buf[0] = 0; - - struct tm tm; - epee::misc_utils::get_gmt_time(t, tm); - strftime(buf, sizeof(buf), "%Y-%m-%d %I:%M:%S %p UTC", &tm); - return buf; - } - std::string get_time_hms(time_t t) { unsigned int hours, minutes, seconds; @@ -1698,9 +1687,11 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net else { uint64_t delta_height = (blockchain_height >= expiry_height) ? 0 : expiry_height - blockchain_height; - uint64_t expiry_epoch_time = now + (delta_height * tools::to_seconds(cryptonote::TARGET_BLOCK_TIME)); + auto expiry_epoch_time = now + (delta_height * tools::to_seconds(cryptonote::TARGET_BLOCK_TIME)); stream << expiry_height << " (in " << delta_height << ") blocks\n"; - stream << indent2 << "Expiry Date (estimated): " << get_date_time(expiry_epoch_time) << " (" << get_human_time_ago(expiry_epoch_time, now) << ")\n"; + stream << indent2 << "Expiry Date (estimated): " << + date::format("%Y-%m-%d %I:%M:%S %p UTC", std::chrono::system_clock::from_time_t(expiry_epoch_time)) << + " (" << get_human_time_ago(expiry_epoch_time, now) << ")\n"; } } From 5ff645821b8801eb522f8d4424dfe1ad594bde4b Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 18:17:58 +0530 Subject: [PATCH 041/182] RPC: GET_TRANSACTION_POOL_STATS improvements/tweaks --- src/cryptonote_core/tx_pool.cpp | 13 +-- src/cryptonote_core/tx_pool.h | 2 - src/daemon/rpc_command_executor.cpp | 96 +++++++++++----------- src/rpc/core_rpc_server.cpp | 13 ++- src/rpc/core_rpc_server_command_parser.cpp | 4 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 40 ++++++++- 7 files changed, 108 insertions(+), 61 deletions(-) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 1dc44b79263..a40013387d1 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1212,7 +1212,7 @@ namespace cryptonote { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); - tx_stats stats; + tx_stats stats{}; const uint64_t now = time(NULL); std::map> agebytes; stats.txs_total = m_blockchain.get_txpool_tx_count(include_unrelayed_txes); @@ -1234,9 +1234,10 @@ namespace cryptonote stats.num_10m++; if (meta.last_failed_height) stats.num_failing++; - uint64_t age = now - meta.receive_time + (now == meta.receive_time); - agebytes[age].first++; - agebytes[age].second += meta.weight; + uint64_t age = now < meta.receive_time ? 0 : now - meta.receive_time; + auto& a = agebytes[age]; + a.first++; + a.second += meta.weight; if (meta.double_spend_seen) ++stats.num_double_spends; return true; @@ -1244,6 +1245,7 @@ namespace cryptonote stats.bytes_med = tools::median(std::move(weights)); if (stats.txs_total > 1) { + stats.histo.resize(10); /* looking for 98th percentile */ size_t end = stats.txs_total * 0.02; uint64_t delta, factor; @@ -1273,9 +1275,8 @@ namespace cryptonote */ stats.histo_98pc = 0; it = agebytes.end(); - factor = stats.txs_total > 9 ? 10 : stats.txs_total; + factor = 10; delta = now - stats.oldest; - stats.histo.resize(factor); } if (!delta) delta = 1; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 20cd34e6dbf..39eebda316c 100755 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -434,8 +434,6 @@ namespace cryptonote uint64_t histo_98pc; ///< the time 98% of txes are "younger" than. std::vector> histo; ///< List of txpool histo [number of txes, size in bytes] pairs. uint32_t num_double_spends; ///< Number of double spend transactions. - - tx_stats(): bytes_total(0), bytes_min(0), bytes_max(0), bytes_med(0), fee_total(0), oldest(0), txs_total(0), num_failing(0), num_10m(0), num_not_relayed(0), histo_98pc(0), num_double_spends(0) {} }; /** diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index c7879847050..a4503fe98d4 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -196,18 +196,6 @@ namespace { return get_human_time_ago(std::chrono::seconds{now - t}, abbreviate); } - std::string get_time_hms(time_t t) - { - unsigned int hours, minutes, seconds; - char buffer[24]; - hours = t / 3600; - t %= 3600; - minutes = t / 60; - t %= 60; - seconds = t; - snprintf(buffer, sizeof(buffer), "%02u:%02u:%02u", hours, minutes, seconds); - return std::string(buffer); - } } rpc_command_executor::rpc_command_executor( @@ -1072,60 +1060,74 @@ bool rpc_command_executor::print_transaction_pool_short() { } bool rpc_command_executor::print_transaction_pool_stats() { - GET_TRANSACTION_POOL_STATS::response res{}; auto full_reward_zone = try_running([this] { return invoke().at("block_size_limit").get() / 2; }, "Failed to retrieve node info"); if (!full_reward_zone) return false; - if (!invoke({}, res, "Failed to retreive transaction pool statistics")) + auto maybe_stats = try_running([this] { return invoke(json{{"include_unrelayed", true}}); }, + "Failed to retrieve transaction pool statistics"); + if (!maybe_stats) return false; + auto& pstats = maybe_stats->at("pool_stats"); - size_t n_transactions = res.pool_stats.txs_total; + size_t n_transactions = pstats["txs_total"].get(); const uint64_t now = time(NULL); - size_t avg_bytes = n_transactions ? res.pool_stats.bytes_total / n_transactions : 0; + auto bytes_total = pstats["bytes_total"].get(); + size_t avg_bytes = n_transactions ? bytes_total / n_transactions : 0; - std::string backlog_message; - if (res.pool_stats.bytes_total <= *full_reward_zone) - { - backlog_message = "no backlog"; - } - else + std::string backlog_message = "no backlog"; + if (bytes_total > *full_reward_zone) { - uint64_t backlog = (res.pool_stats.bytes_total + *full_reward_zone - 1) / *full_reward_zone; + uint64_t backlog = (bytes_total + *full_reward_zone - 1) / *full_reward_zone; backlog_message = fmt::format("estimated {} block ({} minutes ) backlog", backlog, backlog * cryptonote::TARGET_BLOCK_TIME / 1min); } - tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ", median " << res.pool_stats.bytes_med << ")" << std::endl - << "fees " << cryptonote::print_money(res.pool_stats.fee_total) << " (avg " << cryptonote::print_money(n_transactions ? res.pool_stats.fee_total / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(res.pool_stats.bytes_total ? res.pool_stats.fee_total / res.pool_stats.bytes_total : 0) << " per byte)" << std::endl - << res.pool_stats.num_double_spends << " double spends, " << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message; - - if (n_transactions > 1 && res.pool_stats.histo.size()) + uint64_t fee_total = pstats["fee_total"].get(); + std::time_t oldest = pstats["oldest"].get(); + tools::msg_writer() << n_transactions << " tx(es), " + << bytes_total << " bytes total (min " << pstats["bytes_min"].get() << ", max " << pstats["bytes_max"].get() + << ", avg " << avg_bytes << ", median " << pstats["bytes_med"].get() << ')' + << '\n' + << "fees " << cryptonote::print_money(fee_total) << " (avg " << cryptonote::print_money(n_transactions ? fee_total / n_transactions : 0) << " per tx, " + << cryptonote::print_money(bytes_total ? fee_total / bytes_total : 0) << " per byte)" + << '\n' + << pstats["num_double_spends"].get() << " double spends, " + << pstats["num_not_relayed"].get() << " not relayed, " + << pstats["num_failing"].get() << " failing, " + << pstats["num_10m"].get() << " older than 10 minutes (oldest " + << (oldest == 0 ? "-" : get_human_time_ago(oldest, now)) << "), " + << backlog_message; + + auto histo = pstats["histo"].get>>(); + if (n_transactions > 1 && !histo.empty()) { - std::vector times; - uint64_t numer; - size_t i, n = res.pool_stats.histo.size(), denom; - times.resize(n); - if (res.pool_stats.histo_98pc) - { - numer = res.pool_stats.histo_98pc; - denom = n-1; - for (i=0; i times; + bool last_is_gt = false; + if (auto it = pstats.find("histo_98pc"); it != pstats.end()) { - numer = now - res.pool_stats.oldest; - denom = n; - for (i=0; iget(); + for (size_t i = 0; i < 11; i++) + times[i] = i * histo98 / 9; + last_is_gt = true; } - tools::msg_writer() << " Age Txes Bytes"; - for (i=0; i(); + for (size_t i = 0; i < 11; i++) + times[i] = i * histo_max / 10; } + + constexpr auto hist_fmt = "{:>10} - {:<14} {:>7} {:>11}"sv; + tools::msg_writer() << fmt::format("{:^23} {:>7} {:>11}", "Age", "Txes", "Bytes"); + for (size_t i = 0; i < 10; i++) + tools::msg_writer() + << fmt::format(hist_fmt, + get_human_time_ago(times[i] * 1s, true), + (last_is_gt && i == 10 ? "" : get_human_time_ago(times[i+1] * 1s, true) + " ago"), + histo[i].first, + histo[i].second); } tools::msg_writer(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 427f748607b..830df894ac9 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1551,8 +1551,8 @@ namespace cryptonote::rpc { // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - auto txpool = m_core.get_pool().get_transaction_stats(context.admin); - stats.response["pool_stats"] = json{ + auto txpool = m_core.get_pool().get_transaction_stats(stats.request.include_unrelayed); + json pool_stats{ {"bytes_total", txpool.bytes_total}, {"bytes_min", txpool.bytes_min}, {"bytes_max", txpool.bytes_max}, @@ -1563,10 +1563,15 @@ namespace cryptonote::rpc { {"num_failing", txpool.num_failing}, {"num_10m", txpool.num_10m}, {"num_not_relayed", txpool.num_not_relayed}, - {"histo_98pc", txpool.histo_98pc}, - {"histo", txpool.histo}, + {"histo", std::move(txpool.histo)}, {"num_double_spends", txpool.num_double_spends}}; + if (txpool.histo_98pc) + pool_stats["histo_98pc"] = txpool.histo_98pc; + else + pool_stats["histo_max"] = std::time(nullptr) - txpool.oldest; + + stats.response["pool_stats"] = std::move(pool_stats); stats.response["status"] = STATUS_OK; } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 69b4c16fb36..8a554715f68 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -287,4 +287,8 @@ namespace cryptonote::rpc { get_values(in, "outputs", get_outputs.request.output_indices); } + void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in) { + get_values(in, "include_unrelayed", pstats.request.include_unrelayed); + } + } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 24a036ae76b..d56aff05e68 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -22,4 +22,5 @@ namespace cryptonote::rpc { void parse_request(STOP_DAEMON& stop_daemon, rpc_input in); void parse_request(SAVE_BC& save_bc, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); + void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 5eb45d0ffe6..3899c972bbf 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1324,11 +1324,47 @@ namespace cryptonote::rpc { /// Output values available from a public RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p pool_stats List of pool stats. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// - \p pool_stats Dict of pool statistics: + /// - \p bytes_total the total size (in bytes) of the transactions in the transaction pool. + /// - \p bytes_min the size of the smallest transaction in the tx pool. + /// - \p bytes_max the size of the largest transaction in the pool. + /// - \p bytes_med the median transaction size in the pool. + /// - \p fee_total the total fees of all transactions in the transaction pool. + /// - \p txs_total the total number of transactions in the transaction pool + /// - \p num_failing the number of failing transactions: that is, transactions that are in the + /// mempool but are not currently eligible to be added to the blockchain. + /// - \p num_10m the number of transactions received within the last ten minutes + /// - \p num_not_relayed the number of transactions which are not being relayed to the + /// network. Only included when the \p include_unrelayed request parameter is set to true. + /// - \p num_double_spends the number of transactions in the mempool that are marked as + /// double-spends of existing blockchain transactions. + /// - \p oldest the unix timestamp of the oldest transaction in the pool. + /// - \p histo pairs of [# txes, size of bytes] that form a histogram of transactions in the + /// mempool, if there are at least two transactions in the mempool (and omitted entirely + /// otherwise). When present, this field will contain 10 pairs: + /// - When `histo_max` is given then `histo` consists of 10 equally-spaced bins from + /// newest to oldest where the newest bin begins at age 0 and the oldest bin ends at age `\p + /// histo_max`. For example, bin `[3]` contains statistics for transactions with ages + /// between `3*histo_max/10` and `4*histo_max/10`. + /// - Otherwise `histo_98pc` will be present in which case `histo` contains 9 equally spaced + /// bins from newest to oldest where the newest bin begins at age 0 and the oldest bin ends + /// at age `histo_98pc`, and at least 98% of the mempool transactions will fall in these 9 + /// bins. The 10th bin contains statistics for all transactions with ages greater than + /// `histo_98pc`. + /// - \p histo_98pc See `histo` for details. + /// - \p histo_max See `histo` for details. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not + /// trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } + + struct request_parameters { + /// Whether to include transactions marked "do not relay" in the returned statistics. False + /// by default: since they are not relayed, they do not form part of the global network + /// transaction pool. + bool include_unrelayed = false; + } request; }; /// Retrieve information about incoming and outgoing P2P connections to your node. From 2d69a5ef3201f865f61301c71fbd6d5f4d6ae935 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 18 Apr 2025 19:34:11 +0530 Subject: [PATCH 042/182] Add `invoke_simple` for invoking simple RPC calls Simple here means no arguments and no return aside from the "status": "OK" field. For commands like stop_mining, stop_daemon, save_bc. --- src/daemon/rpc_command_executor.cpp | 28 +++++----------------------- src/daemon/rpc_command_executor.h | 12 ++++++++++++ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index a4503fe98d4..463640f40db 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -45,6 +45,7 @@ #include "checkpoints/checkpoints.h" #include #include +#include #include #include "common/beldex_integration_test_hooks.h" @@ -55,6 +56,7 @@ #include #include #include +#include #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "daemon" @@ -393,14 +395,7 @@ bool rpc_command_executor::print_peer_list_stats() { } bool rpc_command_executor::save_blockchain() { - SAVE_BC::response res{}; - - if (!invoke({}, res, "Couldn't save blockchain")) - return false; - - tools::success_msg_writer() << "Blockchain saved"; - - return true; + return invoke_simple("Couldn't save blockchain", "Blockchain saved"); } bool rpc_command_executor::show_hash_rate() { @@ -1155,25 +1150,12 @@ bool rpc_command_executor::start_mining(const cryptonote::account_public_address } bool rpc_command_executor::stop_mining() { - STOP_MINING::response res{}; - - if (!invoke({}, res, "Unable to stop mining")) - return false; - - tools::success_msg_writer() << "Mining stopped"; - return true; + return invoke_simple("Couldn't stop mining", "Mining stopped"); } bool rpc_command_executor::stop_daemon() { - STOP_DAEMON::response res{}; - - if (!invoke({}, res, "Failed to stop daemon")) - return false; - - tools::success_msg_writer() << "Stop signal sent"; - - return true; + return invoke_simple("Couldn't stop daemon", "Stop signal sent"); } bool rpc_command_executor::get_limit(bool up, bool down) diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 479956a38de..665f839ed1f 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -132,6 +132,18 @@ class rpc_command_executor final { return invoke(RPC::names()[0], std::is_base_of_v, std::move(params), check_status_ok); } + // Invokes a simple RPC method that doesn't take any arguments and for which we don't care about + // the return value beyond the "status": "OK" field. Returns true (and prints a success message) + // on success, false (with a failure message printed) on failure. + template && !cryptonote::rpc::FIXME_has_nested_response_v, int> = 0> + bool invoke_simple(std::string_view error_prefix, std::string_view success_msg) { + if (!try_running([this] { return invoke(); }, error_prefix)) + return false; + + tools::success_msg_writer() << success_msg; + return true; + } + bool print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json); bool print_mn_state_changes(uint64_t start_height, uint64_t end_height); From 363993c35df221d238ff309af12571dd8c80faa5 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Sat, 19 Apr 2025 09:42:41 +0530 Subject: [PATCH 043/182] Remove useless junk from miner: autodetect, miner_conf.json --- src/blockchain_db/blockchain_db.cpp | 1 - src/blockchain_db/lmdb/db_lmdb.cpp | 1 - src/cryptonote_basic/miner.cpp | 218 +++--------------- src/cryptonote_basic/miner.h | 46 ++-- src/cryptonote_config.h | 1 - .../cryptonote_protocol_handler.inl | 1 - src/p2p/net_node.inl | 6 +- src/rpc/core_rpc_server.cpp | 17 -- src/rpc/core_rpc_server.h | 3 +- src/rpc/core_rpc_server_commands_defs.cpp | 6 +- src/rpc/core_rpc_server_commands_defs.h | 17 -- src/wallet/wallet2.cpp | 1 - 12 files changed, 58 insertions(+), 260 deletions(-) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 26b8e5eaed8..0f712e48f37 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -34,7 +34,6 @@ #include "epee/string_tools.h" #include "blockchain_db.h" #include "cryptonote_basic/cryptonote_format_utils.h" -#include "epee/profile_tools.h" #include "ringct/rctOps.h" #include "common/hex.h" diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index ce05175a910..04bf274019d 100755 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -46,7 +46,6 @@ #include "common/median.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "crypto/crypto.h" -#include "epee/profile_tools.h" #include "ringct/rctOps.h" #include "checkpoints/checkpoints.h" diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index ae4e91eaa95..c6277dcfb68 100755 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -56,30 +56,15 @@ namespace cryptonote namespace { - const command_line::arg_descriptor arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; - const command_line::arg_descriptor arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; - const command_line::arg_descriptor arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; + const command_line::arg_descriptor arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; + const command_line::arg_descriptor arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; } miner::miner(i_miner_handler* phandler, const get_block_hash_t &gbh):m_stop(1), m_template{}, - m_template_no(0), - m_diffic(0), - m_thread_index(0), m_phandler(phandler), - m_gbh(gbh), - m_height(0), - m_pausers_count(0), - m_threads_total(0), - m_starter_nonce(0), - m_last_hr_merge_time(0), - m_hashes(0), - m_total_hashes(0), - m_do_print_hashrate(false), - m_do_mining(false), - m_current_hash_rate(0), - m_block_reward(0) + m_gbh(gbh) {} //----------------------------------------------------------------------------------------------------- miner::~miner() @@ -115,13 +100,7 @@ namespace cryptonote uint64_t height{}; uint64_t expected_reward; //only used for RPC calls - could possibly be useful here too? - cryptonote::blobdata extra_nonce; - if(m_extra_messages.size() && m_config.current_extra_message_index < m_extra_messages.size()) - { - extra_nonce = m_extra_messages[m_config.current_extra_message_index]; - } - - if(!m_phandler->create_next_miner_block_template(bl, m_mine_address, di, height, expected_reward, extra_nonce)) + if(!m_phandler->create_next_miner_block_template(bl, m_mine_address, di, height, expected_reward, ""s)) { LOG_ERROR("Failed to get_block_template(), stopping mining"); return false; @@ -137,143 +116,32 @@ namespace cryptonote return true; }); - m_update_merge_hr_interval.do_call([&](){ - merge_hr(); - return true; - }); - - m_autodetect_interval.do_call([&](){ - update_autodetection(); + m_update_hashrate_interval.do_call([&](){ + update_hashrate(); return true; }); return true; } //----------------------------------------------------------------------------------------------------- - void miner::do_print_hashrate(bool do_hr) + void miner::update_hashrate() { - m_do_print_hashrate = do_hr; - } - //----------------------------------------------------------------------------------------------------- - void miner::merge_hr() - { - if(m_last_hr_merge_time && is_mining()) - { - m_current_hash_rate = m_hashes * 1000 / ((epee::misc_utils::get_tick_count() - m_last_hr_merge_time + 1)); - std::unique_lock lock{m_last_hash_rates_lock}; - m_last_hash_rates.push_back(m_current_hash_rate); - if(m_last_hash_rates.size() > 19) - m_last_hash_rates.pop_front(); - if(m_do_print_hashrate) - { - uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), 0); - float hr = static_cast(total_hr)/static_cast(m_last_hash_rates.size()); - const auto flags = std::cout.flags(); - const auto precision = std::cout.precision(); - std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << std::setiosflags(flags) << std::setprecision(precision) << std::endl; - } - } - m_last_hr_merge_time = epee::misc_utils::get_tick_count(); - m_hashes = 0; - } - //----------------------------------------------------------------------------------------------------- - void miner::update_autodetection() - { - if (m_threads_autodetect.empty()) - return; - - uint64_t now = epee::misc_utils::get_ns_count(); - uint64_t dt = now - m_threads_autodetect.back().first; - if (dt < AUTODETECT_WINDOW * 1000000000ull) - return; - - // work out how many more hashes we got - m_threads_autodetect.back().first = dt; - uint64_t dh = m_total_hashes - m_threads_autodetect.back().second; - m_threads_autodetect.back().second = dh; - float hs = dh / (dt / (float)1000000000); - MGINFO("Mining autodetection: " << m_threads_autodetect.size() << " threads: " << hs << " H/s"); - - // when we don't increase by at least 2%, stop, otherwise check next - // if N and N+1 have mostly the same hash rate, we want to "lighter" one - bool found = false; - if (m_threads_autodetect.size() > 1) - { - int previdx = m_threads_autodetect.size() - 2; - float previous_hs = m_threads_autodetect[previdx].second / (m_threads_autodetect[previdx].first / (float)1000000000); - if (previous_hs > 0 && hs / previous_hs < AUTODETECT_GAIN_THRESHOLD) - { - m_threads_total = m_threads_autodetect.size() - 1; - m_threads_autodetect.clear(); - MGINFO("Optimal number of threads seems to be " << m_threads_total); - found = true; - } - } - - if (!found) - { - // setup one more thread - m_threads_autodetect.push_back({now, m_total_hashes}); - m_threads_total = m_threads_autodetect.size(); - } - - // restart all threads - std::unique_lock lock{m_threads_lock}; - m_stop = true; - for (auto& th : m_threads) - if (th.joinable()) - th.join(); - m_threads.clear(); - m_stop = false; - m_thread_index = 0; - for(size_t i = 0; i != m_threads_total; i++) - m_threads.emplace_back([this] { return worker_thread(false); }); + std::unique_lock lock{m_hashrate_mutex}; + auto hashes = m_hashes.exchange(0); + using dseconds = std::chrono::duration; + if (m_last_hr_update && is_mining()) + m_current_hash_rate = hashes / dseconds{std::chrono::steady_clock::now() - *m_last_hr_update}.count(); + m_last_hr_update = std::chrono::steady_clock::now(); } //----------------------------------------------------------------------------------------------------- void miner::init_options(boost::program_options::options_description& desc) { - command_line::add_arg(desc, arg_extra_messages); command_line::add_arg(desc, arg_start_mining); command_line::add_arg(desc, arg_mining_threads); } //----------------------------------------------------------------------------------------------------- bool miner::init(const boost::program_options::variables_map& vm, network_type nettype) { - if(command_line::has_arg(vm, arg_extra_messages)) - { - std::string buff; - bool r = tools::slurp_file(fs::u8path(command_line::get_arg(vm, arg_extra_messages)), buff); - CHECK_AND_ASSERT_MES(r, false, "Failed to load file with extra messages: " << command_line::get_arg(vm, arg_extra_messages)); - auto extra_vec = tools::split_any(buff, "\n"sv, true); - m_extra_messages.resize(extra_vec.size()); - for(size_t i = 0; i != extra_vec.size(); i++) - { - tools::trim(extra_vec[i]); - if(!extra_vec[i].size()) - continue; - if (!oxenc::is_base64(extra_vec[i])) - { - MWARNING("Invalid (non-base64) extra message `" << extra_vec[i] << "'"); - continue; - } - - std::string buff = oxenc::from_base64(extra_vec[i]); - if(buff != "0") - m_extra_messages[i] = buff; - } - m_config_dir = fs::u8path(command_line::get_arg(vm, arg_extra_messages)).parent_path(); - m_config = {}; - fs::path filename = m_config_dir / MINER_CONFIG_FILE_NAME; - if (std::string contents; - !tools::slurp_file(filename, contents) || - !epee::serialization::load_t_from_json(m_config, contents)) - { - MERROR("Failed to load data from " << filename); - return false; - } - MINFO("Loaded " << m_extra_messages.size() << " extra messages, current index " << m_config.current_extra_message_index); - } - if(command_line::has_arg(vm, arg_start_mining)) { address_parse_info info; @@ -308,16 +176,10 @@ namespace cryptonote return m_threads_total; } //----------------------------------------------------------------------------------------------------- - bool miner::start(const account_public_address& adr, size_t threads_count, uint64_t stop_after, bool slow_mining) + bool miner::start(const account_public_address& adr, int threads_count, int stop_after, bool slow_mining) { m_mine_address = adr; - m_threads_total = static_cast(threads_count); - if (threads_count == 0) - { - m_threads_autodetect.clear(); - m_threads_autodetect.push_back({epee::misc_utils::get_ns_count(), m_total_hashes}); - m_threads_total = 1; - } + m_threads_total = std::max(threads_count, 1); m_starter_nonce = crypto::rand(); std::unique_lock lock{m_threads_lock}; if(is_mining()) @@ -335,32 +197,25 @@ namespace cryptonote request_block_template();//lets update block template m_stop = false; - m_thread_index = 0; m_stop_height = stop_after ? m_height + stop_after : std::numeric_limits::max(); if (stop_after) MGINFO("Mining until height " << m_stop_height); - for(size_t i = 0; i != m_threads_total; i++) - { - m_threads.emplace_back([=] { return worker_thread(slow_mining); }); - } + for (int i = 0; i < m_threads_total; i++) + m_threads.emplace_back([=] { return worker_thread(i, slow_mining); }); - if (threads_count == 0) - MINFO("Mining has started, autodetecting optimal number of threads, good luck!" ); - else - MINFO("Mining has started with " << threads_count << " threads, good luck!" ); + MINFO("Mining has started with " << m_threads_total << " threads, good luck!" ); return true; } //----------------------------------------------------------------------------------------------------- - uint64_t miner::get_speed() const + double miner::get_speed() const { - if(is_mining()) { + if (is_mining()) { + std::unique_lock lock{m_hashrate_mutex}; return m_current_hash_rate; } - else { - return 0; - } + return 0.0; } //----------------------------------------------------------------------------------------------------- extern "C" void rx_stop_mining(void); @@ -384,7 +239,6 @@ namespace cryptonote MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); m_threads.clear(); - m_threads_autodetect.clear(); rx_stop_mining(); return true; } @@ -416,7 +270,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------------- void miner::pause() { - std::unique_lock lock{m_miners_count_lock}; + std::unique_lock lock{m_miners_count_mutex}; MDEBUG("miner::pause: " << m_pausers_count << " -> " << (m_pausers_count + 1)); ++m_pausers_count; if(m_pausers_count == 1 && is_mining()) @@ -425,7 +279,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------------- void miner::resume() { - std::unique_lock lock{m_miners_count_lock}; + std::unique_lock lock{m_miners_count_mutex}; MDEBUG("miner::resume: " << m_pausers_count << " -> " << (m_pausers_count - 1)); --m_pausers_count; if(m_pausers_count < 0) @@ -437,12 +291,11 @@ namespace cryptonote MDEBUG("MINING RESUMED"); } //----------------------------------------------------------------------------------------------------- - bool miner::worker_thread(bool slow_mining) + bool miner::worker_thread(uint32_t index, bool slow_mining) { - uint32_t th_local_index = m_thread_index++; - MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(th_local_index) + "]"); - MGINFO("Miner thread was started ["<< th_local_index << "]"); - uint32_t nonce = m_starter_nonce + th_local_index; + MLOG_SET_THREAD_NAME(std::string("[miner ") + std::to_string(index) + "]"); + MGINFO("Miner thread was started ["<< index << "]"); + uint32_t nonce = m_starter_nonce + index; uint64_t height = 0; difficulty_type local_diff = 0; uint32_t local_template_ver = 0; @@ -467,7 +320,7 @@ namespace cryptonote height = m_height; } local_template_ver = m_template_no; - nonce = m_starter_nonce + th_local_index; + nonce = m_starter_nonce + index; } if(!local_template_ver)//no any set_block_template call @@ -492,23 +345,16 @@ namespace cryptonote if(check_hash(h, local_diff)) { //we lucky! - ++m_config.current_extra_message_index; MGINFO_GREEN("Found block " << get_block_hash(b) << " at height " << height << " for difficulty: " << local_diff); cryptonote::block_verification_context bvc; - if(!m_phandler->handle_block_found(b, bvc) || !bvc.m_added_to_main_chain) - --m_config.current_extra_message_index; - else if (!m_config_dir.empty()) - //success update, lets update config - if (std::string json; epee::serialization::store_t_to_json(m_config, json)) - tools::dump_file(m_config_dir / fs::u8path(MINER_CONFIG_FILE_NAME), json); + m_phandler->handle_block_found(b, bvc); } - nonce+=m_threads_total; + nonce += static_cast(m_threads_total); ++m_hashes; - ++m_total_hashes; } rx_slow_hash_free_state(); - MGINFO("Miner thread stopped ["<< th_local_index << "]"); + MGINFO("Miner thread stopped ["<< index << "]"); if (call_stop) // Call in a detached thread because the thread calling stop() needs to be able to join this // worker thread. diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index 6ce919b2c46..3d291d4eea8 100755 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -70,8 +70,8 @@ namespace cryptonote static void init_options(boost::program_options::options_description& desc); bool set_block_template(const block& bl, const difficulty_type& diffic, uint64_t height, uint64_t block_reward); bool on_block_chain_update(); - bool start(const account_public_address& adr, size_t threads_count, uint64_t stop_after = 0, bool slow_mining = false); - uint64_t get_speed() const; + bool start(const account_public_address& adr, int threads_count, int stop_after = 0, bool slow_mining = false); + double get_speed() const; uint32_t get_threads_count() const; bool stop(); bool is_mining() const; @@ -82,14 +82,12 @@ namespace cryptonote static bool find_nonce_for_given_block(const get_block_hash_t &gbh, block& bl, const difficulty_type& diffic, uint64_t height); void pause(); void resume(); - void do_print_hashrate(bool do_hr); uint64_t get_block_reward() const { return m_block_reward; } private: - bool worker_thread(bool slow_mining = false); + bool worker_thread(uint32_t index, bool slow_mining = false); bool request_block_template(); - void merge_hr(); - void update_autodetection(); + void update_hashrate(); struct miner_config { @@ -105,14 +103,14 @@ namespace cryptonote uint64_t m_stop_height = std::numeric_limits::max(); std::mutex m_template_lock; block m_template; - std::atomic m_template_no; + std::atomic m_template_no = 0; std::atomic m_starter_nonce; - difficulty_type m_diffic; - uint64_t m_height; + difficulty_type m_diffic = 0; + uint64_t m_height = 0; std::atomic m_thread_index; - std::atomic m_threads_total; - std::atomic m_pausers_count; - std::mutex m_miners_count_lock; + std::atomic m_threads_total; + std::atomic m_pausers_count; + std::mutex m_miners_count_mutex; std::list m_threads; std::mutex m_threads_lock; @@ -120,20 +118,14 @@ namespace cryptonote get_block_hash_t m_gbh; account_public_address m_mine_address; tools::periodic_task m_update_block_template_interval{5s}; - tools::periodic_task m_update_merge_hr_interval{2s}; - tools::periodic_task m_autodetect_interval{1s}; - std::vector m_extra_messages; - miner_config m_config; - fs::path m_config_dir; - std::atomic m_last_hr_merge_time; - std::atomic m_hashes; - std::atomic m_total_hashes; - std::atomic m_current_hash_rate; - std::mutex m_last_hash_rates_lock; - std::list m_last_hash_rates; - bool m_do_print_hashrate; - bool m_do_mining; - std::vector> m_threads_autodetect; - std::atomic m_block_reward; + tools::periodic_task m_update_hashrate_interval{2s}; + + mutable std::mutex m_hashrate_mutex; + std::optional m_last_hr_update; + std::atomic m_hashes = 0; + double m_current_hash_rate = 0.0; + + bool m_do_mining = false; + std::atomic m_block_reward = 0; }; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index ea7b356c064..70ab50c1ad8 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -181,7 +181,6 @@ inline constexpr auto POOLDATA_FILENAME = "poolstate.bin"sv; inline constexpr auto BLOCKCHAINDATA_FILENAME = "data.mdb"sv; inline constexpr auto BLOCKCHAINDATA_LOCK_FILENAME = "lock.mdb"sv; inline constexpr auto P2P_NET_DATA_FILENAME = "p2pstate.bin"sv; -inline constexpr auto MINER_CONFIG_FILE_NAME = "miner_conf.json"sv; inline constexpr uint64_t PRUNING_STRIPE_SIZE = 4096; // the smaller, the smoother the increase inline constexpr uint64_t PRUNING_LOG_STRIPES = 3; // the higher, the more space saved diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index a5bf6df4271..cc5cd06597d 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -48,7 +48,6 @@ #include "cryptonote_basic/verification_context.h" #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_core/tx_pool.h" -#include "epee/profile_tools.h" #include "epee/net/network_throttle-detail.hpp" #include "common/pruning.h" #include "common/random.h" diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 8f592eee5a5..4a09e02acc4 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -698,7 +698,7 @@ namespace nodetool for (auto& zone : m_network_zones) { zone.second.m_net_server.get_config_object().set_handler(this); - zone.second.m_net_server.get_config_object().m_invoke_timeout = std::chrono::milliseconds{cryptonote::p2p::DEFAULT_INVOKE_TIMEOUT}.count(); + zone.second.m_net_server.get_config_object().m_invoke_timeout = std::chrono::milliseconds{cryptonote::p2p::DEFAULT_INVOKE_TIMEOUT}; if (!zone.second.m_bind_ip.empty()) { @@ -932,7 +932,7 @@ namespace nodetool LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE(AND CLOSE) INVOKED OK"); } context_ = context; - }, std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT}.count()); + }, std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT}); if(r) { @@ -2077,7 +2077,7 @@ namespace nodetool LOG_PRINT_CC_L0(context_,"try_get_support_flags response"); f(context_, rsp.support_flags); }, - std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT}.count() + std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT} ); return r; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 830df894ac9..125e7032d36 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1439,23 +1439,6 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - SET_LOG_HASH_RATE::response core_rpc_server::invoke(SET_LOG_HASH_RATE::request&& req, rpc_context context) - { - SET_LOG_HASH_RATE::response res{}; - - PERF_TIMER(on_set_log_hash_rate); - if(m_core.get_miner().is_mining()) - { - m_core.get_miner().do_print_hashrate(req.visible); - res.status = STATUS_OK; - } - else - { - res.status = STATUS_NOT_MINING; - } - return res; - } - //------------------------------------------------------------------------------------------------------------------------------ SET_LOG_LEVEL::response core_rpc_server::invoke(SET_LOG_LEVEL::request&& req, rpc_context context) { SET_LOG_LEVEL::response res{}; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index c5f0a7e2527..6bb04e037d4 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -141,7 +141,7 @@ namespace cryptonote::rpc { /// Stores an RPC command callback. These are set up in core_rpc_server.cpp. struct rpc_command { - using result_type = std::variant; + using result_type = std::variant; // Called with the incoming command data; returns the response body if all goes well, // otherwise throws an exception. result_type(*invoke)(rpc_request&&, core_rpc_server&); @@ -234,7 +234,6 @@ namespace cryptonote::rpc { SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); - SET_LOG_HASH_RATE::response invoke(SET_LOG_HASH_RATE::request&& req, rpc_context context); SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); GET_TRANSACTION_POOL::response invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 4ee0869b2d7..0a3e5c14194 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -544,9 +544,9 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_PUBLIC_NODES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_HASH_RATE::request) - KV_SERIALIZE(visible) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_HASH_RATE::request) +// KV_SERIALIZE(visible) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_LEVEL::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 3899c972bbf..19f1c8309f5 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1115,22 +1115,6 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Set the log hash rate display mode. - struct SET_LOG_HASH_RATE : LEGACY - { - static constexpr auto names() { return NAMES("set_log_hash_rate"); } - - struct request - { - bool visible; // States if hash rate logs should be visible (true) or hidden (false) - - KV_MAP_SERIALIZABLE - }; - - struct response : STATUS {}; - }; - BELDEX_RPC_DOC_INTROSPECT // Set the daemon log level. By default, log level is set to `0`. For more fine-tuned logging // control set the set_log_categories command instead. @@ -2848,7 +2832,6 @@ namespace cryptonote::rpc { GET_BLOCK, GET_PEER_LIST, GET_PUBLIC_NODES, - SET_LOG_HASH_RATE, SET_LOG_LEVEL, SET_LOG_CATEGORIES, GET_TRANSACTION_POOL, diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c27510474ba..11c04c71640 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -56,7 +56,6 @@ #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" -#include "epee/profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" #include "serialization/string.h" From f8f3f8f80801640a941c46618bdbcfb6c6490985 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sat, 19 Apr 2025 09:51:50 +0530 Subject: [PATCH 044/182] Remove "support_flags" from p2p protocol It does nothing. This keeps a stub that returns zero so that other nodes can still query us, which can be removed after the next HF. --- src/cryptonote_config.h | 3 - .../cryptonote_protocol_defs.h | 2 - .../cryptonote_protocol_handler.h | 2 +- .../cryptonote_protocol_handler.inl | 32 +++++----- src/p2p/net_node.h | 12 ++-- src/p2p/net_node.inl | 59 ++----------------- src/p2p/net_node_common.h | 8 +-- src/p2p/p2p_protocol_defs.h | 1 + src/rpc/core_rpc_server.cpp | 1 - 9 files changed, 30 insertions(+), 90 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 70ab50c1ad8..dee5969bada 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -161,9 +161,6 @@ namespace p2p { inline constexpr auto IP_BLOCK_TIME = 24h; inline constexpr size_t IP_FAILS_BEFORE_BLOCK = 10; inline constexpr auto IDLE_CONNECTION_KILL_INTERVAL = 5min; - inline constexpr uint32_t SUPPORT_FLAG_FLUFFY_BLOCKS = 0x01; - inline constexpr uint32_t SUPPORT_FLAGS = SUPPORT_FLAG_FLUFFY_BLOCKS; - } // namespace p2p // filename constants: diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index cd3f6112e73..436985f9249 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -83,8 +83,6 @@ namespace cryptonote uint64_t avg_upload; uint64_t current_upload; - uint32_t support_flags; - std::string connection_id; uint64_t height; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index b5216672308..0f92c4e1e1f 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -130,7 +130,7 @@ namespace cryptonote { LOG_PRINT_L3("[" << epee::net_utils::print_connection_context_short(exclude_context) << "] post relay " << tools::type_name() << " -->"); std::vector> connections; - m_p2p->for_each_connection([&exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) + m_p2p->for_each_connection([&exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id) { if (context.m_state > cryptonote_connection_context::state_synchronizing) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index cc5cd06597d..4644d4b8036 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -211,7 +211,6 @@ namespace cryptonote ss << std::setw(30) << std::left << "Remote Host" << std::setw(20) << "Peer id" - << std::setw(20) << "Support Flags" << std::setw(30) << "Recv/Sent (inactive,sec)" << std::setw(25) << "State" << std::setw(20) << "Livetime(sec)" @@ -221,7 +220,7 @@ namespace cryptonote << std::setw(13) << "Up(now)" << "\n"; - m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id, uint32_t support_flags) + m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id) { bool local_ip = cntxt.m_remote_address.is_local(); const auto now = std::chrono::steady_clock::now(); @@ -229,7 +228,6 @@ namespace cryptonote ss << std::setw(30) << std::left << std::string(cntxt.m_is_income ? " [INC]":"[OUT]") + cntxt.m_remote_address.str() << std::setw(20) << nodetool::peerid_to_string(peer_id) - << std::setw(20) << std::hex << support_flags << std::setw(30) << std::to_string(cntxt.m_recv_cnt) + "(" + std::to_string(tools::to_seconds(now - cntxt.m_last_recv)) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(tools::to_seconds(now - cntxt.m_last_send)) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) @@ -270,7 +268,7 @@ namespace cryptonote { std::list connections; - m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id, uint32_t support_flags) + m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id) { connection_info cnx; auto now = std::chrono::steady_clock::now(); @@ -290,8 +288,6 @@ namespace cryptonote cnx.peer_id = nodetool::peerid_to_string(peer_id); - cnx.support_flags = support_flags; - cnx.live_time = std::chrono::duration_cast(now - cntxt.m_started); cnx.recv_idle_time = std::chrono::duration_cast(now - std::max(cntxt.m_started, cntxt.m_last_recv)); cnx.send_idle_time = std::chrono::duration_cast(now - std::max(cntxt.m_started, cntxt.m_last_send)); @@ -1714,7 +1710,7 @@ skip: template void t_cryptonote_protocol_handler::notify_new_stripe(cryptonote_connection_context& cntxt, uint32_t stripe) { - m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool { if (cntxt.m_connection_id == context.m_connection_id) return true; @@ -1774,7 +1770,7 @@ skip: bool t_cryptonote_protocol_handler::kick_idle_peers() { MTRACE("Checking for idle peers..."); - m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool { if (context.m_state == cryptonote_connection_context::state_synchronizing && context.m_last_request_time) { @@ -1805,7 +1801,7 @@ skip: MTRACE("Checking for outgoing syncing peers..."); unsigned n_syncing = 0, n_synced = 0; boost::uuids::uuid last_synced_peer_id(boost::uuids::nil_uuid()); - m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool { if (!peer_id || context.m_is_income) // only consider connected outgoing peers return true; @@ -1838,7 +1834,7 @@ skip: template bool t_cryptonote_protocol_handler::check_standby_peers() { - m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool { if (context.m_state == cryptonote_connection_context::state_standby) { @@ -1993,7 +1989,7 @@ skip: if (next_stripe > 0) { unsigned int n_out_peers = 0, n_peers_on_next_stripe = 0; - m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ + m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id)->bool{ if (!ctx.m_is_income) ++n_out_peers; if (ctx.m_state >= cryptonote_connection_context::state_synchronizing && tools::get_pruning_stripe(ctx.m_pruning_seed) == next_stripe) @@ -2038,7 +2034,7 @@ skip: { // flush stale spans std::set live_connections; - m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool{ live_connections.insert(context.m_connection_id); return true; }); @@ -2418,7 +2414,7 @@ skip: size_t t_cryptonote_protocol_handler::get_synchronizing_connections_count() { size_t count = 0; - m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool{ if(context.m_state == cryptonote_connection_context::state_synchronizing) ++count; return true; @@ -2548,7 +2544,7 @@ skip: { // sort peers between fluffy ones and others std::vector> fluffyConnections; - m_p2p->for_each_connection([&exclude_context, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) + m_p2p->for_each_connection([&exclude_context, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id) { if (peer_id && exclude_context.m_connection_id != context.m_connection_id && context.m_remote_address.get_zone() == epee::net_utils::zone::public_) { @@ -2649,7 +2645,7 @@ skip: { std::ostringstream ss; const auto now = std::chrono::steady_clock::now(); - m_p2p->for_each_connection([&](const connection_context &ctx, nodetool::peerid_type peer_id, uint32_t support_flags) { + m_p2p->for_each_connection([&](const connection_context &ctx, nodetool::peerid_type peer_id) { const uint32_t stripe = tools::get_pruning_stripe(ctx.m_pruning_seed); char state_char = cryptonote::get_protocol_state_char(ctx.m_state); ss << stripe + state_char; @@ -2677,7 +2673,7 @@ skip: // if we already have a few peers on this stripe, but none on next one, try next one unsigned int n_next = 0, n_subsequent = 0, n_others = 0; const uint32_t subsequent_pruning_stripe = 1 + next_pruning_stripe % (1<for_each_connection([&](const connection_context &context, nodetool::peerid_type peer_id, uint32_t support_flags) { + m_p2p->for_each_connection([&](const connection_context &context, nodetool::peerid_type peer_id) { if (context.m_state >= cryptonote_connection_context::state_synchronizing) { if (context.m_pruning_seed == 0 || tools::get_pruning_stripe(context.m_pruning_seed) == next_pruning_stripe) @@ -2708,7 +2704,7 @@ skip: if (target && target <= height) return false; size_t n_out_peers = 0; - m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ + m_p2p->for_each_connection([&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id)->bool{ if (!ctx.m_is_income) ++n_out_peers; return true; @@ -2747,7 +2743,7 @@ skip: void t_cryptonote_protocol_handler::on_connection_close(cryptonote_connection_context &context) { uint64_t target = 0; - m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id, uint32_t support_flags) { + m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id) { MINFO("DEBUGconnection state:" << cntxt.m_state << " cntxtId:" << cntxt.m_connection_id << " context:"<= cryptonote_connection_context::state_synchronizing && cntxt.m_connection_id != context.m_connection_id){ target = std::max(target, cntxt.m_remote_blockchain_height); diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 04fa6a0d4a7..b2c621640f5 100755 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -109,10 +109,9 @@ namespace nodetool template struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base { - p2p_connection_context_t(): peer_id(0), support_flags(0), m_in_timedsync(false) {} + p2p_connection_context_t(): peer_id(0), m_in_timedsync(false) {} peerid_type peer_id; - uint32_t support_flags; bool m_in_timedsync; std::set sent_addresses; }; @@ -140,7 +139,6 @@ namespace nodetool { network_config m_net_config{}; uint64_t m_peer_id{crypto::rand()}; - uint32_t m_support_flags{0}; }; struct network_zone @@ -208,7 +206,6 @@ namespace nodetool m_config.m_net_config.connection_timeout = cryptonote::p2p::DEFAULT_CONNECTION_TIMEOUT; m_config.m_net_config.ping_connection_timeout = cryptonote::p2p::DEFAULT_PING_CONNECTION_TIMEOUT; m_config.m_net_config.send_peerlist_sz = cryptonote::p2p::DEFAULT_PEERS_IN_HANDSHAKE; - m_config.m_support_flags = 0; // only set in public zone } }; @@ -281,6 +278,7 @@ namespace nodetool HANDLE_INVOKE_T2(COMMAND_HANDSHAKE, handle_handshake) HANDLE_INVOKE_T2(COMMAND_TIMED_SYNC, handle_timed_sync) HANDLE_INVOKE_T2(COMMAND_PING, handle_ping) + // TODO: remove after HF HANDLE_INVOKE_T2(COMMAND_REQUEST_SUPPORT_FLAGS, handle_get_support_flags) CHAIN_INVOKE_MAP_TO_OBJ_FORCE_CONTEXT(m_payload_handler, typename t_payload_net_handler::connection_context&) END_INVOKE_MAP2() @@ -291,6 +289,7 @@ namespace nodetool int handle_handshake(int command, typename COMMAND_HANDSHAKE::request& arg, typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context); int handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context); int handle_ping(int command, COMMAND_PING::request& arg, COMMAND_PING::response& rsp, p2p_connection_context& context); + // TODO: remove after HF int handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context); bool init_config(); bool make_default_peer_id(); @@ -309,8 +308,8 @@ namespace nodetool virtual bool invoke_notify_to_peer(int command, const epee::span req_buff, const epee::net_utils::connection_context_base& context); virtual bool drop_connection(const epee::net_utils::connection_context_base& context); virtual void request_callback(const epee::net_utils::connection_context_base& context); - virtual void for_each_connection(std::function f); - virtual bool for_connection(const boost::uuids::uuid&, std::function f); + virtual void for_each_connection(std::function f); + virtual bool for_connection(const boost::uuids::uuid&, std::function f); virtual bool add_host_fail(const epee::net_utils::network_address &address); //----------------- i_connection_filter -------------------------------------------------------- virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL); @@ -340,7 +339,6 @@ namespace nodetool bool is_addr_connected(const epee::net_utils::network_address& peer); template bool try_ping(basic_node_data& node_data, p2p_connection_context& context, const t_callback &cb); - bool try_get_support_flags(const p2p_connection_context& context, std::function f); bool make_expected_connections_count(network_zone& zone, PeerType peer_type, size_t expected_connections); void record_addr_failed(const epee::net_utils::network_address& addr); bool is_addr_recently_failed(const epee::net_utils::network_address& addr); diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 4a09e02acc4..507f7ea271d 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -119,7 +119,6 @@ namespace nodetool if (storage) m_peerlist_storage = std::move(*storage); - m_network_zones[epee::net_utils::zone::public_].m_config.m_support_flags = cryptonote::p2p::SUPPORT_FLAGS; m_first_connection_maker_call = true; CATCH_ENTRY_L0("node_server::init_config", false); @@ -127,23 +126,23 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - void node_server::for_each_connection(std::function f) + void node_server::for_each_connection(std::function f) { for(auto& zone : m_network_zones) { zone.second.m_net_server.get_config_object().foreach_connection([&](p2p_connection_context& cntx){ - return f(cntx, cntx.peer_id, cntx.support_flags); + return f(cntx, cntx.peer_id); }); } } //----------------------------------------------------------------------------------- template - bool node_server::for_connection(const boost::uuids::uuid &connection_id, std::function f) + bool node_server::for_connection(const boost::uuids::uuid &connection_id, std::function f) { for(auto& zone : m_network_zones) { const bool result = zone.second.m_net_server.get_config_object().for_connection(connection_id, [&](p2p_connection_context& cntx){ - return f(cntx, cntx.peer_id, cntx.support_flags); + return f(cntx, cntx.peer_id); }); if (result) return true; @@ -945,15 +944,6 @@ namespace nodetool if (!timeout) zone.m_net_server.get_config_object().close(context_.m_connection_id); } - else if (!just_take_peerlist) - { - LOG_PRINT_L0("do_handshake_with_peer try_get_support_flags"); - try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) - { - flags_context.support_flags = support_flags; - }); - } - return hsh_result; } //----------------------------------------------------------------------------------- @@ -1824,7 +1814,7 @@ namespace nodetool int node_server::handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context) { LOG_PRINT_CC_L0(context, "handle_get_support_flags"); - rsp.support_flags = m_network_zones.at(context.m_remote_address.get_zone()).m_config.m_support_flags; + rsp.support_flags = 0; return 1; } //----------------------------------------------------------------------------------- @@ -2051,39 +2041,6 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - bool node_server::try_get_support_flags(const p2p_connection_context& context, std::function f) - { - LOG_PRINT_CC_L0(context,"try_get_support_flags"); - if(context.m_remote_address.get_zone() != epee::net_utils::zone::public_) - return false; - - - using request_t = typename COMMAND_REQUEST_SUPPORT_FLAGS::request; - using response_t = typename COMMAND_REQUEST_SUPPORT_FLAGS::response; - request_t support_flags_request{}; - bool r = epee::net_utils::async_invoke_remote_command2 - ( - context.m_connection_id, - COMMAND_REQUEST_SUPPORT_FLAGS::ID, - support_flags_request, - m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object(), - [=](int code, const response_t& rsp, p2p_connection_context& context_) - { - if(code < 0) - { - LOG_PRINT_CC_L0(context_, "COMMAND_REQUEST_SUPPORT_FLAGS invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); - return; - } - LOG_PRINT_CC_L0(context_,"try_get_support_flags response"); - f(context_, rsp.support_flags); - }, - std::chrono::milliseconds{cryptonote::p2p::DEFAULT_HANDSHAKE_INVOKE_TIMEOUT} - ); - - return r; - } - //----------------------------------------------------------------------------------- - template int node_server::handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) { if(!m_payload_handler.process_payload_sync_data(std::move(arg.payload_data), context, false)) @@ -2221,12 +2178,6 @@ namespace nodetool LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); }); } - LOG_PRINT_CCONTEXT_L0("COMMAND_HANDSHAKE try_get_support_flags"); - try_get_support_flags(context, [](p2p_connection_context& flags_context, const uint32_t& support_flags) - { - LOG_PRINT_L0("COMMAND_HANDSHAKE support_flags"); - flags_context.support_flags = support_flags; - }); //fill response LOG_PRINT_CCONTEXT_L0("COMMAND_HANDSHAKE get_peerlist_head"); diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 7d48ff98445..f1ce9d5b2e2 100755 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -54,8 +54,8 @@ namespace nodetool virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0; virtual void request_callback(const epee::net_utils::connection_context_base& context)=0; virtual uint64_t get_public_connections_count()=0; - virtual void for_each_connection(std::function f)=0; - virtual bool for_connection(const boost::uuids::uuid&, std::function f)=0; + virtual void for_each_connection(std::function f)=0; + virtual bool for_connection(const boost::uuids::uuid&, std::function f)=0; virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0; virtual bool unblock_host(const epee::net_utils::network_address &address)=0; virtual std::map get_blocked_hosts()=0; @@ -93,11 +93,11 @@ namespace nodetool { } - virtual void for_each_connection(std::function f) + virtual void for_each_connection(std::function f) { } - virtual bool for_connection(const boost::uuids::uuid&, std::function f) + virtual bool for_connection(const boost::uuids::uuid&, std::function f) { return false; } diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 9ccff1d4aea..b38cb86d026 100755 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -276,6 +276,7 @@ namespace nodetool /************************************************************************/ /* */ /************************************************************************/ + // TODO: remove after HF struct COMMAND_REQUEST_SUPPORT_FLAGS { const static int ID = P2P_COMMANDS_POOL_BASE + 7; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 125e7032d36..fc4d6b1bdf3 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2123,7 +2123,6 @@ namespace cryptonote::rpc { {"current_download", ci.current_download}, {"avg_upload", ci.avg_upload}, {"current_upload", ci.current_upload}, - {"support_flags", ci.support_flags}, {"connection_id", ci.connection_id}, {"height", ci.height}, }; From 4924470ed1ce3abf2b98c7af78f8621cb95ded2e Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Sat, 19 Apr 2025 10:02:46 +0530 Subject: [PATCH 045/182] START_MINING fixes - parse args,fix the slow_mining parameter not being passed through to the miner --- src/cryptonote_basic/miner.cpp | 4 ++-- src/rpc/core_rpc_server.cpp | 20 ++++++++------------ src/rpc/core_rpc_server_command_parser.cpp | 5 +++++ src/rpc/core_rpc_server_commands_defs.h | 12 ++++++++++-- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index c6277dcfb68..d4d1f3b56ac 100755 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -197,8 +197,8 @@ namespace cryptonote request_block_template();//lets update block template m_stop = false; - m_stop_height = stop_after ? m_height + stop_after : std::numeric_limits::max(); - if (stop_after) + m_stop_height = stop_after > 0 ? m_height + stop_after : std::numeric_limits::max(); + if (stop_after > 0) MGINFO("Mining until height " << m_stop_height); for (int i = 0; i < m_threads_total; i++) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index fc4d6b1bdf3..10438b0da76 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1250,9 +1250,8 @@ namespace cryptonote::rpc { } cryptonote::address_parse_info info; - if(!get_account_address_from_str(info, m_core.get_nettype(), start_mining.request['miner_address'])) - { - start_mining.response["status"] = "Failed, wrong address"; + if(!get_account_address_from_str(info, m_core.get_nettype(), start_mining.request.miner_address)){ + start_mining.response["status"] = "Failed, invalid address"; LOG_PRINT_L0(start_mining.response["status"]); return; } @@ -1263,24 +1262,21 @@ namespace cryptonote::rpc { return; } - unsigned int concurrency_count = std::thread::hardware_concurrency() * 4; + int max_concurrency_count = std::thread::hardware_concurrency() * 4; // if we couldn't detect threads, set it to a ridiculously high number - if(concurrency_count == 0) - { - concurrency_count = 257; - } + if(max_concurrency_count == 0) + max_concurrency_count = 257; // if there are more threads requested than the hardware supports // then we fail and log that. - if(start_mining.request["threads_count"] > concurrency_count) - { + if (start_mining.request.threads_count > max_concurrency_count) { start_mining.response["status"] = "Failed, too many threads relative to CPU cores."; LOG_PRINT_L0(start_mining.response["status"]); return; } - cryptonote::miner &miner= m_core.get_miner(); + auto& miner = m_core.get_miner(); if (miner.is_mining()) { start_mining.response["status"] = "Already mining"; @@ -1288,7 +1284,7 @@ namespace cryptonote::rpc { return; } - if(!miner.start(info.address, static_cast(start_mining.request["threads_count"]), start_mining.request["num_blocks"])) + if(!miner.start(info.address, start_mining.request.threads_count, start_mining.request.num_blocks, start_mining.request.slow_mining)) { start_mining.response["status"] = "Failed, mining not started"; LOG_PRINT_L0(start_mining.response["status"]); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 8a554715f68..a9b0dbcb720 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -245,6 +245,11 @@ namespace cryptonote::rpc { } void parse_request(START_MINING& start_mining, rpc_input in) { + get_values(in, + "miner_address", required{start_mining.request.miner_address}, + "num_blocks", start_mining.request.num_blocks, + "slow_mining", start_mining.request.slow_mining, + "threads_count", start_mining.request.threads_count); } void parse_request(STOP_MINING& stop_mining, rpc_input in) { } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 19f1c8309f5..c29919ce23d 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,6 +49,7 @@ #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "cryptonote_basic/cryptonote_basic.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/verification_context.h" #include "cryptonote_basic/difficulty.h" #include "crypto/hash.h" @@ -633,9 +634,9 @@ namespace cryptonote::rpc { /// Inputs: /// /// - \p miner_address Account address to mine to. - /// - \p threads_count Number of mining threads to run. + /// - \p threads_count Number of mining threads to run. Defaults to 1 thread if omitted or 0. /// - \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). - /// - \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily inteded for testing. + /// - \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily intended for testing. /// /// Output values available from a restricted/admin RPC endpoint: /// @@ -643,6 +644,13 @@ namespace cryptonote::rpc { struct START_MINING : LEGACY { static constexpr auto names() { return NAMES("start_mining"); } + + struct request_parameters { + std::string miner_address; + int threads_count = 1; + int num_blocks = 0; + bool slow_mining = false; + } request; }; //----------------------------------------------- From b630551928e1d54662c065df73985464730fa28d Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sat, 19 Apr 2025 10:08:46 +0530 Subject: [PATCH 046/182] Fix mining status commands --- src/daemon/rpc_command_executor.cpp | 62 +++++++++++++---------------- src/rpc/core_rpc_server.cpp | 2 +- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 463640f40db..e27a0783464 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -486,8 +486,8 @@ bool rpc_command_executor::show_status() { HARD_FORK_INFO::request hfreq{}; HARD_FORK_INFO::response hfres{}; - MINING_STATUS::response mres{}; - bool has_mining_info = false; + bool has_mining_info = false, mining_active = false; + long mining_hashrate = 0; hfreq.version = 0; bool mining_busy = false; @@ -498,14 +498,21 @@ bool rpc_command_executor::show_status() { if (auto it = info.find("start_time"); it != info.end() && it->get() > 0) // This will only be non-null if we were recognized as admin (which we need for mining info) { restricted_response = true; - has_mining_info = invoke({}, mres, "Failed to retrieve mining info", false); - if (has_mining_info) { - if (mres.status == STATUS_BUSY) + if (auto maybe_mining_info = try_running([this] { return invoke(false); }, "Failed to retrieve mining info")) { + has_mining_info = true; + auto& mres = *maybe_mining_info; + if (mres["status"] == STATUS_BUSY) mining_busy = true; - else if (mres.status != STATUS_OK) { + else if (mres["status"] != STATUS_OK) { tools::fail_msg_writer() << "Failed to retrieve mining info"; return false; + } else { + mining_active = mres["active"].get(); + if (mining_active) + mining_hashrate = mres["speed"].get(); } + } else { + return false; } } @@ -568,8 +575,8 @@ bool rpc_command_executor::show_status() { if (hfres.version < cryptonote::feature::POS && !has_mining_info) str << ", mining info unavailable"; - if (has_mining_info && !mining_busy && mres.active) - str << ", mining at " << get_mining_speed(mres.speed); + if (has_mining_info && !mining_busy && mining_active) + str << ", mining at " << get_mining_speed(mining_hashrate); if (hfres.version < cryptonote::feature::POS) str << ", net hash " << get_mining_speed(info["difficulty"].get() / info["target"].get()); @@ -632,42 +639,27 @@ bool rpc_command_executor::show_status() { } bool rpc_command_executor::mining_status() { - MINING_STATUS::response mres{}; - - if (!invoke({}, mres, "Failed to retrieve mining info", false)) + auto maybe_mining_info = try_running([this] { return invoke(false); }, "Failed to retrieve mining info"); + if (!maybe_mining_info) return false; bool mining_busy = false; - if (mres.status == STATUS_BUSY) - { + auto& mres = *maybe_mining_info; + if (mres["status"] == STATUS_BUSY) mining_busy = true; - } - else if (mres.status != STATUS_OK) - { + else if (mres["status"] != STATUS_OK) { tools::fail_msg_writer() << "Failed to retrieve mining info"; return false; } - - if (mining_busy || !mres.active) - { + bool active = mres["active"].get(); + long speed = mres["speed"].get(); + if (mining_busy || !active) tools::msg_writer() << "Not currently mining"; + else { + tools::msg_writer() << "Mining at " << get_mining_speed(speed) << " with " << mres["threads_count"].get() << " threads"; + tools::msg_writer() << "Mining address: " << mres["address"].get(); } - else - { - tools::msg_writer() << "Mining at " << get_mining_speed(mres.speed) << " with " << mres.threads_count << " threads"; - } - - tools::msg_writer() << "PoW algorithm: " << mres.pow_algorithm; - if (mres.active) - { - tools::msg_writer() << "Mining address: " << mres.address; - } - - if (!mining_busy && mres.active && mres.speed > 0 && mres.block_target > 0 && mres.difficulty > 0) - { - uint64_t daily = 86400 / (double)mres.difficulty * mres.speed * mres.block_reward; - tools::msg_writer() << "Expected: " << cryptonote::print_money(daily) << " BELDEX daily, " << cryptonote::print_money(7*daily) << " weekly"; - } + tools::msg_writer() << "PoW algorithm: " << mres["pow_algorithm"].get(); return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 10438b0da76..679572c8c84 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1326,7 +1326,7 @@ namespace cryptonote::rpc { mining_status.response["block_target"] = tools::to_seconds(old::TARGET_BLOCK_TIME_12); // old_block_time mining_status.response["difficulty"] = m_core.get_blockchain_storage().get_difficulty_for_next_block(false /*POS*/); if ( lMiner.is_mining() ) { - mining_status.response["speed"] = lMiner.get_speed(); + mining_status.response["speed"] = std::lround(lMiner.get_speed()); mining_status.response["threads_count"] = lMiner.get_threads_count(); mining_status.response["block_reward"] = lMiner.get_block_reward(); } From 20590963a798b202b2caba1e0e93db90ac37562d Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Sat, 19 Apr 2025 10:16:39 +0530 Subject: [PATCH 047/182] small random epee improvements. --- contrib/epee/include/epee/net/enums.h | 29 +++++++++++----- .../epee/net/network_throttle-detail.hpp | 34 +++++++++---------- .../include/epee/net/network_throttle.hpp | 3 +- contrib/epee/src/levin_base.cpp | 1 + contrib/epee/src/net_utils_base.cpp | 13 +++++-- contrib/epee/src/network_throttle-detail.cpp | 6 ++-- src/p2p/net_node.inl | 10 +++--- src/rpc/core_rpc_server.cpp | 15 ++++---- 8 files changed, 65 insertions(+), 46 deletions(-) diff --git a/contrib/epee/include/epee/net/enums.h b/contrib/epee/include/epee/net/enums.h index 51029e0319d..165815f9ea5 100755 --- a/contrib/epee/include/epee/net/enums.h +++ b/contrib/epee/include/epee/net/enums.h @@ -30,13 +30,11 @@ #include #include +#include -namespace epee -{ -using namespace std::literals; - -namespace net_utils +namespace epee::net_utils { + using namespace std::literals; enum class address_type : std::uint8_t { // Do not change values, this will break serialization @@ -55,10 +53,19 @@ namespace net_utils tor = 3 }; - // implementations in src/net_utils_base.cpp + //! \return String name of address type + constexpr std::string_view to_string(address_type a) noexcept { + switch (a) { + case address_type::ipv4: return "IPv4"sv; + case address_type::ipv6: return "IPv6"sv; + case address_type::i2p: return "I2P"sv; + case address_type::tor: return "Tor"sv; + default: return "invalid"sv; + } + } //! \return String name of zone or "invalid" on error. - constexpr std::string_view zone_to_string(zone value) noexcept { + constexpr std::string_view to_string(zone value) noexcept { switch(value) { case zone::public_: return "public"sv; case zone::i2p: return "i2p"sv; @@ -74,6 +81,10 @@ namespace net_utils if (value == "tor"sv) return zone::tor; return zone::invalid; } -} // net_utils -} // epee + // implementations in src/net_utils_base.cpp + + std::ostream& operator<<(std::ostream& o, address_type a); + std::ostream& operator<<(std::ostream& o, zone z); + +} // epee::net_utils diff --git a/contrib/epee/include/epee/net/network_throttle-detail.hpp b/contrib/epee/include/epee/net/network_throttle-detail.hpp index fde1e3f46c9..b790e1a4d53 100755 --- a/contrib/epee/include/epee/net/network_throttle-detail.hpp +++ b/contrib/epee/include/epee/net/network_throttle-detail.hpp @@ -77,33 +77,33 @@ class network_throttle : public i_network_throttle { public: network_throttle(const std::string &nameshort, const std::string &name, int window_size=-1); virtual ~network_throttle(); - virtual void set_name(const std::string &name); - virtual void set_target_speed( network_speed_kbps target ); - virtual network_speed_kbps get_target_speed(); + void set_name(const std::string &name) override; + void set_target_speed( network_speed_kbps target ) override; + network_speed_kbps get_target_speed() override; // add information about events: - virtual void handle_trafic_exact(size_t packet_size); ///< count the new traffic/packet; the size is exact considering all network costs - virtual void handle_trafic_tcp(size_t packet_size); ///< count the new traffic/packet; the size is as TCP, we will consider MTU etc + void handle_trafic_exact(size_t packet_size) override; ///< count the new traffic/packet; the size is exact considering all network costs + void handle_trafic_tcp(size_t packet_size) override; ///< count the new traffic/packet; the size is as TCP, we will consider MTU etc - virtual void tick(); ///< poke and update timers/history (recalculates, moves the history if needed, checks the real clock etc) + void tick() override; ///< poke and update timers/history (recalculates, moves the history if needed, checks the real clock etc) - virtual double get_time_seconds() const ; ///< timer that we use, time in seconds, monotionic + double get_time_seconds() const override; ///< timer that we use, time in seconds, monotionic // time calculations: - virtual void calculate_times(size_t packet_size, calculate_times_struct &cts, bool dbg, double force_window) const; ///< MAIN LOGIC (see base class for info) + void calculate_times(size_t packet_size, calculate_times_struct &cts, bool dbg, double force_window) const override; ///< MAIN LOGIC (see base class for info) - virtual network_time_seconds get_sleep_time_after_tick(size_t packet_size); ///< increase the timer if needed, and get the package size - virtual network_time_seconds get_sleep_time(size_t packet_size) const; ///< gets the Delay (recommended Delay time) from calc. (not safe: only if time didnt change?) TODO + network_time_seconds get_sleep_time_after_tick(size_t packet_size) override; ///< increase the timer if needed, and get the package size + network_time_seconds get_sleep_time(size_t packet_size) const override; ///< gets the Delay (recommended Delay time) from calc. (not safe: only if time didnt change?) TODO - virtual size_t get_recommended_size_of_planned_transport() const; ///< what should be the size (bytes) of next data block to be transported - virtual size_t get_recommended_size_of_planned_transport_window(double force_window) const; ///< ditto, but for given windows time frame - virtual double get_current_speed() const; - virtual void get_stats(uint64_t &total_packets, uint64_t &total_bytes) const; + size_t get_recommended_size_of_planned_transport() const override; ///< what should be the size (bytes) of next data block to be transported + size_t get_recommended_size_of_planned_transport_window(double force_window) const; ///< ditto, but for given windows time frame + double get_current_speed() const; + std::pair get_stats() const override; private: - virtual network_time_seconds time_to_slot(network_time_seconds t) const { return std::floor( t ); } // convert exact time eg 13.7 to rounded time for slot number in history 13 - virtual void _handle_trafic_exact(size_t packet_size, size_t orginal_size); - virtual void logger_handle_net(const std::string &filename, double time, size_t size); + network_time_seconds time_to_slot(network_time_seconds t) const { return std::floor( t ); } // convert exact time eg 13.7 to rounded time for slot number in history 13 + void _handle_trafic_exact(size_t packet_size, size_t orginal_size); + void logger_handle_net(const std::string &filename, double time, size_t size) override; }; /*** diff --git a/contrib/epee/include/epee/net/network_throttle.hpp b/contrib/epee/include/epee/net/network_throttle.hpp index 11982cf8a2f..187f186e32e 100755 --- a/contrib/epee/include/epee/net/network_throttle.hpp +++ b/contrib/epee/include/epee/net/network_throttle.hpp @@ -119,7 +119,8 @@ class i_network_throttle { virtual double get_time_seconds() const =0; // a timer virtual void logger_handle_net(const std::string &filename, double time, size_t size)=0; - virtual void get_stats(uint64_t &total_packets, uint64_t &total_bytes) const =0; + // Returns {total packets, total bytes} + virtual std::pair get_stats() const =0; }; diff --git a/contrib/epee/src/levin_base.cpp b/contrib/epee/src/levin_base.cpp index e8e876abd16..cc4a2e41695 100755 --- a/contrib/epee/src/levin_base.cpp +++ b/contrib/epee/src/levin_base.cpp @@ -31,6 +31,7 @@ #include "epee/int-util.h" +using namespace std::literals; namespace epee { namespace levin diff --git a/contrib/epee/src/net_utils_base.cpp b/contrib/epee/src/net_utils_base.cpp index 746c2d5e911..e264b8c0278 100755 --- a/contrib/epee/src/net_utils_base.cpp +++ b/contrib/epee/src/net_utils_base.cpp @@ -5,8 +5,9 @@ #include "epee/string_tools.h" #include "epee/net/local_ip.h" +#include "epee/net/enums.h" -namespace epee { namespace net_utils +namespace epee::net_utils { bool ipv4_network_address::equal(const ipv4_network_address& other) const noexcept { return is_same_host(other) && port() == other.port(); } @@ -104,5 +105,13 @@ namespace epee { namespace net_utils ss << ctx.m_remote_address.str() << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } -}} + std::ostream& operator<<(std::ostream& o, address_type a) + { + return o << to_string(a); + } + std::ostream& operator<<(std::ostream& o, zone z) + { + return o << to_string(z); + } +} // namespace epee::net_utils diff --git a/contrib/epee/src/network_throttle-detail.cpp b/contrib/epee/src/network_throttle-detail.cpp index 4683285c238..8f095971bb4 100755 --- a/contrib/epee/src/network_throttle-detail.cpp +++ b/contrib/epee/src/network_throttle-detail.cpp @@ -342,10 +342,8 @@ double network_throttle::get_current_speed() const { return bytes_transferred / ((m_history.size() - 1) * m_slot_size); } -void network_throttle::get_stats(uint64_t &total_packets, uint64_t &total_bytes) const { - total_packets = m_total_packets; - total_bytes = m_total_bytes; -} +std::pair network_throttle::get_stats() const { + return {m_total_packets, m_total_bytes}; } // namespace diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 507f7ea271d..a4d08bb1766 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -445,7 +445,7 @@ namespace nodetool network_zone& zone = add_zone(proxy.zone); if (zone.m_connect != nullptr) { - MERROR("Listed --" << arg_tx_proxy.name << " twice with " << epee::net_utils::zone_to_string(proxy.zone)); + MERROR("Listed --" << arg_tx_proxy.name << " twice with " << proxy.zone); return false; } zone.m_connect = &socks_connect; @@ -473,7 +473,7 @@ namespace nodetool { if (zone.second.m_connect == nullptr) { - MERROR("Set outgoing peer for " << epee::net_utils::zone_to_string(zone.first) << " but did not set --" << arg_tx_proxy.name); + MERROR("Set outgoing peer for " << zone.first << " but did not set --" << arg_tx_proxy.name); return false; } } @@ -489,7 +489,7 @@ namespace nodetool if (!zone.m_bind_ip.empty()) { - MERROR("Listed --" << arg_anonymous_inbound.name << " twice with " << epee::net_utils::zone_to_string(inbound.our_address.get_zone()) << " network"); + MERROR("Listed --" << arg_anonymous_inbound.name << " twice with " << inbound.our_address.get_zone() << " network"); return false; } @@ -1835,8 +1835,8 @@ namespace nodetool { if (zone == m_network_zones.end()) { - MWARNING("Unable to relay all messages, " << epee::net_utils::zone_to_string(c_id.first) << " not available"); - return false; + MWARNING("Unable to relay all messages, " << c_id.first << " not available"); + return false; } if (c_id.first <= zone->first) break; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 679572c8c84..c2720f18cde 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -39,6 +39,7 @@ #include #include #include +#include "epee/net/network_throttle.hpp" #include "common/string_util.h" #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" @@ -464,17 +465,15 @@ namespace cryptonote::rpc { get_net_stats.response["start_time"] = m_core.get_start_time(); { std::lock_guard lock{epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in}; - uint64_t total_packets_in, total_bytes_in; - epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(total_packets_in, total_bytes_in); - get_net_stats.response["total_packets_in"] = total_packets_in; - get_net_stats.response["total_bytes_in"] = total_bytes_in; + auto [packets, bytes] = epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(); + get_net_stats.response["total_packets_in"] = packets; + get_net_stats.response["total_bytes_in"] = bytes; } { std::lock_guard lock{epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_out}; - uint64_t total_packets_out, total_bytes_out; - epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(total_packets_out, total_bytes_out); - get_net_stats.response["total_packets_out"] = total_packets_out; - get_net_stats.response["total_bytes_out"] = total_bytes_out; + auto [packets, bytes] = epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(); + get_net_stats.response["total_packets_out"] = packets; + get_net_stats.response["total_bytes_out"] = bytes; } get_net_stats.response["status"] = STATUS_OK; } From 83c7c4d7c50b54252b688d0bd271b792e48815a3 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Sat, 19 Apr 2025 10:20:55 +0530 Subject: [PATCH 048/182] Remove hide_hr, show_hr commands --- src/common/string_util.h | 18 ++++++++++++++++++ src/daemon/command_parser_executor.cpp | 14 -------------- src/daemon/command_parser_executor.h | 4 ---- src/daemon/command_server.cpp | 10 ---------- src/daemon/rpc_command_executor.cpp | 26 -------------------------- src/daemon/rpc_command_executor.h | 4 ---- 6 files changed, 18 insertions(+), 58 deletions(-) diff --git a/src/common/string_util.h b/src/common/string_util.h index fd61b5bbdd3..c0def3ec4b3 100755 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -92,6 +92,24 @@ std::string join_transform(std::string_view delimiter, const Container& c, Unary return join_transform(delimiter, c.begin(), c.end(), std::forward(transform)); } +/// Concatenates a bunch of random values together with delim as a separator via << operator. +/// Returns the result as a string. +template +std::string join_stuff(std::string_view delim, T&& first, Ts&&... stuff) { + std::ostringstream o; + o << std::forward(first); + ((o << delim << std::forward(stuff)), ...); + return o.str(); +} + +/// Concatenates arguments via << operator, returns as a string. +template +std::string concat(T&&... stuff) { + std::ostringstream o; + (o << ... << std::forward(stuff)); + return o.str(); +} + /// Simple version of whitespace trimming: mutates the given string view to remove leading /// space, \t, \r, \n. (More exotic and locale-dependent whitespace is not removed). void trim(std::string_view& s); diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 4aec97f1db3..186fe74e604 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -173,20 +173,6 @@ bool command_parser_executor::save_blockchain(const std::vector& ar return m_executor.save_blockchain(); } -bool command_parser_executor::show_hash_rate(const std::vector& args) -{ - if (!args.empty()) return false; - - return m_executor.show_hash_rate(); -} - -bool command_parser_executor::hide_hash_rate(const std::vector& args) -{ - if (!args.empty()) return false; - - return m_executor.hide_hash_rate(); -} - bool command_parser_executor::show_difficulty(const std::vector& args) { if (!args.empty()) return false; diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index d7c6348f777..953815a6bcb 100755 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -51,10 +51,6 @@ class command_parser_executor final bool save_blockchain(const std::vector& args); - bool show_hash_rate(const std::vector& args); - - bool hide_hash_rate(const std::vector& args); - bool show_difficulty(const std::vector& args); bool show_status(const std::vector& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 5e51b9c32eb..ff3f2b83fb2 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -174,16 +174,6 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) , [this](const auto &x) { return m_parser.print_transaction_pool_stats(x); } , "Print the transaction pool's statistics." ); - m_command_lookup.set_handler( - "show_hr" - , [this](const auto &x) { return m_parser.show_hash_rate(x); } - , "Start showing the current hash rate." - ); - m_command_lookup.set_handler( - "hide_hr" - , [this](const auto &x) { return m_parser.hide_hash_rate(x); } - , "Stop showing the hash rate." - ); m_command_lookup.set_handler( "save" , [this](const auto &x) { return m_parser.save_blockchain(x); } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index e27a0783464..f1ff6799091 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -398,32 +398,6 @@ bool rpc_command_executor::save_blockchain() { return invoke_simple("Couldn't save blockchain", "Blockchain saved"); } -bool rpc_command_executor::show_hash_rate() { - SET_LOG_HASH_RATE::request req{}; - SET_LOG_HASH_RATE::response res{}; - req.visible = true; - - if (!invoke(std::move(req), res, "Couldn't enable hash rate logging")) - return false; - - tools::success_msg_writer() << "Hash rate logging is on"; - - return true; -} - -bool rpc_command_executor::hide_hash_rate() { - SET_LOG_HASH_RATE::request req{}; - SET_LOG_HASH_RATE::response res{}; - req.visible = false; - - if (!invoke(std::move(req), res, "Couldn't disable hash rate logging")) - return false; - - tools::success_msg_writer() << "Hash rate logging is off"; - - return true; -} - bool rpc_command_executor::show_difficulty() { auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); if (!maybe_info) diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 665f839ed1f..66dde31fd0d 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -154,10 +154,6 @@ class rpc_command_executor final { bool save_blockchain(); - bool show_hash_rate(); - - bool hide_hash_rate(); - bool show_difficulty(); bool show_status(); From 9b827a0fa79b3420d20708d70f48a750a9b99475 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sat, 19 Apr 2025 10:28:08 +0530 Subject: [PATCH 049/182] Remove debugging & superfluous 'return;'s --- src/rpc/core_rpc_server.cpp | 18 ------------------ src/rpc/rpc_binary.h | 9 +++++++++ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index c2720f18cde..74a171d7e8c 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -697,7 +697,6 @@ namespace cryptonote::rpc { } get_outputs.response["status"] = STATUS_OK; - return; } //------------------------------------------------------------------------------------------------------------------------------ GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response core_rpc_server::invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context) @@ -1244,7 +1243,6 @@ namespace cryptonote::rpc { if(!check_core_ready()){ start_mining.response["status"] = STATUS_BUSY; - LOG_PRINT_L0(start_mining.response["status"]); return; } @@ -1279,7 +1277,6 @@ namespace cryptonote::rpc { if (miner.is_mining()) { start_mining.response["status"] = "Already mining"; - LOG_PRINT_L0(start_mining.response["status"]); return; } @@ -1312,8 +1309,6 @@ namespace cryptonote::rpc { return; } stop_mining.response["status"] = STATUS_OK; - LOG_PRINT_L0(stop_mining.response["status"]); - return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(MINING_STATUS& mining_status, rpc_context context) @@ -1340,8 +1335,6 @@ namespace cryptonote::rpc { "Cryptonight Heavy (Variant 2)"; mining_status.response["status"] = STATUS_OK; - LOG_PRINT_L0(mining_status.response["status"]); - return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(SAVE_BC& save_bc, rpc_context context) @@ -1354,8 +1347,6 @@ namespace cryptonote::rpc { return; } save_bc.response["status"] = STATUS_OK; - LOG_PRINT_L0(save_bc.response["status"]); - return; } //------------------------------------------------------------------------------------------------------------------------------ GET_PEER_LIST::response core_rpc_server::invoke(GET_PEER_LIST::request&& req, rpc_context context) @@ -1518,8 +1509,6 @@ namespace cryptonote::rpc { m_core.get_pool().get_transaction_hashes(tx_hashes, context.admin); get_transaction_pool_hashes.response_hex["tx_hashes"] = tx_hashes; get_transaction_pool_hashes.response["status"] = STATUS_OK; - LOG_PRINT_L0(get_transaction_pool_hashes.response["status"].get()); - return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_TRANSACTION_POOL_STATS& stats, rpc_context context) @@ -1571,8 +1560,6 @@ namespace cryptonote::rpc { PERF_TIMER(on_stop_daemon); m_p2p.send_stop_signal(); stop_daemon.response["status"] = STATUS_OK; - LOG_PRINT_L0(stop_daemon.response["status"]); - return; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1610,14 +1597,11 @@ namespace cryptonote::rpc { if (m_should_use_bootstrap_daemon) { getblockcount.response["status"] = "This command is unsupported for bootstrap daemon"; - LOG_PRINT_L0(getblockcount.response["status"]); return; } } getblockcount.response["count"] = m_core.get_current_blockchain_height(); getblockcount.response["status"] = STATUS_OK; - LOG_PRINT_L0(getblockcount.response["status"]); - return; } //------------------------------------------------------------------------------------------------------------------------------ GETBLOCKHASH::response core_rpc_server::invoke(GETBLOCKHASH::request&& req, rpc_context context) @@ -2623,8 +2607,6 @@ namespace cryptonote::rpc { m_core.get_pool().get_transaction_backlog(backlog); get_transaction_pool_backlog.response["backlog"] = json::parse(backlog.begin(), backlog.end()); get_transaction_pool_backlog.response["status"] = STATUS_OK; - LOG_PRINT_L0(get_transaction_pool_backlog.response["status"]); - return; } namespace { diff --git a/src/rpc/rpc_binary.h b/src/rpc/rpc_binary.h index 3a4f1327ffc..ea36f2c709b 100644 --- a/src/rpc/rpc_binary.h +++ b/src/rpc/rpc_binary.h @@ -74,6 +74,15 @@ namespace cryptonote::rpc { nlohmann::json& operator=(const T& val) { return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; } + /// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning + /// each one into a new array of binary values. + template + nlohmann::json& operator=(const std::vector& vals) { + auto a = nlohmann::json::array(); + for (auto& val : vals) + json_binary_proxy{a.emplace_back(), format} = val; + return e = std::move(a); + } }; } From d7c5cc9221cd05d887a1517599446811847bcd91 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sat, 19 Apr 2025 13:04:37 +0530 Subject: [PATCH 050/182] RPC: HARD_FORK_INFO Implements new RPC for hard_fork_info, which `beldexd status` (most notably, but there are others) requires. --- src/daemon/rpc_command_executor.cpp | 49 ++++++++++-------- src/rpc/core_rpc_server.cpp | 26 +++++----- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 7 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 26 +++++----- src/rpc/core_rpc_server_commands_defs.h | 60 +++++++++++----------- 7 files changed, 92 insertions(+), 79 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index f1ff6799091..197acbef73a 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -458,16 +458,14 @@ bool rpc_command_executor::show_status() { return false; auto& info = *maybe_info; - HARD_FORK_INFO::request hfreq{}; - HARD_FORK_INFO::response hfres{}; + auto maybe_hf = try_running([this] { return invoke(); }, + "Failed to retrieve hard fork info"); + if (!maybe_hf) + return false; + auto& hfinfo = *maybe_hf; bool has_mining_info = false, mining_active = false; long mining_hashrate = 0; - - hfreq.version = 0; bool mining_busy = false; - if (!invoke(std::move(hfreq), hfres, "Failed to retrieve hard fork info")) - return false; - bool restricted_response = false; if (auto it = info.find("start_time"); it != info.end() && it->get() > 0) // This will only be non-null if we were recognized as admin (which we need for mining info) { @@ -547,18 +545,20 @@ bool rpc_command_executor::show_status() { str << " was used"; } - if (hfres.version < cryptonote::feature::POS && !has_mining_info) + auto hf_version = hfinfo["version"].get(); + if (hf_version < cryptonote::feature::POS && !has_mining_info) str << ", mining info unavailable"; if (has_mining_info && !mining_busy && mining_active) str << ", mining at " << get_mining_speed(mining_hashrate); - if (hfres.version < cryptonote::feature::POS) + if (hf_version < cryptonote::feature::POS) str << ", net hash " << get_mining_speed(info["difficulty"].get() / info["target"].get()); str << ", v" << info["version"].get(); - str << "(net v" << static_cast(hfres.version) << ')'; - if (hfres.earliest_height) - print_fork_extra_info(str, *hfres.earliest_height, net_height, 1s * info["target"].get()); + str << "(net v" << +hf_version << ')'; + auto earliest = hfinfo.value("earliest_height", uint64_t{0}); + if (earliest) + print_fork_extra_info(str, earliest, net_height, 1s * info["target"].get()); std::time_t now = std::time(nullptr); @@ -1393,16 +1393,19 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) return false; auto& info = *maybe_info; - GET_BASE_FEE_ESTIMATE::response feres{}; - HARD_FORK_INFO::response hfres{}; + auto maybe_hf = try_running([this] { return invoke(); }, + "Failed to retrieve hard fork info"); + if (!maybe_hf) + return false; + auto& hfinfo = *maybe_hf; - if (!invoke({}, feres, "Failed to retrieve current fee info") || - !invoke({static_cast(cryptonote::feature::PER_BYTE_FEE)}, hfres, "Failed to retrieve hard fork info")) + GET_BASE_FEE_ESTIMATE::response feres{}; + if (!invoke({}, feres, "Failed to retrieve current fee info")) return false; auto height = info["height"].get(); tools::msg_writer() << "Height: " << height << ", diff " << info["difficulty"].get() << ", cum. diff " << info["cumulative_difficulty"].get() - << ", target " << info["target"].get() << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee_per_byte) << "/" << (hfres.enabled ? "byte" : "kB") + << ", target " << info["target"].get() << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee_per_byte) << "/" << (hfinfo["enabled"].get() ? "byte" : "kB") if (nblocks > 0) { @@ -2021,10 +2024,14 @@ bool rpc_command_executor::prepare_registration(bool force_registration) return false; auto& info = *maybe_info; + auto maybe_hf = try_running([this] { return invoke(); }, + "Failed to retrieve hard fork info"); + if (!maybe_hf) + return false; + auto& hfinfo = *maybe_hf; + GET_MASTER_KEYS::response kres{}; - HARD_FORK_INFO::response hf_res{}; - if (!invoke({}, hf_res, "Failed to retrieve hard fork info") || - !invoke({}, kres, "Failed to retrieve master node keys")) + if (!invoke({}, kres, "Failed to retrieve master node keys")) return false; if (!info.value("master_node", false)) @@ -2048,7 +2055,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) } uint64_t block_height = std::max(info["height"].get(), info["target_height"].get()); - auto hf_version = hf_res.version; + uint8_t hf_version = hfinfo["version"].get(); #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) cryptonote::network_type const nettype = cryptonote::FAKECHAIN; #else diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 74a171d7e8c..a9ceb123c95 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2128,26 +2128,26 @@ namespace cryptonote::rpc { get_connections.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - HARD_FORK_INFO::response core_rpc_server::invoke(HARD_FORK_INFO::request&& req, rpc_context context) + void core_rpc_server::invoke(HARD_FORK_INFO& hfinfo, rpc_context context) { - HARD_FORK_INFO::response res{}; - PERF_TIMER(on_hard_fork_info); + /* if (use_bootstrap_daemon_if_necessary(req, res)) return res; - - const Blockchain &blockchain = m_core.get_blockchain_storage(); + */ + const auto& blockchain = m_core.get_blockchain_storage(); auto version = - req.version > 0 ? static_cast(req.version) : - req.height > 0 ? blockchain.get_network_version(req.height) : + hfinfo.request.version > 0 ? hfinfo.request.version : + hfinfo.request.height > 0 ? blockchain.get_network_version(hfinfo.request.height) : blockchain.get_network_version(); - res.version = version; - res.enabled = blockchain.get_network_version() >= version; + hfinfo.response["version"] = version; + hfinfo.response["enabled"] = blockchain.get_network_version() >= version; auto heights = get_hard_fork_heights(m_core.get_nettype(), version); - res.earliest_height = heights.first; - res.last_height = heights.second; - res.status = STATUS_OK; - return res; + if (heights.first) + hfinfo.response["earliest_height"] = *heights.first; + if (heights.second) + hfinfo.response["latest_height"] = *heights.second; + hfinfo.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ GETBANS::response core_rpc_server::invoke(GETBANS::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 6bb04e037d4..e32c9d487cd 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -204,6 +204,7 @@ namespace cryptonote::rpc { void invoke(BNS_RESOLVE& resolve, rpc_context context); void invoke(GET_NET_STATS& get_net_stats, rpc_context context); void invoke(GET_OUTPUTS& get_outputs, rpc_context context); + void invoke(HARD_FORK_INFO& hfinfo, rpc_context context); void invoke(START_MINING& start_mining, rpc_context context); void invoke(STOP_MINING& stop_mining, rpc_context context); void invoke(SAVE_BC& save_bc, rpc_context context); @@ -253,7 +254,6 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); - HARD_FORK_INFO::response invoke(HARD_FORK_INFO::request&& req, rpc_context context); SETBANS::response invoke(SETBANS::request&& req, rpc_context context); GETBANS::response invoke(GETBANS::request&& req, rpc_context context); BANNED::response invoke(BANNED::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index a9b0dbcb720..b0f846de5dc 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -296,4 +296,11 @@ namespace cryptonote::rpc { get_values(in, "include_unrelayed", pstats.request.include_unrelayed); } + void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in) { + get_values(in, + "height", hfinfo.request.height, + "version", hfinfo.request.version); + if (hfinfo.request.height && hfinfo.request.version) + throw std::runtime_error{"Error: at most one of 'height'" + std::to_string(hfinfo.request.height) + "/" + std::to_string(hfinfo.request.version) + " and 'version' may be specified"}; + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index d56aff05e68..c607dd3da4d 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -23,4 +23,5 @@ namespace cryptonote::rpc { void parse_request(SAVE_BC& save_bc, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); + void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 0a3e5c14194..a928d85582f 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -736,21 +736,21 @@ KV_SERIALIZE_MAP_CODE_BEGIN(IN_PEERS::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(HARD_FORK_INFO::request) - KV_SERIALIZE(version) - KV_SERIALIZE(height) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(HARD_FORK_INFO::request) +// KV_SERIALIZE(version) +// KV_SERIALIZE(height) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(HARD_FORK_INFO::response) - KV_SERIALIZE_ENUM(version) - KV_SERIALIZE(revision) - KV_SERIALIZE(enabled) - KV_SERIALIZE(earliest_height) - KV_SERIALIZE(last_height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(HARD_FORK_INFO::response) +// KV_SERIALIZE_ENUM(version) +// KV_SERIALIZE(revision) +// KV_SERIALIZE(enabled) +// KV_SERIALIZE(earliest_height) +// KV_SERIALIZE(last_height) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::ban) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index c29919ce23d..9503113f57e 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1540,32 +1540,30 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Look up information regarding hard fork voting and readiness. + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore + /// untrusted ('true'), or when the daemon is fully synced ('false'). + /// - \p version The major block version for the fork. + /// - \p enabled Indicates whether the hard fork is enforced on the blockchain (that is, whether + /// the blockchain height is at or above the requested hardfork). + /// - \p earliest_height Block height at which the hard fork will become enabled. + /// - \p last_height The last block height at which this hard fork will be active; will be omitted + /// if this beldexd is not aware of any following hard fork. struct HARD_FORK_INFO : PUBLIC { static constexpr auto names() { return NAMES("hard_fork_info"); } - struct request - { - uint8_t version; // The major block version for the fork (only one of `version` and `height` may be given). - uint64_t height; // Request hard fork info about this height (only one of `version` and `height` may be given). - - KV_MAP_SERIALIZABLE - }; - - struct response - { - hf version; // The major block version for the fork. - uint8_t revision; // The network revision of this daemon (e.g. 1 for HF 19.1). - bool enabled; // Indicates whether hard fork is enforced (that is, at or above the requested hardfork) - std::optional earliest_height; // Block height at which hard fork will be enabled. - std::optional last_height; // The last block height at which this hard fork will be active; will be omitted if this beldexd is not aware of any future hard fork. - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + struct request_parameters { + /// If specified, this is the hard fork (i.e. major block) version for the fork. Only one of + /// `version` and `height` may be given; returns the current hard fork info if neither is + /// given. + uint8_t version = 0; + /// Request hard fork info by querying a particular height. Only one of `version` and + /// `height` may be given. + uint64_t height = 0; + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -2099,8 +2097,8 @@ namespace cryptonote::rpc { /// Get information on some, all, or a random subset of Master Nodes. struct master_node_contribution /// { /// Output variables available are as follows (you can request which parameters are returned; see - std::string key_image; // The contribution's key image that is locked on the network. /// the request parameters description). Note that OXEN values are returned in atomic OXEN units, - std::string key_image_pub_key; // The contribution's key image, public key component /// which are nano-OXEN (i.e. 1.000000000 OXEN will be returned as 1000000000). + std::string key_image; // The contribution's key image that is locked on the network. /// the request parameters description). Note that BELDEX values are returned in atomic BELDEX units, + std::string key_image_pub_key; // The contribution's key image, public key component /// which are nano-BELDEX (i.e. 1.000000000 BELDEX will be returned as 1000000000). uint64_t amount; // The amount that is locked in this contribution. /// /// - \p height the height of the current top block. (Note that this is one less than the @@ -2194,9 +2192,9 @@ namespace cryptonote::rpc { /// server (as received in the last uptime proof). Omitted if we have never received a proof. /// - \p contributors Array of contributors, contributing to this Master Node. Each element is /// a dict containing: - /// - \p amount The total amount of OXEN staked by this contributor into + /// - \p amount The total amount of BELDEX staked by this contributor into /// this Master Node. - /// - \p reserved The amount of OXEN reserved by this contributor for this Master Node; this + /// - \p reserved The amount of BELDEX reserved by this contributor for this Master Node; this /// field will be included only if the contributor has unfilled, reserved space in the /// master node. /// - \p address The wallet address of this contributor to which rewards are sent and from @@ -2206,13 +2204,13 @@ namespace cryptonote::rpc { /// Each element contains: /// - \p key_image The contribution's key image which is locked on the network. /// - \p key_image_pub_key The contribution's key image, public key component. - /// - \p amount The amount of OXEN that is locked in this contribution. + /// - \p amount The amount of BELDEX that is locked in this contribution. /// - /// - \p total_contributed The total amount of OXEN contributed to this Master Node. - /// - \p total_reserved The total amount of OXEN contributed or reserved for this Master Node. + /// - \p total_contributed The total amount of BELDEX contributed to this Master Node. + /// - \p total_reserved The total amount of BELDEX contributed or reserved for this Master Node. /// Only included in the response if there are still unfilled reservations (i.e. if it is /// greater than total_contributed). - /// - \p staking_requirement The total OXEN staking requirement in that is/was required to be + /// - \p staking_requirement The total BELDEX staking requirement in that is/was required to be /// contributed for this Master Node. /// - \p portions_for_operator The operator fee to take from the master node reward, as a /// fraction of 18446744073709551612 (2^64 - 4) (that is, this number corresponds to 100%). @@ -2802,6 +2800,7 @@ namespace cryptonote::rpc { GET_INFO, BNS_RESOLVE, GET_OUTPUTS, + HARD_FORK_INFO, START_MINING, STOP_MINING, SAVE_BC, @@ -2849,7 +2848,6 @@ namespace cryptonote::rpc { SET_LIMIT, OUT_PEERS, IN_PEERS, - HARD_FORK_INFO, GETBANS, SETBANS, BANNED, From 4e7c0131879a10d8b0c9c796c9d6c1f260026050 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sun, 20 Apr 2025 12:54:05 +0530 Subject: [PATCH 051/182] RPC: GET_TRANSACTIONS Various cleanups. Moves RPC-specific logic out of tx_pool and into the rpc code. --- src/cryptonote_core/tx_pool.cpp | 90 +---- src/cryptonote_core/tx_pool.h | 42 +- src/daemon/rpc_command_executor.cpp | 98 ++--- src/rpc/core_rpc_server.cpp | 434 ++++++++++++--------- src/rpc/core_rpc_server.h | 1 + src/rpc/core_rpc_server_command_parser.cpp | 16 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 189 ++++----- src/rpc/core_rpc_server_commands_defs.h | 300 +++++++------- src/rpc/rpc_binary.h | 9 + 10 files changed, 613 insertions(+), 567 deletions(-) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index a40013387d1..a302da990e7 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -911,6 +911,14 @@ namespace cryptonote ++m_cookie; return true; } + tx_memory_pool::key_images_container tx_memory_pool::get_spent_key_images(bool already_locked) { + std::unique_lock tx_lock{*this, std::defer_lock}; + std::unique_lock bc_lock{m_blockchain, std::defer_lock}; + if (!already_locked) + std::lock(tx_lock, bc_lock); + + return m_spent_key_images; + } //--------------------------------------------------------------------------------- bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen) { @@ -1297,88 +1305,6 @@ namespace cryptonote return stats; } - //------------------------------------------------------------------ - //TODO: investigate whether boolean return is appropriate - bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos, std::function post_process, bool include_sensitive_data) const - { - std::unique_lock tx_lock{m_transactions_lock, std::defer_lock}; - std::unique_lock bc_lock{m_blockchain, std::defer_lock}; - auto flash_lock = flash_shared_lock(std::defer_lock); - std::lock(tx_lock, bc_lock, flash_lock); - - tx_infos.reserve(m_blockchain.get_txpool_tx_count()); - key_image_infos.reserve(m_blockchain.get_txpool_tx_count()); - - m_blockchain.for_all_txpool_txes([&tx_infos, this, include_sensitive_data, post_process=std::move(post_process)](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ - transaction tx; - if (!parse_and_validate_tx_from_blob(*bd, tx)) - { - MERROR("Failed to parse tx from txpool"); - // continue - return true; - } - tx_infos.emplace_back(); - auto& txi = tx_infos.back(); - txi.id_hash = tools::type_to_hex(txid); - txi.tx_blob = *bd; - tx.set_hash(txid); - txi.tx_json = obj_to_json_str(tx); - txi.blob_size = bd->size(); - txi.weight = meta.weight; - txi.fee = meta.fee; - txi.kept_by_block = meta.kept_by_block; - txi.max_used_block_height = meta.max_used_block_height; - txi.max_used_block_id_hash = tools::type_to_hex(meta.max_used_block_id); - txi.last_failed_height = meta.last_failed_height; - txi.last_failed_id_hash = tools::type_to_hex(meta.last_failed_id); - // In restricted mode we do not include this data: - txi.receive_time = include_sensitive_data ? meta.receive_time : 0; - txi.relayed = meta.relayed; - // In restricted mode we do not include this data: - txi.last_relayed_time = include_sensitive_data ? meta.last_relayed_time : 0; - txi.do_not_relay = meta.do_not_relay; - txi.double_spend_seen = meta.double_spend_seen; - txi.flash = has_flash(txid); - if (post_process) - post_process(tx, txi); - return true; - }, true, include_sensitive_data); - - txpool_tx_meta_t meta; - for (const key_images_container::value_type& kee : m_spent_key_images) { - const crypto::key_image& k_image = kee.first; - const std::unordered_set& kei_image_set = kee.second; - rpc::spent_key_image_info ki{}; - ki.id_hash = tools::type_to_hex(k_image); - for (const crypto::hash& tx_id_hash : kei_image_set) - { - if (!include_sensitive_data) - { - try - { - if (!m_blockchain.get_txpool_tx_meta(tx_id_hash, meta)) - { - MERROR("Failed to get tx meta from txpool"); - return false; - } - if (!meta.relayed) - // Do not include that transaction if in restricted mode and it's not relayed - continue; - } - catch (const std::exception &e) - { - MERROR("Failed to get tx meta from txpool: " << e.what()); - return false; - } - } - ki.txs_hashes.push_back(tools::type_to_hex(tx_id_hash)); - } - // Only return key images for which we have at least one tx that we can show for them - if (!ki.txs_hashes.empty()) - key_image_infos.push_back(ki); - } - return true; - } //--------------------------------------------------------------------------------- bool tx_memory_pool::check_for_key_images(const std::vector& key_images, std::vector& spent) const { diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 39eebda316c..15c1a672e8f 100755 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -445,20 +445,6 @@ namespace cryptonote */ tx_stats get_transaction_stats(bool include_unrelayed_txes = true) const; - /** - * @brief get information about all transactions and key images in the pool - * - * see documentation on tx_info and spent_key_image_info for more details - * - * @param tx_infos return-by-reference the transactions' information - * @param key_image_infos return-by-reference the spent key images' information - * @param post_process optional function to call to do any extra tx_info processing from the transaction - * @param include_sensitive_data include unrelayed txes and fields that are sensitive to the node confidentiality - * - * @return true - */ - bool get_transactions_and_spent_keys_info(std::vector& tx_infos, std::vector& key_image_infos, std::function post_process = nullptr, bool include_sensitive_data = true) const; - /** * @brief check for presence of key images in the pool * @@ -562,6 +548,23 @@ namespace cryptonote * @param bytes the max cumulative txpool weight in bytes */ void set_txpool_max_weight(size_t bytes); + + //TODO: confirm the below comments and investigate whether or not this + // is the desired behavior + //! map key images to transactions which spent them + /*! this seems odd, but it seems that multiple transactions can exist + * in the pool which both have the same spent key. This would happen + * in the event of a reorg where someone creates a new/different + * transaction on the assumption that the original will not be in a + * block again. + */ + using key_images_container = std::unordered_map>; + + /// Returns a copy of the map of key images -> set of transactions which spent them. + /// + /// \param already_locked can be passed as true if the caller already has a lock on the + /// blockchain and mempool objects; otherwise a new lock will be obtained by the call. + key_images_container get_spent_key_images(bool already_locked = false); private: @@ -699,17 +702,6 @@ namespace cryptonote */ bool remove_flash_conflicts(const crypto::hash &id, const std::vector &conflict_txs, uint64_t *flash_rollback_height); - //TODO: confirm the below comments and investigate whether or not this - // is the desired behavior - //! map key images to transactions which spent them - /*! this seems odd, but it seems that multiple transactions can exist - * in the pool which both have the same spent key. This would happen - * in the event of a reorg where someone creates a new/different - * transaction on the assumption that the original will not be in a - * block again. - */ - typedef std::unordered_map > key_images_container; - mutable std::recursive_mutex m_transactions_lock; //!< mutex for the pool //! container for spent key images from the transactions in the pool diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 197acbef73a..a3b1c0d521a 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -847,66 +847,74 @@ bool rpc_command_executor::print_transaction(const crypto::hash& transaction_has bool include_metadata, bool include_hex, bool include_json) { - GET_TRANSACTIONS::request req{}; - GET_TRANSACTIONS::response res{}; - req.txs_hashes.push_back(tools::type_to_hex(transaction_hash)); - req.split = true; - if (!invoke(std::move(req), res, "Transaction retrieval failed")) + auto maybe_tx = try_running([this, &transaction_hash] { + return invoke(json{ + {"tx_hashes", json::array({tools::type_to_hex(transaction_hash)})}, + {"split", true}}); + }, "Transaction retrieval failed"); + if (!maybe_tx) return false; - if (1 == res.txs.size()) - { - auto& tx = res.txs.front(); - bool pruned = tx.prunable_hash && !tx.prunable_as_hex; + auto& txi = *maybe_tx; + auto txs = txi["txs"]; + if (txs.size() != 1) { + tools::fail_msg_writer() << "Transaction wasn't found: " << transaction_hash << "\n"; + return true; + } - if (tx.in_pool) - tools::success_msg_writer() << "Found in pool"; - else - tools::success_msg_writer() << "Found in blockchain at height " << tx.block_height << (pruned ? " (pruned)" : ""); + auto tx = txs.front(); + auto prunable_hash = tx.value("prunable_hash", ""sv); + auto prunable_hex = tx.value("prunable", ""sv); + bool pruned = !prunable_hash.empty() && prunable_hex.empty(); - const std::string &pruned_as_hex = *tx.pruned_as_hex; // Always included with req.split=true + bool in_pool = tx["in_pool"].get(); + if (in_pool) + tools::success_msg_writer() << "Found in pool"; + else + tools::success_msg_writer() << "Found in blockchain at height " << tx["block_height"].get() << (pruned ? " (pruned)" : ""); + + auto pruned_hex = tx["pruned"].get(); // Always included with req.split=true - std::optional t; - if (include_metadata || include_json) + std::optional t; + if (include_metadata || include_json) + { + if (oxenmq::is_hex(pruned_hex) && oxenmq::is_hex(prunable_hex)) { - if (oxenc::is_hex(pruned_as_hex) && (!tx.prunable_as_hex || oxenc::is_hex(*tx.prunable_as_hex))) + std::string blob = oxenmq::from_hex(pruned_hex); + if (!prunable_hex.empty()) + blob += oxenmq::from_hex(prunable_hex); + + bool parsed = pruned + ? cryptonote::parse_and_validate_tx_base_from_blob(blob, t.emplace()) + : cryptonote::parse_and_validate_tx_from_blob(blob, t.emplace()); + if (!parsed) { - std::string blob = oxenc::from_hex(pruned_as_hex); - if (tx.prunable_as_hex) - blob += oxenc::from_hex(*tx.prunable_as_hex); - - bool parsed = pruned - ? cryptonote::parse_and_validate_tx_base_from_blob(blob, t.emplace()) - : cryptonote::parse_and_validate_tx_from_blob(blob, t.emplace()); - if (!parsed) - { - tools::fail_msg_writer() << "Failed to parse transaction data"; - t.reset(); - } + tools::fail_msg_writer() << "Failed to parse transaction data"; + t.reset(); } } + } - // Print metadata if requested - if (include_metadata) - { - if (!tx.in_pool) - tools::msg_writer() << "Block timestamp: " << tx.block_timestamp << " (" << tools::get_human_readable_timestamp(tx.block_timestamp) << ")"; - tools::msg_writer() << "Size: " << tx.size; - if (t) - tools::msg_writer() << "Weight: " << cryptonote::get_transaction_weight(*t); + // Print metadata if requested + if (include_metadata) + { + if (!in_pool) { + auto ts = tx["block_timestamp"].get(); + tools::msg_writer() << "Block timestamp: " << ts << " (" << tools::get_human_readable_timestamp(ts) << ")"; } + tools::msg_writer() << "Size: " << tx["size"].get(); + if (t) + tools::msg_writer() << "Weight: " << cryptonote::get_transaction_weight(*t); + } - // Print raw hex if requested - if (include_hex) - tools::success_msg_writer() << pruned_as_hex << (tx.prunable_as_hex ? *tx.prunable_as_hex : "") << '\n'; + // Print raw hex if requested + if (include_hex) + tools::success_msg_writer() << pruned_hex << prunable_hex << '\n'; - // Print json if requested - if (include_json && t) + // Print json if requested + if (include_json && t) tools::success_msg_writer() << cryptonote::obj_to_json_str(*t) << '\n'; - } - else - tools::fail_msg_writer() << "Transaction wasn't found: " << transaction_hash << std::endl; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a9ceb123c95..a782e652ecc 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -734,121 +734,147 @@ namespace cryptonote::rpc { } struct extra_extractor { - GET_TRANSACTIONS::extra_entry& entry; + nlohmann::json& entry; const network_type nettype; const cryptonote::hf hf_version; + json_binary_proxy::fmt format; - void operator()(const tx_extra_pub_key& x) { entry.pubkey = tools::type_to_hex(x.pub_key); } + // If we encounter duplicate values then we want to produce an array of values, but with just + // a single one we want just the value itself; this does that. Returns a reference to the + // assigned value (whether as a top-level value or array element). + template + json& set(const std::string& key, T&& value, bool binary = is_binary_parameter || is_binary_vector) { + auto* x = &entry[key]; + if (!x->is_null() && !x->is_array()) + x = &(entry[key] = json::array({std::move(*x)})); + if (x->is_array()) + x = &x->emplace_back(); + if constexpr (is_binary_parameter || is_binary_vector || std::is_convertible_v) { + if (binary) + return json_binary_proxy{*x, format} = std::forward(value); + } + assert(!binary); + return *x = std::forward(value); + } + + void operator()(const tx_extra_pub_key& x) { set("pubkey", x.pub_key); } void operator()(const tx_extra_nonce& x) { if ((x.nonce.size() == sizeof(crypto::hash) + 1 && x.nonce[0] == TX_EXTRA_NONCE_PAYMENT_ID) || (x.nonce.size() == sizeof(crypto::hash8) + 1 && x.nonce[0] == TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID)) - entry.payment_id = oxenc::to_hex(x.nonce.begin() + 1, x.nonce.end()); + set("payment_id", std::string_view{x.nonce.data() + 1, x.nonce.size() - 1}, true); else - entry.extra_nonce = oxenc::to_hex(x.nonce); + set("extra_nonce", x.nonce, true); } - void operator()(const tx_extra_merge_mining_tag& x) { entry.mm_depth = x.depth; entry.mm_root = tools::type_to_hex(x.merkle_root); } - void operator()(const tx_extra_additional_pub_keys& x) { entry.additional_pubkeys = hexify(x.data); } - void operator()(const tx_extra_burn& x) { entry.burn_amount = x.amount; } - void operator()(const tx_extra_master_node_winner& x) { entry.mn_winner = tools::type_to_hex(x.m_master_node_key); } - void operator()(const tx_extra_master_node_pubkey& x) { entry.mn_pubkey = tools::type_to_hex(x.m_master_node_key); } + void operator()(const tx_extra_merge_mining_tag& x) { set("mm_depth", x.depth); set("mm_root", x.merkle_root); } + void operator()(const tx_extra_additional_pub_keys& x) { set("additional_pubkeys", x.data); } + void operator()(const tx_extra_burn& x) { set("burn_amount", x.amount); } + void operator()(const tx_extra_master_node_winner& x) { set("mn_winner", x.m_master_node_key); } + void operator()(const tx_extra_master_node_pubkey& x) { set("mn_pubkey", x.m_master_node_key); } void operator()(const tx_extra_security_signature& x) { entry.security_sig = tools::type_to_hex(x.m_security_signature); } void operator()(const tx_extra_master_node_register& x) { - auto& reg = entry.mn_registration.emplace(); - reg.fee = microportion(x.m_portions_for_operator); - reg.expiry = x.m_expiration_timestamp; - for (size_t i = 0; i < x.m_portions.size(); i++) { - auto& [wallet, portion] = reg.contributors.emplace_back(); - wallet = get_account_address_as_str(nettype, false, {x.m_public_spend_keys[i], x.m_public_view_keys[i]}); - portion = microportion(x.m_portions[i]); - } + json reservations{}; + for (size_t i = 0; i < x.m_portions.size(); i++) + reservations[get_account_address_as_str(nettype, false, {x.m_public_spend_keys[i], x.m_public_view_keys[i]})] + = microportion(x.m_portions[i]); + set("mn_registration", json{ + {"fee", microportion(x.m_portions_for_operator)}, + {"expiry", x.m_expiration_timestamp}, + {"reservations", std::move(reservations)}}); } void operator()(const tx_extra_master_node_contributor& x) { - entry.mn_contributor = get_account_address_as_str(nettype, false, {x.m_spend_public_key, x.m_view_public_key}); + set("mn_contributor", get_account_address_as_str(nettype, false, {x.m_spend_public_key, x.m_view_public_key})); } template auto& _state_change(const T& x) { // Common loading code for nearly-identical state_change and deregister_old variables: - auto& sc = entry.mn_state_change.emplace(); - sc.height = x.block_height; - sc.index = x.master_node_index; - sc.voters.reserve(x.votes.size()); + auto voters = json::array(); for (auto& v : x.votes) - sc.voters.push_back(v.validator_index); - return sc; + voters.push_back(v.validator_index); + + json sc{ + {"height", x.block_height}, + {"index", x.master_node_index}, + {"voters", std::move(voters)}}; + return set("mn_state_change", std::move(sc)); } void operator()(const tx_extra_master_node_deregister_old& x) { auto& sc = _state_change(x); - sc.old_dereg = true; - sc.type = "dereg"; + sc["old_dereg"] = true; + sc["type"] = "dereg"; } void operator()(const tx_extra_master_node_state_change& x) { auto& sc = _state_change(x); if (x.reason_consensus_all) - sc.reasons = cryptonote::coded_reasons(x.reason_consensus_all); + sc["reasons"] = cryptonote::coded_reasons(x.reason_consensus_all); // If `any` has reasons not included in all then list the extra ones separately: if (uint16_t reasons_maybe = x.reason_consensus_any & ~x.reason_consensus_all) - sc.reasons_maybe = cryptonote::coded_reasons(reasons_maybe); + sc["reasons_maybe"] = cryptonote::coded_reasons(reasons_maybe); switch (x.state) { - case master_nodes::new_state::decommission: sc.type = "decom"; break; - case master_nodes::new_state::recommission: sc.type = "recom"; break; - case master_nodes::new_state::deregister: sc.type = "dereg"; break; - case master_nodes::new_state::ip_change_penalty: sc.type = "ip"; break; + case master_nodes::new_state::decommission: sc["type"] = "decom"; break; + case master_nodes::new_state::recommission: sc["type"] = "recom"; break; + case master_nodes::new_state::deregister: sc["type"] = "dereg"; break; + case master_nodes::new_state::ip_change_penalty: sc["type"] = "ip"; break; case master_nodes::new_state::_count: /*leave blank*/ break; } } - void operator()(const tx_extra_tx_secret_key& x) { entry.tx_secret_key = tools::type_to_hex(x.key); } + void operator()(const tx_extra_tx_secret_key& x) { set("tx_secret_key", tools::view_guts(x.key), true); } void operator()(const tx_extra_tx_key_image_proofs& x) { - entry.locked_key_images.reserve(x.proofs.size()); - for (auto& proof : x.proofs) entry.locked_key_images.emplace_back(tools::type_to_hex(proof.key_image)); - } - void operator()(const tx_extra_tx_key_image_unlock& x) { entry.key_image_unlock = tools::type_to_hex(x.key_image); } - void _load_owner(std::optional& entry, const bns::generic_owner& owner) { + std::vector kis; + kis.reserve(x.proofs.size()); + for (auto& proof : x.proofs) + kis.push_back(proof.key_image); + set("locked_key_images", std::move(kis)); + } + void operator()(const tx_extra_tx_key_image_unlock& x) { set("key_image_unlock", x.key_image); } + void _load_owner(json& parent, const std::string& key, const bns::generic_owner& owner) { if (!owner) return; if (owner.type == bns::generic_owner_sig_type::monero) - entry = get_account_address_as_str(nettype, owner.wallet.is_subaddress, owner.wallet.address); + parent[key] = get_account_address_as_str(nettype, owner.wallet.is_subaddress, owner.wallet.address); else if (owner.type == bns::generic_owner_sig_type::ed25519) - entry = tools::type_to_hex(owner.ed25519); + json_binary_proxy{parent[key], json_binary_proxy::fmt::hex} = owner.ed25519; } + //TODO CHECK ON HF void operator()(const tx_extra_beldex_name_system& x) { - auto& bns = entry.bns.emplace(); - bns.version = x.version; + json bns{}; + bns["version"] = x.version; if ((x.is_buying() || x.is_renewing()) && (x.version == 1)) - bns.blocks = bns::expiry_blocks(nettype, x.mapping_years) ; + bns["blocks"] = bns::expiry_blocks(nettype, x.mapping_years) ; if(x.version == 0) switch (x.type) { case bns::mapping_type::belnet: [[fallthrough]]; case bns::mapping_type::belnet_2years: [[fallthrough]]; case bns::mapping_type::belnet_5years: [[fallthrough]]; - case bns::mapping_type::belnet_10years: bns.type = "belnet"; break; + case bns::mapping_type::belnet_10years: bns["type"] = "belnet"; break; - case bns::mapping_type::bchat: bns.type = "bchat"; break; - case bns::mapping_type::wallet: bns.type = "wallet"; break; - case bns::mapping_type::eth_addr: bns.type = "eth_addr"; break; + case bns::mapping_type::bchat: bns["type"] = "bchat"; break; + case bns::mapping_type::wallet: bns["type"] = "wallet"; break; + case bns::mapping_type::eth_addr: bns["type"] = "eth_addr"; break; case bns::mapping_type::update_record_internal: [[fallthrough]]; case bns::mapping_type::_count: break; } if (x.is_buying()) - bns.buy = true; + bns["buy"] = true; else if (x.is_updating()) - bns.update = true; + bns["update"] = true; else if (x.is_renewing()) - bns.renew = true; - bns.name_hash = tools::type_to_hex(x.name_hash); + bns["renew"] = true; + auto bns_bin = json_binary_proxy{bns, format}; + bns_bin["name_hash"] = x.name_hash; if (!x.encrypted_bchat_value.empty()) - bns.value_bchat = oxenc::to_hex(x.encrypted_bchat_value); + bns_bin["value_bchat"] = x.encrypted_bchat_value; if (!x.encrypted_wallet_value.empty()) - bns.value_wallet = oxenc::to_hex(x.encrypted_wallet_value); + bns_bin["value_wallet"] = x.encrypted_wallet_value; if (!x.encrypted_belnet_value.empty()) - bns.value_belnet = oxenc::to_hex(x.encrypted_belnet_value); + bns_bin["value_belnet"] = x.encrypted_belnet_value; if (!x.encrypted_eth_addr_value.empty()) - bns.value_eth_addr = oxenc::to_hex(x.encrypted_eth_addr_value); - _load_owner(bns.owner, x.owner); - _load_owner(bns.backup_owner, x.backup_owner); + bns_bin["value_eth_addr"] = x.encrypted_eth_addr_value; + _load_owner(bns, "owner", x.owner); + _load_owner(bns, "backup_owner", x.backup_owner); } // Ignore these fields: @@ -857,96 +883,151 @@ namespace cryptonote::rpc { }; - bool load_tx_extra_data(GET_TRANSACTIONS::extra_entry& e, const transaction& tx, network_type nettype, cryptonote::hf hf_version) + void load_tx_extra_data(nlohmann::json& e, const transaction& tx, network_type nettype, cryptonote::hf hf_version, bool is_bt) { + e = json::object(); std::vector extras; if (!parse_tx_extra(tx.extra, extras)) - return false; - extra_extractor visitor{e, nettype, hf_version}; + return; + extra_extractor visitor{e, nettype, hf_version, is_bt ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex}; for (const auto& extra : extras) var::visit(visitor, extra); - return true; } } + + struct tx_info { + crypto::hash id; // txid hash + txpool_tx_meta_t meta; + std::string tx_blob; // Blob containing the transaction data. + bool flash; // True if this is a signed flash transaction + //std::optional extra; // Parsed tx_extra information (only if requested) + //std::optional stake_amount; // Will be set to the staked amount if the transaction is a staking transaction *and* stake amounts were requested. + }; + + static std::vector get_pool_txs_impl(cryptonote::core& core, std::function post_process) { + auto& bc = core.get_blockchain_storage(); + auto& pool = core.get_pool(); + + std::vector tx_infos; + tx_infos.reserve(bc.get_txpool_tx_count()); + + bc.for_all_txpool_txes( + [&tx_infos, &pool, post_process=std::move(post_process)] + (const crypto::hash& txid, const txpool_tx_meta_t& meta, const cryptonote::blobdata* bd) { + transaction tx; + if (!parse_and_validate_tx_from_blob(*bd, tx)) + { + MERROR("Failed to parse tx from txpool"); + // continue + return true; + } + auto& txi = tx_infos.emplace_back(); + txi.id = txid; + txi.meta = meta; + txi.tx_blob = *bd; + tx.set_hash(txid); + //txi.tx_json = obj_to_json_str(tx); + txi.flash = pool.has_flash(txid); + if (post_process) + post_process(tx, txi); + return true; + }, true); + + return tx_infos; + } + + auto pool_locks(cryptonote::core& core) { + auto& pool = core.get_pool(); + std::unique_lock tx_lock{pool, std::defer_lock}; + std::unique_lock bc_lock{core.get_blockchain_storage(), std::defer_lock}; + auto flash_lock = pool.flash_shared_lock(std::defer_lock); + std::lock(tx_lock, bc_lock, flash_lock); + return std::make_tuple(std::move(tx_lock), std::move(bc_lock), std::move(flash_lock)); + } + + static std::pair, tx_memory_pool::key_images_container> get_pool_txs_kis( + cryptonote::core& core, std::function post_process = {}) { + auto locks = pool_locks(core); + return {get_pool_txs_impl(core, std::move(post_process)), core.get_pool().get_spent_key_images(true)}; + } + + static std::vector get_pool_txs( + cryptonote::core& core, std::function post_process = {}) { + auto locks = pool_locks(core); + return get_pool_txs_impl(core, std::move(post_process)); + } + + static tx_memory_pool::key_images_container get_pool_kis( + cryptonote::core& core, std::function post_process = {}) { + auto locks = pool_locks(core); + return core.get_pool().get_spent_key_images(true); + } //------------------------------------------------------------------------------------------------------------------------------ - GET_TRANSACTIONS::response core_rpc_server::invoke(GET_TRANSACTIONS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_TRANSACTIONS& get, rpc_context context) { - GET_TRANSACTIONS::response res{}; - PERF_TIMER(on_get_transactions); + /* if (use_bootstrap_daemon_if_necessary(req, res)) return res; - - std::vector vh; - for(const auto& tx_hex_str: req.txs_hashes) - { - if (!tools::hex_to_type(tx_hex_str, vh.emplace_back())) - { - res.status = "Failed to parse hex representation of transaction hash"; - return res; - } - } + */ std::vector missed_txs; - std::vector> txs; - bool r = m_core.get_split_transactions_blobs(vh, txs, missed_txs); - if(!r) + using split_tx = std::tuple; + std::vector txs; + if (!m_core.get_split_transactions_blobs(get.request.tx_hashes, txs, missed_txs)) { - res.status = "Failed"; - return res; + get.response["status"] = STATUS_FAILED; + return; } - LOG_PRINT_L2("Found " << txs.size() << "/" << vh.size() << " transactions on the blockchain"); + LOG_PRINT_L2("Found " << txs.size() << "/" << get.request.tx_hashes.size() << " transactions on the blockchain"); // try the pool for any missing txes - auto &pool = m_core.get_pool(); + auto& pool = m_core.get_pool(); size_t found_in_pool = 0; std::unordered_map per_tx_pool_tx_info; if (!missed_txs.empty()) { - std::vector pool_tx_info; - std::vector pool_key_image_info; - bool r = pool.get_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info, nullptr, context.admin); - if(r) - { + try { + auto pool_tx_info = get_pool_txs(m_core); // sort to match original request - std::vector> sorted_txs; + std::vector sorted_txs; unsigned txs_processed = 0; - for (const crypto::hash &h: vh) + for (const auto& h: get.request.tx_hashes) { auto missed_it = std::find(missed_txs.begin(), missed_txs.end(), h); if (missed_it == missed_txs.end()) { if (txs.size() == txs_processed) { - res.status = "Failed: internal error - txs is empty"; - return res; + get.response["status"] = "Failed: internal error - txs is empty"; + return; } // core returns the ones it finds in the right order if (std::get<0>(txs[txs_processed]) != h) { - res.status = "Failed: tx hash mismatch"; - return res; + get.response["status"] = "Failed: tx hash mismatch"; + return; } sorted_txs.push_back(std::move(txs[txs_processed])); ++txs_processed; continue; } - const std::string hash_string = tools::type_to_hex(h); + auto ptx_it = std::find_if(pool_tx_info.begin(), pool_tx_info.end(), - [&hash_string](const tx_info &txi) { return hash_string == txi.id_hash; }); + [&h](const auto& txi) { return h == txi.id; }); if (ptx_it != pool_tx_info.end()) { cryptonote::transaction tx; if (!cryptonote::parse_and_validate_tx_from_blob(ptx_it->tx_blob, tx)) { - res.status = "Failed to parse and validate tx from blob"; - return res; + get.response["status"] = "Failed to parse and validate tx from blob"; + return; } serialization::binary_string_archiver ba; try { tx.serialize_base(ba); } catch (const std::exception& e) { - res.status = "Failed to serialize transaction base: "s + e.what(); - return res; + get.response["status"] = "Failed to serialize transaction base: "s + e.what(); + return; } std::string pruned = ba.str(); std::string pruned2{ptx_it->tx_blob, pruned.size()}; @@ -956,26 +1037,29 @@ namespace cryptonote::rpc { ++found_in_pool; } } - txs = sorted_txs; + txs = std::move(sorted_txs); + } catch (const std::exception& e) { + // Log error but continue + MERROR(e.what()); } - LOG_PRINT_L2("Found " << found_in_pool << "/" << vh.size() << " transactions in the pool"); + get.response_hex["missed_tx"] = missed_txs; // non-plural here intentional to not break existing clients + LOG_PRINT_L2("Found " << found_in_pool << "/" << get.request.tx_hashes.size() << " transactions in the pool"); } - - res.missed_tx.reserve(missed_txs.size()); - for(const auto& miss_tx: missed_txs) - res.missed_tx.push_back(tools::type_to_hex(miss_tx)); - uint64_t immutable_height = m_core.get_blockchain_storage().get_immutable_height(); auto flash_lock = pool.flash_shared_lock(std::defer_lock); // Defer until/unless we actually need it + + auto& txs_out = get.response["txs"]; + txs_out = json::array(); auto height = m_core.get_current_blockchain_height(); auto net = nettype(); auto hf_version = get_network_version(net, height); cryptonote::blobdata tx_data; - for(const auto& [tx_hash, unprunable_data, prunable_hash, prunable_data]: txs) + for (const auto& [tx_hash, unprunable_data, prunable_hash, prunable_data]: txs) { - auto& e = res.txs.emplace_back(); - e.tx_hash = tools::type_to_hex(tx_hash); - e.size = unprunable_data.size() + prunable_data.size(); + auto& e = txs_out.emplace_back(); + auto e_bin = get.response_hex["txs"].back(); + e_bin["tx_hash"] = tx_hash; + e["size"] = unprunable_data.size() + prunable_data.size(); // If the transaction was pruned then the prunable part will be empty but the prunable hash // will be non-null. (Some txes, like coinbase txes, are non-prunable and will have empty @@ -983,22 +1067,22 @@ namespace cryptonote::rpc { bool prunable = prunable_hash != crypto::null_hash; bool pruned = prunable && prunable_data.empty(); - if (pruned || (prunable && (req.split || req.prune))) - e.prunable_hash = tools::type_to_hex(prunable_hash); + if (pruned || (prunable && (get.request.split || get.request.prune))) + e_bin["prunable_hash"] = prunable_hash; - if (req.split || req.prune || pruned) + if (get.request.split || get.request.prune || pruned) { - if (req.decode_as_json) + if (get.request.decode_as_json) { tx_data = unprunable_data; - if (!req.prune) + if (!get.request.prune) tx_data += prunable_data; } else { - e.pruned_as_hex = oxenc::to_hex(unprunable_data); - if (!req.prune && prunable && !pruned) - e.prunable_as_hex = oxenc::to_hex(prunable_data); + e_bin["pruned"] = unprunable_data; + if (!get.request.prune && prunable && !pruned) + e_bin["prunable"] = prunable_data; } } else @@ -1006,87 +1090,88 @@ namespace cryptonote::rpc { // use non-splitted form, leaving pruned_as_hex and prunable_as_hex as empty tx_data = unprunable_data; tx_data += prunable_data; - if (!req.decode_as_json) - e.as_hex = oxenc::to_hex(tx_data); + if (!get.request.decode_as_json) + e_bin["data"] = tx_data; } cryptonote::transaction t; - if (req.decode_as_json || req.tx_extra || req.stake_info) + if (get.request.decode_as_json || get.request.tx_extra || get.request.stake_info) { - if (req.prune || pruned) + if (get.request.prune || pruned) { if (!cryptonote::parse_and_validate_tx_base_from_blob(tx_data, t)) { - res.status = "Failed to parse and validate base tx data"; - return res; + get.response["status"] = "Failed to parse and validate base tx data"; + return; } - if (req.decode_as_json) - e.as_json = obj_to_json_str(pruned_transaction{t}); + // I hate this because it goes deep into epee: + if (get.request.decode_as_json) + e["as_json"] = obj_to_json_str(pruned_transaction{t}); } else { if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) { - res.status = "Failed to parse and validate tx data"; - return res; + get.response["status"] = "Failed to parse and validate tx data"; + return; } - if (req.decode_as_json) - e.as_json = obj_to_json_str(t); + if (get.request.decode_as_json) + e["as_json"] = obj_to_json_str(t); } - - if (req.tx_extra) - load_tx_extra_data(e.extra.emplace(), t, net, hf_version); + if (get.request.tx_extra) + load_tx_extra_data(e["extra"], t, nettype(), hf_version, get.is_bt()); } auto ptx_it = per_tx_pool_tx_info.find(tx_hash); - e.in_pool = ptx_it != per_tx_pool_tx_info.end(); + bool in_pool = ptx_it != per_tx_pool_tx_info.end(); + e["in_pool"] = in_pool; bool might_be_flash = true; - if (e.in_pool) + auto height = std::numeric_limits::max(); + if (in_pool) { - e.block_height = e.block_timestamp = std::numeric_limits::max(); - e.double_spend_seen = ptx_it->second.double_spend_seen; - e.relayed = ptx_it->second.relayed; - e.received_timestamp = ptx_it->second.receive_time; + if (ptx_it->second.meta.double_spend_seen) + e["double_spend_seen"] = true; + e["relayed"] = ptx_it->second.meta.relayed; + e["received_timestamp"] = ptx_it->second.meta.receive_time; } else { - e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); - e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height); - e.received_timestamp = 0; - e.double_spend_seen = false; - e.relayed = false; - if (e.block_height <= immutable_height) + height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); + e["block_height"] = height; + e["block_timestamp"] = m_core.get_blockchain_storage().get_db().get_block_timestamp(height); + if (height <= immutable_height) might_be_flash = false; } - if (req.stake_info) { - auto hf_version = get_network_version(nettype(), e.in_pool ? m_core.get_current_blockchain_height() : e.block_height); + if (get.request.stake_info) { + auto hf_version = get_network_version(nettype(), in_pool ? m_core.get_current_blockchain_height() : height); master_nodes::staking_components sc; - if (master_nodes::tx_get_staking_components_and_amounts(nettype(), hf_version, t, e.block_height, &sc) + if (master_nodes::tx_get_staking_components_and_amounts(nettype(), hf_version, t, height, &sc) && sc.transferred > 0) - e.stake_amount = sc.transferred; + e["stake_amount"] = sc.transferred; } if (might_be_flash) { if (!flash_lock) flash_lock.lock(); - e.flash = pool.has_flash(tx_hash); + e["flash"] = pool.has_flash(tx_hash); } // output indices too if not in pool - if (!e.in_pool) + if (!in_pool) { - bool r = m_core.get_tx_outputs_gindexs(tx_hash, e.output_indices); - if (!r) + std::vector indices; + if (m_core.get_tx_outputs_gindexs(tx_hash, indices)) + e["output_indices"] = std::move(indices); + else { - res.status = "Failed"; - return res; + get.response["status"] = STATUS_FAILED; + return; } } } - LOG_PRINT_L2(res.txs.size() << " transactions found, " << res.missed_tx.size() << " not found"); - res.status = STATUS_OK; - return res; + LOG_PRINT_L2(get.response["txs"].size() << " transactions found, " << missed_txs.size() << " not found"); + get.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ IS_KEY_IMAGE_SPENT::response core_rpc_server::invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context) @@ -1118,37 +1203,16 @@ namespace cryptonote::rpc { res.spent_status.push_back(spent_status[n] ? IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN : IS_KEY_IMAGE_SPENT::UNSPENT); // check the pool too - std::vector txs; - std::vector ki; - r = m_core.get_pool().get_transactions_and_spent_keys_info(txs, ki, nullptr, context.admin); - if(!r) - { + try { + auto kis = get_pool_kis(m_core); + for (size_t n = 0; n < res.spent_status.size(); ++n) + if (res.spent_status[n] == IS_KEY_IMAGE_SPENT::UNSPENT && kis.count(key_images[n])) + res.spent_status[n] = IS_KEY_IMAGE_SPENT::SPENT_IN_POOL; + } catch (const std::exception& e) { + MERROR("Failed to get pool key images: " << e.what()); res.status = "Failed"; return res; } - for (std::vector::const_iterator i = ki.begin(); i != ki.end(); ++i) - { - crypto::hash hash; - crypto::key_image spent_key_image; - if (tools::hex_to_type(i->id_hash, hash)) - { - memcpy(&spent_key_image, &hash, sizeof(hash)); // a bit dodgy, should be other parse functions somewhere - for (size_t n = 0; n < res.spent_status.size(); ++n) - { - if (res.spent_status[n] == IS_KEY_IMAGE_SPENT::UNSPENT) - { - if (key_images[n] == spent_key_image) - { - res.spent_status[n] = IS_KEY_IMAGE_SPENT::SPENT_IN_POOL; - break; - } - } - } - } - else - MERROR("Invalid hash: " << i->id_hash); - } - res.status = STATUS_OK; return res; } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index e32c9d487cd..6df99ee0e24 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -214,6 +214,7 @@ namespace cryptonote::rpc { void invoke(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_context context); void invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context); void invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context); + void invoke(GET_TRANSACTIONS& req, rpc_context context); void invoke(GET_CONNECTIONS& get_connections, rpc_context context); void invoke(SYNC_INFO& sync, rpc_context context); void invoke(GET_MASTER_NODE_STATUS& sns, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index b0f846de5dc..e6a2effb5b7 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -303,4 +303,20 @@ namespace cryptonote::rpc { if (hfinfo.request.height && hfinfo.request.version) throw std::runtime_error{"Error: at most one of 'height'" + std::to_string(hfinfo.request.height) + "/" + std::to_string(hfinfo.request.version) + " and 'version' may be specified"}; } + + void parse_request(GET_TRANSACTIONS& get, rpc_input in) { + // Backwards compat for old stupid "txs_hashes" input name + if (auto* json_in = std::get_if(&in)) + if (auto it = json_in->find("txs_hashes"); it != json_in->end()) + (*json_in)["tx_hashes"] = std::move(*it); + + get_values(in, + "decode_as_json", get.request.decode_as_json, + "prune", get.request.prune, + "split", get.request.split, + "stake_info", get.request.stake_info, + "tx_extra", get.request.tx_extra, + "tx_hashes", get.request.tx_hashes); + } + } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index c607dd3da4d..fdae36ba9a6 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -23,5 +23,6 @@ namespace cryptonote::rpc { void parse_request(SAVE_BC& save_bc, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); + void parse_request(GET_TRANSACTIONS& hfinfo, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index a928d85582f..2f561d58094 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -90,102 +90,101 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::response) KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::request) - KV_SERIALIZE(txs_hashes) - KV_SERIALIZE(decode_as_json) - KV_SERIALIZE(tx_extra) - KV_SERIALIZE(prune) - KV_SERIALIZE(split) - KV_SERIALIZE(stake_info) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::request) +// KV_SERIALIZE(txs_hashes) +// KV_SERIALIZE(decode_as_json) +// KV_SERIALIZE(tx_extra) +// KV_SERIALIZE(prune) +// KV_SERIALIZE(split) +// KV_SERIALIZE(stake_info) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::mn_reg_info::contribution) - KV_SERIALIZE(wallet) - KV_SERIALIZE(portion) -KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::mn_reg_info) - KV_SERIALIZE(contributors) - KV_SERIALIZE(fee) - KV_SERIALIZE(expiry) -KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::state_change) - KV_SERIALIZE(old_dereg) - KV_SERIALIZE(type) - KV_SERIALIZE(height) - KV_SERIALIZE(index) - KV_SERIALIZE(voters) - KV_SERIALIZE(reasons); - KV_SERIALIZE(reasons_maybe); -KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::bns_details) - KV_SERIALIZE(version) - KV_SERIALIZE(buy) - KV_SERIALIZE(update) - KV_SERIALIZE(renew) - KV_SERIALIZE(type) - KV_SERIALIZE(blocks) - KV_SERIALIZE(name_hash) - KV_SERIALIZE(prev_txid) - KV_SERIALIZE(value_bchat) - KV_SERIALIZE(value_wallet) - KV_SERIALIZE(value_belnet) - KV_SERIALIZE(value_eth_addr) - KV_SERIALIZE(owner) - KV_SERIALIZE(backup_owner) -KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry) - KV_SERIALIZE(pubkey) - KV_SERIALIZE(burn_amount) - KV_SERIALIZE(extra_nonce) - KV_SERIALIZE(payment_id) - KV_SERIALIZE(mm_depth) - KV_SERIALIZE(mm_root) - KV_SERIALIZE(additional_pubkeys) - KV_SERIALIZE(mn_winner) - KV_SERIALIZE(mn_pubkey) - KV_SERIALIZE(mn_registration) - KV_SERIALIZE(mn_contributor) - KV_SERIALIZE(mn_state_change) - KV_SERIALIZE(tx_secret_key) - KV_SERIALIZE(locked_key_images) - KV_SERIALIZE(key_image_unlock) - KV_SERIALIZE(bns) -KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::entry) - KV_SERIALIZE(tx_hash) - KV_SERIALIZE(as_hex) - KV_SERIALIZE(as_json) - KV_SERIALIZE(pruned_as_hex) - KV_SERIALIZE(prunable_as_hex) - KV_SERIALIZE(prunable_hash) - KV_SERIALIZE(size) - KV_SERIALIZE(in_pool) - KV_SERIALIZE(double_spend_seen) - if (!in_pool) - { - KV_SERIALIZE(block_height) - KV_SERIALIZE(block_timestamp) - KV_SERIALIZE(output_indices) - } - else - { - KV_SERIALIZE(relayed) - KV_SERIALIZE(received_timestamp) - } - KV_SERIALIZE(flash) - KV_SERIALIZE(extra) - KV_SERIALIZE(stake_amount) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::mn_reg_info::contribution) +// KV_SERIALIZE(wallet) +// KV_SERIALIZE(portion) +// KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::mn_reg_info) +// KV_SERIALIZE(contributors) +// KV_SERIALIZE(fee) +// KV_SERIALIZE(expiry) +// KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::state_change) +// KV_SERIALIZE(old_dereg) +// KV_SERIALIZE(type) +// KV_SERIALIZE(height) +// KV_SERIALIZE(index) +// KV_SERIALIZE(voters) +// KV_SERIALIZE(reasons); +// KV_SERIALIZE(reasons_maybe); +// KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::bns_details) +// KV_SERIALIZE(version) +// KV_SERIALIZE(buy) +// KV_SERIALIZE(update) +// KV_SERIALIZE(renew) +// KV_SERIALIZE(type) +// KV_SERIALIZE(blocks) +// KV_SERIALIZE(name_hash) +// KV_SERIALIZE(prev_txid) +// KV_SERIALIZE(value_bchat) +// KV_SERIALIZE(value_wallet) +// KV_SERIALIZE(value_belnet) +// KV_SERIALIZE(value_eth_addr) +// KV_SERIALIZE(owner) +// KV_SERIALIZE(backup_owner) +// KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry) +// KV_SERIALIZE(pubkey) +// KV_SERIALIZE(burn_amount) +// KV_SERIALIZE(extra_nonce) +// KV_SERIALIZE(payment_id) +// KV_SERIALIZE(mm_depth) +// KV_SERIALIZE(mm_root) +// KV_SERIALIZE(additional_pubkeys) +// KV_SERIALIZE(mn_winner) +// KV_SERIALIZE(mn_pubkey) +// KV_SERIALIZE(mn_registration) +// KV_SERIALIZE(mn_contributor) +// KV_SERIALIZE(mn_state_change) +// KV_SERIALIZE(tx_secret_key) +// KV_SERIALIZE(locked_key_images) +// KV_SERIALIZE(key_image_unlock) +// KV_SERIALIZE(bns) +// KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::entry) +// KV_SERIALIZE(tx_hash) +// KV_SERIALIZE(as_hex) +// KV_SERIALIZE(as_json) +// KV_SERIALIZE(pruned_as_hex) +// KV_SERIALIZE(prunable_as_hex) +// KV_SERIALIZE(prunable_hash) +// KV_SERIALIZE(size) +// KV_SERIALIZE(in_pool) +// KV_SERIALIZE(double_spend_seen) +// if (!in_pool) +// { +// KV_SERIALIZE(block_height) +// KV_SERIALIZE(block_timestamp) +// KV_SERIALIZE(output_indices) +// } +// else +// { +// KV_SERIALIZE(relayed) +// KV_SERIALIZE(received_timestamp) +// } +// KV_SERIALIZE(flash) +// KV_SERIALIZE(extra) +// KV_SERIALIZE(stake_amount) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::response) - KV_SERIALIZE(txs) - KV_SERIALIZE(missed_tx) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::response) +// KV_SERIALIZE(txs) +// KV_SERIALIZE(missed_tx) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::request) @@ -565,7 +564,9 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_CATEGORIES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(tx_info) +// KV_SERIALIZE_MAP_CODE_BEGIN(tx_info) +// FIXME: delete +KV_SERIALIZE_MAP_CODE_BEGIN(old_tx_info) KV_SERIALIZE(id_hash) KV_SERIALIZE(tx_json) KV_SERIALIZE(blob_size) @@ -583,7 +584,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(tx_info) KV_SERIALIZE(double_spend_seen) KV_SERIALIZE(tx_blob) KV_SERIALIZE(extra) - KV_SERIALIZE(stake_amount) + // KV_SERIALIZE(stake_amount) KV_SERIALIZE_MAP_CODE_END() diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 9503113f57e..f103f25b39f 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -214,7 +214,7 @@ namespace cryptonote::rpc { /// - /p status -- Generic RPC error code. "OK" is the success value. /// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. /// - /p hash -- Hash of the block at the current height - /// - /p immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 SN checkpoints. Omitted if not available. + /// - /p immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 MN checkpoints. Omitted if not available. /// - /p immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. struct GET_HEIGHT : PUBLIC, LEGACY, NO_ARGS { @@ -336,125 +336,153 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Look up one or more transactions by hash. + /// Look up one or more transactions by hash. + /// + /// Outputs: + /// + /// - /p status -- Generic RPC error code. "OK" is the success value. + /// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to + /// true, otherwise will be omitted. + /// - \p missed_tx -- list of transaction hashes that were not found. If all were found then this + /// field is omitted. + /// - \p txs -- list of transaction details; each element is a dict containing: + /// - \p tx_hash -- Transaction hash. + /// - \p size -- Size of the transaction, in bytes. Note that if the transaction has been pruned + /// this is the post-pruning size, not the original size. + /// - \p in_pool -- Will be set to true if the transaction is in the transaction pool (`true`) + /// and omitted if mined into a block. + /// - \p flash -- True if this is an approved, flash transaction; this information is generally + /// only available for approved in-pool transactions and txes in very recent blocks. + /// - \p block_height -- Block height including the transaction. Omitted for tx pool + /// transactions. + /// - \p block_timestamp -- Unix time at which the block has been added to the blockchain. + /// Omitted for tx pool transactions. + /// - \p output_indices -- List of transaction indexes. Omitted for tx pool transactions. + /// - \p relayed -- For `in_pool` transactions this field will be set to indicate whether the + /// transaction has been relayed to the network. + /// - \p double_spend_seen -- Will be set to true for tx pool transactions that are + /// double-spends (and thus cannot be added to the blockchain). Omitted for mined + /// transactions. + /// - \p received_timestamp -- Timestamp transaction was received in the pool. Omitted for + /// mined blocks. + /// - \p data -- Full, unpruned transaction data. For a json request this is hex-encoded; for a + /// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`, + /// `split`, or `prune` is requested; or if the transaction has been pruned in the database. + /// - \p pruned -- The non-prunable part of the transaction, encoded as hex (for json requests). + /// Always included if `split` or `prune` are specified; without those options it will be + /// included instead of `data` if the transaction has been pruned. + /// - \p prunable -- The prunable part of the transaction. Only included when `split` is + /// specified, the transaction is prunable, and the tx has not been pruned from the database. + /// - \p prunable_hash -- The hash of the prunable part of the transaction. Will be provided if + /// either: the tx has been pruned; or the tx is prunable and either of `prune` or `split` are + /// specified. + /// FIXME: drop this crap: + /// - \p as_json -- Transaction information parsed into json. Requires decode_as_json in request. + /// - \p extra -- Parsed "extra" transaction information; omitted unless specifically requested. + /// This is a dict containing one or more of the following keys. + /// - \p pubkey -- The tx extra public key + /// - \p burn_amount -- The amount of OXEN that this transaction burns + /// - \p extra_nonce -- Optional extra nonce value (in hex); will be empty if nonce is + /// recognized as a payment id + /// - \p payment_id -- The payment ID, if present. This is either a 16 hex character (8-byte) + /// encrypted payment id, or a 64 hex character (32-byte) deprecated, unencrypted payment ID + /// - \p mm_depth -- (Merge-mining) the merge-mined depth + /// - \p mm_root -- (Merge-mining) the merge mining merkle root hash + /// - \p additional_pubkeys -- Additional public keys + /// - \p mn_winner -- Master node block reward winner public key + /// - \p mn_pubkey -- Master node public key (e.g. for registrations, stakes, unlocks) + /// - \p mn_contributor -- Master node contributor wallet address (for stakes) + /// - \p tx_secret_key -- The transaction secret key, included in registrations/stakes to + /// decrypt transaction amounts and recipients + /// - \p locked_key_images -- Key image(s) locked by the transaction (for registrations, + /// stakes) + /// - \p key_image_unlock -- A key image being unlocked in a stake unlock request (an unlock + /// will be started for *all* key images locked in the same MN contributions). + /// - \p mn_registration -- Master node registration details; this is a dict containing: + /// - \p fee the operator fee expressed in millionths (i.e. 234567 == 23.4567%) + /// - \p expiry the unix timestamp at which the registration signature expires + /// - \p contributors: dict of (wallet => portion) pairs indicating the staking portions + /// reserved for the operator and any reserved contribution spots in the registration. + /// Portion is expressed in millionths (i.e. 250000 = 25% staking portion). + /// - \p mn_state_change -- Information for a "state change" transaction such as a + /// deregistration, decommission, recommission, or ip change reset transaction. This is a + /// dict containing: + /// - \p old_dereg will be set to true if this is an "old" deregistration transaction + /// (before the Loki 4 hardfork), omitted for more modern state change txes. + /// - \p type string indicating the state change type: "dereg", "decomm", "recomm", or "ip" + /// for a deregistration, decommission, recommission, or ip change penalty transaction. + /// - \p height the voting block height for the changing master node and voting master + /// nodes that produced this state change transaction. + /// - \p index the position of the affected node in the random list of tested nodes for this + /// `height`. + /// - \p voters the positions of validators in the testing quorum for this `height` who + /// tested and voted for this state change. This typically contains the first 7 voters + /// who voted for the state change (out of a possible set of 10). + /// - \p reasons list of reported reasons for a decommission or deregistration as reported + /// by the voting quorum. This contains any reasons that all 7+ voters agreed on, and + /// contains one or more of: + /// - \p "uptime" -- the master node was missing uptime proofs + /// - \p "checkpoints" -- the master node missed too many recent checkpoint votes + /// - \p "pos" -- the master node missed too many recent pos votes + /// - \p "storage" -- the master node's storage server was unreachable for too long + /// - \p "belnet" -- the master node's belnet router was unreachable for too long + /// - \p "timecheck" -- the master node's beldexd was not reachable for too many recent + /// time synchronization checks. (This generally means oxend's quorumnet port is not + /// reachable). + /// - \p "timesync" -- the master node's clock was too far out of sync + /// The list is omitted entirely if there are no reasons at all or if there are no reasons + /// that were agreed upon by all voting master nodes. + /// - \p reasons_maybe list of reported reasons that some but not all master nodes provided + /// for the deregistration/decommission. Possible values are identical to the above. + /// This list is omitted entirely if it would be empty (i.e. there are no reasons at all, + /// or all voting master nodes agreed on all given reasons). + /// - \p bns -- BNS registration or update transaction details. This contains keys: + /// - \p buy -- set to true if this is an BNS buy record; omitted otherwise. + /// - \p update -- set to true if this is an BNS record update; omitted otherwise. + /// - \p renew -- set to true if this is an BNS renewal; omitted otherwise. + /// - \p bchat_value -- the BNS request type string. For registrations: "bchat", + /// for a record update: "update". + /// - \p wallet_value -- the BNS request type string. For registrations: "wallet", + /// for a record update: "update". + /// - \p belnet_value -- the BNS request type string. For registrations: "belnet", + /// for a record update: "update". + /// - \p blocks -- The registration length in blocks; omitted for registrations (such as + /// Session/Wallets) that do not expire. + /// - \p name_hash -- The hashed name of the record being purchased/updated. Encoded in hex + /// for json requests. Note that the actual name is not provided on the blockchain. + /// - \p prev_txid -- For an update this field is set to the txid of the previous BNS update + /// or registration (i.e. the most recent transaction that this record is updating). + /// - \p value -- The encrypted value of the record (in hex for json requests) being + /// set/updated. \see ONS_RESOLVE for details on encryption/decryption. + /// - \p owner -- the owner of this record being set in a registration or update; this can + /// be a primary wallet address, wallet subaddress, or a plain public key. + /// - \p backup_owner -- an optional backup owner who also has permission to edit the + /// record. + /// - \p stake_amount -- If `stake_info` is explicitly requested then this field will be set to + /// the calculated transaction stake amount (only applicable if the transaction is a master + /// node registration or stake). struct GET_TRANSACTIONS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transactions", "gettransactions"); } - - // Information from a transactions tx-extra fields. Fields within this will only be populated - // when actually found in the transaction. (Requires tx_extra=true in the request). - struct extra_entry - { - struct mn_reg_info - { - struct contribution - { - std::string wallet; // Contributor wallet - uint32_t portion; // Reserved portion, as the rounded nearest value out of 1'000'000 (i.e. 234567 == 23.4567%). - KV_MAP_SERIALIZABLE - }; - - std::vector contributors; // Operator contribution plus any reserved contributions - uint32_t fee; // Operator fee, as the rounded nearest value out of 1'000'000 - uint64_t expiry; // unix timestamp at which the registration expires - KV_MAP_SERIALIZABLE - }; - struct state_change - { - std::optional old_dereg; // Will be present and set to true iff this record is an old (pre-HF12) deregistration field - std::string type; // "dereg", "decom", "recom", or "ip" indicating the state change type - uint64_t height; // The voting block height for the changing master node and validators - uint32_t index; // The index of all tested nodes at the given height for which this state change applies - std::vector voters; // The position of validators in the testing quorum who validated and voted for this state change. This typically contains just 7 required voter slots (of 10 eligible voters). - std::optional> reasons; // Reasons for the decommissioning/deregistration as reported by the voting quorum. This contains any reasons that all voters agreed on, one or more of: "uptime" (missing uptime proofs), "checkpoints" (missed checkpoint votes), "POS" (missing POS votes), "storage" (storage server pings failed), "belnet" (belnet router unreachable), "timecheck" (time sync pings failed), "timesync" (time was out of sync) - std::optional> reasons_maybe; // If present, this contains any decomm/dereg reasons that were given by some but not all quorum voters - KV_MAP_SERIALIZABLE - }; - struct bns_details - { - uint8_t version; // The version which is given when the registrations version = 1 after the hf18 - std::optional buy; // Provided and true iff this is an BNS buy record - std::optional update; // Provided and true iff this is an BNS record update - std::optional renew; // Provided and true iff this is an BNS record renewal - std::optional type; // The BNS request type. For registrations: "belnet", "bchat", "wallet"; for a record update: "update" - std::optional blocks; // The registration length in blocks (only applies to belnet registrations; bchat/wallet registrations do not expire) - std::string name_hash; // The hashed name of the record being purchased/updated, in hex (the actual name is not provided on the blockchain). - std::optional prev_txid; // For an update, this points at the txid of the previous bns update transaction. - std::optional value_bchat; // The encrypted value of the record, in hex for the bchat. Note that this is encrypted using the actual name itself (*not* the hashed name). - std::optional value_wallet; // The encrypted value of the record, in hex for the wallet. Note that this is encrypted using the actual name itself (*not* the hashed name). - std::optional value_belnet; // The encrypted value of the record, in hex for the belnet. Note that this is encrypted using the actual name itself (*not* the hashed name). - std::optional value_eth_addr;// The encrypted value of the record, in hex for the belnet. Note that this is encrypted using the actual name itself (*not* the hashed name). - std::optional owner; // The owner of this record; this can be a main wallet, wallet subaddress, or a plain public key. - std::optional backup_owner; // Backup owner wallet/pubkey of the record, if provided. - KV_MAP_SERIALIZABLE - }; - - std::optional pubkey; // The tx extra public key - std::optional burn_amount; // The amount of BELDEX that this transaction burns - std::optional extra_nonce; // Optional extra nonce value (in hex); will be empty if nonce is recognized as a payment id - std::optional payment_id; // The payment ID, if present. This is either a 16 hex character (8-byte) encrypted payment id, or a 64 hex character (32-byte) deprecated, unencrypted payment ID - std::optional mm_depth; // (Merge-mining) the merge-mined depth - std::optional mm_root; // (Merge-mining) the merge mining merkle root hash - std::vector additional_pubkeys; // Additional public keys - std::optional mn_winner; // Master node block reward winner public key - std::optional mn_pubkey; // Master node public key (e.g. for registrations, stakes, unlocks) - std::optional security_sig; // Security Signature - std::optional mn_registration; // Master node registration details - std::optional mn_contributor; // Master node contributor wallet address (for stakes) - std::optional mn_state_change; // A state change transaction (deregistration, decommission, recommission, ip change) - std::optional tx_secret_key; // The transaction secret key, included in registrations/stakes to decrypt transaction amounts and recipients - std::vector locked_key_images; // Key image(s) locked by the transaction (for registrations, stakes) - std::optional key_image_unlock; // A key image being unlocked in a stake unlock request (an unlock will be started for *all* key images locked in the same MN contributions). - std::optional bns; // an BNS registration or update - KV_MAP_SERIALIZABLE - }; - - struct entry - { - std::string tx_hash; // Transaction hash. - std::optional as_hex; // Full transaction information as a hex string. Always omitted if any of `decode_as_json`, `split`, or `prune` is requested; or if the transaction has been pruned in the database. - std::optional pruned_as_hex; // The non-prunable part of the transaction. Always included if `split` or `prune` and specified; without those options it will be included instead of `as_hex` if the transaction has been pruned. - std::optional prunable_as_hex; // The prunable part of the transaction. Only included when `split` is specified, the transaction is prunable, and the tx has not been pruned from the database. - std::optional prunable_hash; // The hash of the prunable part of the transaction. Will be provided if either: the tx has been pruned; or the tx is prunable and either of `prune` or `split` are specified. - std::optional as_json; // Transaction information parsed into json. Requires decode_as_json in request. - uint32_t size; // Size of the transaction, in bytes. Note that if the transaction has been pruned this is the post-pruning size, not the original size. - bool in_pool; // States if the transaction is in pool (`true`) or included in a block (`false`). - bool double_spend_seen; // States if the transaction is a double-spend (`true`) or not (`false`). - uint64_t block_height; // Block height including the transaction. - uint64_t block_timestamp; // Unix time at which the block has been added to the blockchain. - std::vector output_indices; // List of transaction indexes. - uint64_t received_timestamp; // Timestamp transaction was received in the pool. - bool relayed; - bool flash; // True if this is an approved, flash transaction (only available for in_pool transactions or txes in recent blocks) - std::optional extra; // Parsed tx_extra information (only if requested) - std::optional stake_amount; // Calculated transaction stake amount, if a staking/registration transaction and `stake_info=true` is requested. - - KV_MAP_SERIALIZABLE - }; - - struct request - { - std::vector txs_hashes; // List of transaction hashes to look up. - bool decode_as_json; // Optional (`false` by default). If set true, the returned transaction information will be decoded. - bool tx_extra; // Parse tx-extra information - bool split; // Always split transactions into non-prunable and prunable parts in the response. `False` by default. - bool prune; // Like `split`, but also omits the prunable part (or details, for decode_as_json) of transactions from the response. `False` by default. - bool stake_info; // If true, calculate staking amount for staking/registration transactions - - KV_MAP_SERIALIZABLE - }; - - - struct response + struct request_parameters { - std::vector missed_tx; // (Optional - returned if not empty) Transaction hashes that could not be found. - std::vector txs; // Array of tx data - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// List of transaction hashes to look up. (Will also be accepted as json input key + /// "txs_hashes" for backwards compatibility). + std::vector tx_hashes; + /// If set to true, the returned transaction information will be decoded. + bool decode_as_json = false; + /// If set to true then parse and return tx-extra information + bool tx_extra = false; + /// If set to true then always split transactions into non-prunable and prunable parts in the + /// response. + bool split = false; + /// Like `split`, but also omits the prunable part (or details, for decode_as_json) of + /// transactions from the response. + bool prune = false; + /// If true then calculate staking amount for staking/registration transactions + bool stake_info = false; + } request; - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -1175,7 +1203,7 @@ namespace cryptonote::rpc { }; BELDEX_RPC_DOC_INTROSPECT - struct tx_info + struct old_tx_info { std::string id_hash; // The transaction ID hash. std::string tx_json; // JSON structure of all information in the transaction @@ -1194,7 +1222,7 @@ namespace cryptonote::rpc { bool double_spend_seen; // States if this transaction has been seen as double spend. std::string tx_blob; // Hexadecimal blob represnting the transaction. bool flash; // True if this is a signed flash transaction - std::optional extra; // Parsed tx_extra information (only if requested) + // std::optional extra; // Parsed tx_extra information (only if requested) std::optional stake_amount; // Will be set to the staked amount if the transaction is a staking transaction *and* stake amounts were requested. KV_MAP_SERIALIZABLE @@ -1227,7 +1255,7 @@ namespace cryptonote::rpc { struct response { std::string status; // General RPC error code. "OK" means everything looks good. - std::vector transactions; // List of transactions in the mempool are not in a block on the main chain at the moment: + std::vector transactions; // List of transactions in the mempool are not in a block on the main chain at the moment: std::vector spent_key_images; // List of spent output key images: bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). @@ -2108,7 +2136,7 @@ namespace cryptonote::rpc { /// node is still syncing the chain. BELDEX_RPC_DOC_INTROSPECT /// - \p block_hash the hash of the most recent block struct master_node_contributor /// - \p hardfork the current hardfork version of the daemon - { /// - \p snode_revision the current snode revision for non-hardfork, but mandatory, master node + { /// - \p mnode_revision the current mnode revision for non-hardfork, but mandatory, master node uint64_t amount; // The total amount of locked Loki in atomic units for this contributor. /// updates. uint64_t reserved; // The amount of Loki in atomic units reserved by this contributor for this Master Node. /// - \p status generic RPC error code; "OK" means the request was successful. std::string address; // The wallet address for this contributor rewards are sent to and contributions came from. /// - \p unchanged when using poll_block_hash, this value is set to true and results are omitted if @@ -2123,20 +2151,20 @@ namespace cryptonote::rpc { // Get information on some, all, or a random subset of Master Nodes. /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). /// - \p registration_height The height at which the registration for the Master Node arrived /// on the blockchain. - /// - \p registration_hf_version The current hard fork at which the registration for the Service + /// - \p registration_hf_version The current hard fork at which the registration for the Master /// Node arrived on the blockchain. - /// - \p requested_unlock_height If an unlock has been requested for this SN, this field + /// - \p requested_unlock_height If an unlock has been requested for this MN, this field /// contains the height at which the Master Node registration expires and contributions will /// be released. /// - \p last_reward_block_height The height that determines when this master node will next /// receive a reward. This field is somewhat misnamed for historic reasons: it is updated - /// when receiving a reward, but is also updated when a SN is activated, recommissioned, or + /// when receiving a reward, but is also updated when a MN is activated, recommissioned, or /// has an IP change position reset, and so does not strictly indicate when a reward was /// received. /// - \p last_reward_transaction_index When multiple Master Nodes register (or become /// active/reactivated) at the same height (i.e. have the same last_reward_block_height), this /// field contains the activating transaction position in the block which is used to break - /// ties in determining which SN is next in the reward list. + /// ties in determining which MN is next in the reward list. /// - \p active True if fully funded and not currently decommissioned (and so `funded && /// !active` implicitly defines decommissioned). /// - \p funded True if the required stakes have been submitted to activate this Master Node. @@ -2149,7 +2177,7 @@ namespace cryptonote::rpc { /// - \p decommission_count The number of times the Master Node has been decommissioned since /// registration /// - \p last_decommission_reason_consensus_all The reason for the last decommission as voted by - /// the testing quorum SNs that decommissioned the node. This is a numeric bitfield made up + /// the testing quorum MNs that decommissioned the node. This is a numeric bitfield made up /// of the sum of given reasons (multiple reasons may be given for a decommission). Values /// are included here if *all* quorum members agreed on the reasons: /// - \c 0x01 - Missing uptime proofs @@ -2162,7 +2190,7 @@ namespace cryptonote::rpc { /// - \c 0x50 - Multi_mn_accept_range_not_met /// - other bit values are reserved for future use. /// - \p last_decommission_reason_consensus_any The reason for the last decommission as voted by - /// *any* SNs. Reasons are set here if *any* quorum member gave a reason, even if not all + /// *any* MNs. Reasons are set here if *any* quorum member gave a reason, even if not all /// quorum members agreed. Bit values are the same as \p /// last_decommission_reason_consensus_all. /// - \p decomm_reasons - a gentler version of the last_decommission_reason_consensus_all/_any @@ -2216,10 +2244,10 @@ namespace cryptonote::rpc { /// fraction of 18446744073709551612 (2^64 - 4) (that is, this number corresponds to 100%). /// Note that some JSON parsers may silently change this value while parsing as typical values /// do not fit into a double without loss of precision. - /// - \p operator_fee The operator fee expressed as thousandths of a percent (and rounded to the - /// nearest integer value). That is, 100000 corresponds to a 100% fee, 5456 corresponds to a - /// 5.456% fee. Note that this number is for human consumption; the actual value that matters - /// for the blockchain is the precise \p portions_for_operator value. + /// - \p operator_fee The operator fee expressed in millionths (and rounded to the nearest + /// integer value). That is, 1000000 corresponds to a 100% fee, 34567 corresponds to a + /// 3.4567% fee. Note that this number is for human consumption; the actual value that + /// matters for the blockchain is the precise \p portions_for_operator value. /// - \p swarm_id The numeric identifier of the Master Node's current swarm. Note that /// returned values can exceed the precision available in a double value, which can result in /// (changed) incorrect values by some JSON parsers. Consider using \p swarm instead if you @@ -2232,7 +2260,7 @@ namespace cryptonote::rpc { /// uptime proof yet. /// - \p storage_lmq_port The port number associated with the storage server (oxenmq interface); /// omitted if we have no uptime proof yet. - /// - \p quorumnet_port The port for direct SN-to-SN beldexd communication (oxenmq interface). + /// - \p quorumnet_port The port for direct MN-to-MN beldexd communication (oxenmq interface). /// Omitted if we have no uptime proof yet. /// - \p pubkey_ed25519 The master node's ed25519 public key for auxiliary services. Omitted if /// we have no uptime proof yet. Note that for newer registrations this will be the same as @@ -2242,7 +2270,7 @@ namespace cryptonote::rpc { /// - \p last_uptime_proof The last time we received an uptime proof for this master node from /// the network, in unix epoch time. 0 if we have never received one. /// - \p storage_server_reachable True if this storage server is currently passing tests for the - /// purposes of SN node testing: true if the last test passed, or if it has been unreachable + /// purposes of MN node testing: true if the last test passed, or if it has been unreachable /// for less than an hour; false if it has been failing tests for more than an hour (and thus /// is considered unreachable). This field is omitted if the queried beldexd is not a master /// node. @@ -2263,14 +2291,14 @@ namespace cryptonote::rpc { /// - \p belnet_last_reachable Same as \p storage_server_last_reachable, but for belnet router /// testing. /// - \p checkpoint_votes dict containing recent received checkpoint voting information for this - /// master node. Service node tests will fail if too many recent POS blocks are missed. + /// master node. Master node tests will fail if too many recent POS blocks are missed. /// Contains keys: /// - \p voted list of blocks heights at which a required vote was received from this /// master node /// - \p missed list of block heights at which a vote from this master node was required /// but not received. /// - \p POS_votes dict containing recent POS blocks in which this master node was supposed - /// to have participated. Service node testing will fail if too many recent POS blocks are + /// to have participated. Master node testing will fail if too many recent POS blocks are /// missed. Contains keys: /// - \p voted list of [HEIGHT,ROUND] pairs in which an expected POS participation was /// recorded for this node. ROUND starts at 0 and increments for backup POS quorums if a @@ -2323,7 +2351,7 @@ namespace cryptonote::rpc { /// If specified then only return results if the current top block hash is different than the /// hash given here. This is intended to allow quick polling of results without needing to do - /// anything if the block (and thus SN registrations) have not changed since the last request. + /// anything if the block (and thus MN registrations) have not changed since the last request. crypto::hash poll_block_hash = crypto::hash::null(); } request; @@ -2341,7 +2369,7 @@ namespace cryptonote::rpc { /// (such as remote testing results) will not be available (through this call or \p /// "get_master_nodes") because a master node is incapable of testing itself for remote /// connectivity. If this daemon is running in master node mode but not registered then only - /// SN pubkey, ip, and port fields are returned. + /// MN pubkey, ip, and port fields are returned. /// - \p height current top block height at the time of the request (note that this is generally /// one less than the "blockchain height"). /// - \p block_hash current top block hash at the time of the request @@ -2811,6 +2839,7 @@ namespace cryptonote::rpc { GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_BACKLOG, GET_TRANSACTION_POOL_STATS, + GET_TRANSACTIONS, GET_MASTER_NODES, GET_MASTER_NODE_STATUS, // Deprecated Monero NIH binary endpoints: @@ -2825,7 +2854,6 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN >; using FIXME_old_rpc_types = tools::type_list< - GET_TRANSACTIONS, IS_KEY_IMAGE_SPENT, SEND_RAW_TX, GET_NET_STATS, diff --git a/src/rpc/rpc_binary.h b/src/rpc/rpc_binary.h index ea36f2c709b..b6651e588bd 100644 --- a/src/rpc/rpc_binary.h +++ b/src/rpc/rpc_binary.h @@ -25,6 +25,11 @@ namespace cryptonote::rpc { template inline constexpr bool is_binary_vector> = is_binary_parameter; + // De-referencing wrappers around the above: + template inline constexpr bool is_binary_parameter = is_binary_parameter; + template inline constexpr bool is_binary_parameter = is_binary_parameter; + template inline constexpr bool is_binary_vector = is_binary_vector; + template inline constexpr bool is_binary_vector = is_binary_vector; void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); @@ -58,6 +63,10 @@ namespace cryptonote::rpc { return json_binary_proxy{e[std::forward(key)], format}; } + /// Returns a binary value proxy around the first/last element (requires an underlying list) + json_binary_proxy front() { return json_binary_proxy{e.front(), format}; } + json_binary_proxy back() { return json_binary_proxy{e.back(), format}; } + /// Assigns binary data from a string_view/string/etc. nlohmann::json& operator=(std::string_view binary_data); From 54b410f74eb789d4a70573c20e8322787a5c0049 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sun, 20 Apr 2025 21:10:15 +0530 Subject: [PATCH 052/182] RPC: add support for fetching transaction pool This drops the dedicated GET_TRANSACTION_POOL endpoint entirely and folds the txpool fetching into GET_TRANSACTIONS via a new `memory_pool` parameter. (This is backwards incompatible, of course, but the results may not be too harmful: the wallet doesn't actually use this endpoint, so it mainly affects internal beldexd commands (fixed here) and the block explorer (which is fairly easily fixed). This also removes some of the useless GET_TRANSACTIONS bits such as decode_as_json. `fee` and `burned` are now always provided in the return (for both specific requested transactions and mempool transactions). As a side effect, this code ventured deep into core/blockchain in terms of dealing with missed transactions: previously they were always returned (via lvalue ref) as a vector, and always had to be specified. This changes to take via pointer to an unordered_set, which can be null if you don't care about the missed ones. This reduces several calls that indeed didn't care about collecting the missed values, and improved virtually all the places that *did* care which need to query which ones were missed rather than needing a specific order. --- src/cryptonote_core/blockchain.cpp | 69 ++-- src/cryptonote_core/blockchain.h | 12 +- src/cryptonote_core/cryptonote_core.cpp | 18 +- src/cryptonote_core/cryptonote_core.h | 12 +- src/cryptonote_core/tx_pool.cpp | 7 +- src/cryptonote_core/tx_pool.h | 2 +- .../cryptonote_protocol_handler.inl | 26 +- src/daemon/command_parser_executor.cpp | 4 +- src/daemon/daemon.cpp | 1 + src/daemon/rpc_command_executor.cpp | 132 ++++---- src/daemon/rpc_command_executor.h | 4 +- src/rpc/core_rpc_server.cpp | 302 ++++++++---------- src/rpc/core_rpc_server.h | 10 +- src/rpc/core_rpc_server_command_parser.cpp | 19 +- src/rpc/core_rpc_server_commands_defs.cpp | 68 ++-- src/rpc/core_rpc_server_commands_defs.h | 112 +++---- src/rpc/rpc_binary.h | 15 +- 17 files changed, 380 insertions(+), 433 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index ede4846748c..fac08606077 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -312,15 +312,11 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() using clock = std::chrono::steady_clock; using work_time = std::chrono::duration; - int64_t constexpr BLOCK_COUNT = 1000; + constexpr int64_t BLOCK_COUNT = 1000; auto work_start = clock::now(); auto scan_start = work_start; work_time bns_duration{}, mnl_duration{}, bns_iteration_duration{}, mnl_iteration_duration{}; - std::vector blocks; - std::vector txs; - std::vector missed_txs; - for (int64_t block_count = total_blocks, index = 0; block_count > 0; @@ -343,7 +339,7 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() bns_iteration_duration = mnl_iteration_duration = {}; } - blocks.clear(); + std::vector blocks; uint64_t height = start_height + (index * BLOCK_COUNT); if (!get_blocks_only(height, static_cast(BLOCK_COUNT), blocks)) { @@ -355,9 +351,8 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() { uint64_t block_height = get_block_height(blk); - txs.clear(); - missed_txs.clear(); - if (!get_transactions(blk.tx_hashes, txs, missed_txs)) + std::vector txs; + if (!get_transactions(blk.tx_hashes, txs)) { MERROR("Unable to get transactions for block for updating BNS DB: " << cryptonote::get_block_hash(blk)); return false; @@ -2007,8 +2002,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id // NOTE: Execute Alt Block Hooks { std::vector txs; - std::vector missed; - if (!get_transactions(b.tx_hashes, txs, missed)) + std::unordered_set missed; + if (!get_transactions(b.tx_hashes, txs, &missed)) { bvc.m_verifivation_failed = true; return false; @@ -2202,8 +2197,8 @@ bool Blockchain::get_blocks_only(uint64_t start_offset, size_t count, std::vecto { for(const auto& blk : blocks) { - std::vector missed_ids; - get_transactions_blobs(blk.tx_hashes, *txs, missed_ids); + std::unordered_set missed_ids; + get_transactions_blobs(blk.tx_hashes, *txs, &missed_ids); CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain"); } } @@ -2225,8 +2220,8 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector missed_ids; - get_transactions_blobs(blk.second.tx_hashes, txs, missed_ids); + std::unordered_set missed_ids; + get_transactions_blobs(blk.second.tx_hashes, txs, &missed_ids); CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "has missed transactions in own block in main blockchain"); } @@ -2272,7 +2267,11 @@ bool Blockchain::handle_get_blocks(NOTIFY_REQUEST_GET_BLOCKS::request& arg, NOTI db_rtxn_guard rtxn_guard (m_db); rsp.current_blockchain_height = get_current_blockchain_height(); std::vector> blocks; - get_blocks(arg.blocks, blocks, rsp.missed_ids); + { + std::unordered_set missed_ids; + get_blocks(arg.blocks, blocks, &missed_ids); + rsp.missed_ids.insert(rsp.missed_ids.end(), missed_ids.begin(), missed_ids.end()); + } uint64_t const top_height = (m_db->height() - 1); uint64_t const earliest_height_to_sync_checkpoints_granularly = @@ -2310,8 +2309,8 @@ bool Blockchain::handle_get_blocks(NOTIFY_REQUEST_GET_BLOCKS::request& arg, NOTI // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids // is for missed blocks, not missed transactions as well. - std::vector missed_tx_ids; - get_transactions_blobs(block.tx_hashes, block_entry.txs, missed_tx_ids); + std::unordered_set missed_tx_ids; + get_transactions_blobs(block.tx_hashes, block_entry.txs, &missed_tx_ids); for (auto &h : block.tx_hashes) { @@ -2353,10 +2352,10 @@ bool Blockchain::handle_get_txs(NOTIFY_REQUEST_GET_TXS::request& arg, NOTIFY_NEW std::lock(blockchain_lock, flash_lock); db_rtxn_guard rtxn_guard (m_db); - std::vector missed; + std::unordered_set missed; // First check the blockchain for any txs: - get_transactions_blobs(arg.txs, rsp.txs, missed); + get_transactions_blobs(arg.txs, rsp.txs, &missed); // Look for any missed txes in the mempool: m_tx_pool.find_transactions(missed, rsp.txs); @@ -2605,7 +2604,7 @@ uint64_t Blockchain::block_difficulty(uint64_t i) const //------------------------------------------------------------------ //TODO: return type should be void, throw on exception // alternatively, return true only if no blocks missed -bool Blockchain::get_blocks(const std::vector& block_ids, std::vector>& blocks, std::vector& missed_bs) const +bool Blockchain::get_blocks(const std::vector& block_ids, std::vector>& blocks, std::unordered_set* missed_bs) const { LOG_PRINT_L3("Blockchain::" << __func__); std::unique_lock lock{*this}; @@ -2623,11 +2622,11 @@ bool Blockchain::get_blocks(const std::vector& block_ids, std::vec { LOG_ERROR("Invalid block: " << block_hash); blocks.pop_back(); - missed_bs.push_back(block_hash); + if (missed_bs) missed_bs->insert(block_hash); } } else - missed_bs.push_back(block_hash); + if (missed_bs) missed_bs->insert(block_hash); } catch (const std::exception& e) { @@ -2639,7 +2638,7 @@ bool Blockchain::get_blocks(const std::vector& block_ids, std::vec //------------------------------------------------------------------ //TODO: return type should be void, throw on exception // alternatively, return true only if no transactions missed -bool Blockchain::get_transactions_blobs(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs, bool pruned) const +bool Blockchain::get_transactions_blobs(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs, bool pruned) const { LOG_PRINT_L3("Blockchain::" << __func__); std::unique_lock lock{*this}; @@ -2654,8 +2653,8 @@ bool Blockchain::get_transactions_blobs(const std::vector& txs_ids txs.push_back(std::move(tx)); else if (!pruned && m_db->get_tx_blob(tx_hash, tx)) txs.push_back(std::move(tx)); - else - missed_txs.push_back(tx_hash); + else if (missed_txs) + missed_txs->insert(tx_hash); } catch (const std::exception& e) { @@ -2689,7 +2688,7 @@ size_t get_transaction_version(const cryptonote::blobdata &bd) return version; } //------------------------------------------------------------------ -bool Blockchain::get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::vector& missed_txs) const +bool Blockchain::get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::unordered_set* missed_txs) const { LOG_PRINT_L3("Blockchain::" << __func__); std::unique_lock lock{*this}; @@ -2711,8 +2710,8 @@ bool Blockchain::get_split_transactions_blobs(const std::vector& t if (!m_db->get_prunable_tx_blob(tx_hash, prunable)) prunable.clear(); } - else - missed_txs.push_back(tx_hash); + else if (missed_txs) + missed_txs->insert(tx_hash); } catch (const std::exception& e) { @@ -2722,7 +2721,7 @@ bool Blockchain::get_split_transactions_blobs(const std::vector& t return true; } //------------------------------------------------------------------ -bool Blockchain::get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs) const +bool Blockchain::get_transactions(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs) const { LOG_PRINT_L3("Blockchain::" << __func__); std::unique_lock lock{*this}; @@ -2743,8 +2742,8 @@ bool Blockchain::get_transactions(const std::vector& txs_ids, std: return false; } } - else - missed_txs.push_back(tx_hash); + else if (missed_txs) + missed_txs->insert(tx_hash); } catch (const std::exception& e) { @@ -2843,9 +2842,9 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons } else { - std::vector mis; - get_transactions_blobs(b.tx_hashes, txs, mis, pruned); - CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); + std::unordered_set mis; + get_transactions_blobs(b.tx_hashes, txs, &mis, pruned); + CHECK_AND_ASSERT_MES(mis.empty(), false, "internal error, transaction from block not found"); } size += blocks.back().first.first.size(); for (const auto &t: txs) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index aaa8e29b152..07618d15c41 100755 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -717,25 +717,25 @@ namespace cryptonote * * @param block_ids a vector of block hashes for which to get the corresponding blocks * @param blocks return-by-reference a vector to store result blocks in - * @param missed_bs return-by-reference a vector to store missed blocks in + * @param missed_bs optional pointer to an unordered_set to add missed blocks ids to * * @return false if an unexpected exception occurs, else true */ - bool get_blocks(const std::vector& block_ids, std::vector>& blocks, std::vector& missed_bs) const; + bool get_blocks(const std::vector& block_ids, std::vector>& blocks, std::unordered_set* missed_bs = nullptr) const; /** * @brief gets transactions based on a list of transaction hashes * * @param txs_ids a vector of hashes for which to get the corresponding transactions * @param txs return-by-reference a vector to store result transactions in - * @param missed_txs return-by-reference a vector to store missed transactions in + * @param missed_txs optional pointer to an unordered set to add missed transactions ids to * @param pruned whether to return full or pruned blobs * * @return false if an unexpected exception occurs, else true */ - bool get_transactions_blobs(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs, bool pruned = false) const; - bool get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::vector& missed_txs) const; - bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs) const; + bool get_transactions_blobs(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs = nullptr, bool pruned = false) const; + bool get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::unordered_set* missed_txs = nullptr) const; + bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs = nullptr) const; /** * @brief looks up transactions based on a list of transaction hashes and returns the block diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 1b1f0f9fdb9..914f637629c 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -453,17 +453,22 @@ namespace cryptonote return m_blockchain_storage.get_blocks_only(start_offset, count, blocks); } //----------------------------------------------------------------------------------------------- - bool core::get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs) const + bool core::get_blocks(const std::vector& block_ids, std::vector> blocks, std::unordered_set* missed_bs) const + { + return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); + } + //----------------------------------------------------------------------------------------------- + bool core::get_transactions(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs) const { return m_blockchain_storage.get_transactions_blobs(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - bool core::get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::vector& missed_txs) const + bool core::get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::unordered_set* missed_txs) const { return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - bool core::get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs) const + bool core::get_transactions(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs) const { return m_blockchain_storage.get_transactions(txs_ids, txs, missed_txs); } @@ -1811,9 +1816,8 @@ namespace cryptonote [this, &cache_to, &result, &cache_build_started](uint64_t height, const crypto::hash& hash, const block& b){ auto& [emission_amount, total_fee_amount, burnt_beldex] = *result; std::vector txs; - std::vector missed_txs; auto coinbase_amount = static_cast(get_outs_money_amount(b.miner_tx)); - get_transactions(b.tx_hashes, txs, missed_txs); + get_transactions(b.tx_hashes, txs); int64_t tx_fee_amount = 0; for(const auto& tx: txs) { @@ -2115,9 +2119,9 @@ namespace cryptonote } else if(bvc.m_added_to_main_chain) { - std::vector missed_txs; + std::unordered_set missed_txs; std::vector txs; - m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, missed_txs); + m_blockchain_storage.get_transactions_blobs(b.tx_hashes, txs, &missed_txs); if(missed_txs.size() && m_blockchain_storage.get_block_id_by_height(get_block_height(b)) != get_block_hash(b)) { LOG_PRINT_L1("Block found but, seems that reorganize just happened after that, do not relay this block"); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 3f6b19172e3..420b682b034 100755 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -511,11 +511,7 @@ namespace cryptonote * * @note see Blockchain::get_blocks(const t_ids_container&, t_blocks_container&, t_missed_container&) const */ - template - bool get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const - { - return m_blockchain_storage.get_blocks(block_ids, blocks, missed_bs); - } + bool get_blocks(const std::vector& block_ids, std::vector> blocks, std::unordered_set* missed_bs = nullptr) const; /** * @copydoc Blockchain::get_block_id_by_height @@ -529,21 +525,21 @@ namespace cryptonote * * @note see Blockchain::get_transactions */ - bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs) const; + bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs = nullptr) const; /** * @copydoc Blockchain::get_transactions * * @note see Blockchain::get_transactions */ - bool get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::vector& missed_txs) const; + bool get_split_transactions_blobs(const std::vector& txs_ids, std::vector>& txs, std::unordered_set* missed_txs = nullptr) const; /** * @copydoc Blockchain::get_transactions * * @note see Blockchain::get_transactions */ - bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::vector& missed_txs) const; + bool get_transactions(const std::vector& txs_ids, std::vector& txs, std::unordered_set* missed_txs = nullptr) const; /** * @copydoc Blockchain::get_block_by_hash diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index a302da990e7..e7a6b80fd2d 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1320,7 +1320,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - int tx_memory_pool::find_transactions(const std::vector &tx_hashes, std::vector &txblobs) const + int tx_memory_pool::find_transactions(const std::unordered_set& tx_hashes, std::vector& txblobs) const { if (tx_hashes.empty()) return 0; @@ -1532,16 +1532,13 @@ namespace cryptonote if (m_blockchain.get_blocks_only(immutable + 1, height, blocks)) { std::vector txs; - std::vector missed_txs; uint64_t earliest = height; for (auto it = blocks.rbegin(); it != blocks.rend(); it++) { const auto& block = *it; auto block_height = cryptonote::get_block_height(block); txs.clear(); - missed_txs.clear(); - - if (!m_blockchain.get_transactions(block.tx_hashes, txs, missed_txs)) + if (!m_blockchain.get_transactions(block.tx_hashes, txs)) { MERROR("Unable to get transactions for block " << block.hash); can_fix_with_a_rollback = false; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 15c1a672e8f..87ee2eb9588 100755 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -474,7 +474,7 @@ namespace cryptonote * * @return number of transactions added to txblobs */ - int find_transactions(const std::vector &tx_hashes, std::vector &txblobs) const; + int find_transactions(const std::unordered_set& tx_hashes, std::vector& txblobs) const; /** * @brief get a list of all relayable transactions and their hashes diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 4644d4b8036..cf5c68b80c9 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -41,7 +41,9 @@ #include #include #include +#include +#include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "common/string_util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/hardfork.h" @@ -755,9 +757,9 @@ namespace cryptonote { std::vector tx_ids; std::vector txes; - std::vector missing; + std::unordered_set missing; tx_ids.push_back(tx_hash); - if (m_core.get_transactions(tx_ids, txes, missing) && missing.empty()) + if (m_core.get_transactions(tx_ids, txes, &missing) && missing.empty()) { if (txes.size() == 1) { @@ -1047,8 +1049,8 @@ namespace cryptonote } std::vector txs; - std::vector missed; - if (!m_core.get_transactions(txids, txs, missed)) + std::unordered_set missed; + if (!m_core.get_transactions(txids, txs, &missed)) { LOG_ERROR_CCONTEXT("Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX, " << "failed to get requested transactions"); @@ -2389,18 +2391,20 @@ skip: << "Use the \"help\" command to see the list of available commands.\n" << "**********************************************************************"); m_sync_timer.pause(); - if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info")) + if (CLOG_ENABLED(Info, "sync-info")) { const auto sync_time = m_sync_timer.value(); const auto add_time = m_add_timer.value(); if (sync_time > 0ns && add_time > 0ns) { - MCLOG_YELLOW(el::Level::Info, "sync-info", "Sync time: " << tools::friendly_duration(sync_time) << " min, idle time " << - (100.f * (1.0f - add_time / sync_time)) << "%" << ", " << - (10 * m_sync_download_objects_size / 1024 / 1024) / 10.f << " + " << - (10 * m_sync_download_chain_size / 1024 / 1024) / 10.f << " MB downloaded, " << - 100.0f * m_sync_old_spans_downloaded / m_sync_spans_downloaded << "% old spans, " << - 100.0f * m_sync_bad_spans_downloaded / m_sync_spans_downloaded << "% bad spans"); + MCLOG_YELLOW(el::Level::Info, "sync-info", + fmt::format("Sync time: {}, idle time {:.2f}%, {:.1f} + {:.1f} MB downloaded, {:.2f}% old spans, {:2f}% bad spans", + tools::friendly_duration(sync_time), + ((sync_time - add_time) / sync_time) * 100.0, + m_sync_download_objects_size / 1'000'000.0, + m_sync_download_chain_size / 1'000'000.0, + 100.0 * m_sync_old_spans_downloaded / m_sync_spans_downloaded, + 100.0 * m_sync_bad_spans_downloaded / m_sync_spans_downloaded)); } } m_core.on_synchronized(); diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 186fe74e604..2d462b8c8ab 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -441,14 +441,14 @@ bool command_parser_executor::print_transaction_pool_long(const std::vector& args) { if (!args.empty()) return false; - return m_executor.print_transaction_pool_short(); + return m_executor.print_transaction_pool(false); } bool command_parser_executor::print_transaction_pool_stats(const std::vector& args) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 9761865fca5..e4ff8f27679 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -44,6 +44,7 @@ #include "rpc/rpc_args.h" #include "rpc/http_server.h" #include "rpc/lmq_server.h" +#include "rpc/bootstrap_daemon.h" #include "cryptonote_protocol/quorumnet.h" #include "cryptonote_core/uptime_proof.h" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index a3b1c0d521a..3937292f557 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -935,99 +935,87 @@ bool rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) { return false; } -static void print_pool(const std::vector &transactions, bool include_json) { - if (transactions.empty()) +static void print_pool(const json& txs) { + if (txs.empty()) { - tools::msg_writer() << "Pool is empty" << std::endl; + tools::msg_writer() << "Pool is empty\n"; return; } - const time_t now = time(NULL); - tools::msg_writer() << "Transactions:"; - for (auto &tx_info : transactions) + const time_t now = time(nullptr); + tools::msg_writer() << txs.size() << " Transactions:\n"; + std::vector lines; + for (auto &tx : txs) { - auto w = tools::msg_writer(); - w << "id: " << tx_info.id_hash << "\n"; - if (include_json) w << tx_info.tx_json << "\n"; - w << "blob_size: " << tx_info.blob_size << "\n" - << "weight: " << tx_info.weight << "\n" - << "fee: " << cryptonote::print_money(tx_info.fee) << "\n" - /// NB(beldex): in v13 we have min_fee = per_out*outs + per_byte*bytes, only the total fee/byte matters for - /// the purpose of building a block template from the pool, so we still print the overall fee / byte here. - /// (we can't back out the individual per_out and per_byte that got used anyway). - << "fee/byte: " << cryptonote::print_money(tx_info.fee / (double)tx_info.weight) << "\n" - << "receive_time: " << tx_info.receive_time << " (" << get_human_time_ago(tx_info.receive_time, now) << ")\n" - << "relayed: " << (tx_info.relayed ? std::to_string(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")" : "no") << "\n" - << std::boolalpha - << "do_not_relay: " << tx_info.do_not_relay << "\n" - << "flash: " << tx_info.flash << "\n" - << "kept_by_block: " << tx_info.kept_by_block << "\n" - << "double_spend_seen: " << tx_info.double_spend_seen << "\n" - << std::noboolalpha - << "max_used_block_height: " << tx_info.max_used_block_height << "\n" - << "max_used_block_id: " << tx_info.max_used_block_id_hash << "\n" - << "last_failed_height: " << tx_info.last_failed_height << "\n" - << "last_failed_id: " << tx_info.last_failed_id_hash << "\n"; + std::vector status; + if (tx.value("blink", false)) status.push_back("blink"sv); + status.push_back(tx["relayed"].get() ? "relayed"sv : "not relayed"sv); + if (tx.value("do_not_relay", false)) status.push_back("do not relay"sv); + if (tx.value("double_spend_seen", false)) status.push_back("double spend"sv); + if (tx.value("kept_by_block", false)) status.push_back("from popped block"sv); + + lines.clear(); + lines.push_back(tx["tx_hash"].get_ref() + ":"s); + lines.push_back(fmt::format("size/weight: {}/{}", tx["size"].get(), tx["weight"].get())); + lines.push_back(fmt::format("fee: {} ({}/byte)", + cryptonote::print_money(tx["fee"].get()), cryptonote::print_money(tx["fee"].get() / tx["weight"].get()))); + lines.push_back(fmt::format("received: {} ({})", tx["received_timestamp"].get(), get_human_time_ago(tx["received_timestamp"].get(), now))); + lines.push_back("status: " + tools::join(", ", status)); + lines.push_back(fmt::format("top required block: {} ({})", tx["max_used_height"].get(), tx["max_used_block"])); + if (tx.count("last_failed_height")) + lines.push_back(fmt::format("last failed block: {} ({})", tx["last_failed_height"].get(), tx["last_failed_block"].get())); + if (auto extra = tx.find("extra"); extra != tx.end()) { + lines.push_back("transaction extra: "); + for (auto c : extra->dump(2)) { + if (c == '\n') + lines.back() += "\n "sv; + else + lines.back() += c; + } + } + tools::msg_writer() << tools::join("\n ", lines) << "\n"; } } -bool rpc_command_executor::print_transaction_pool_long() { - GET_TRANSACTION_POOL::response res{}; - - if (!invoke({}, res, "Failed to retrieve transaction pool details")) +bool rpc_command_executor::print_transaction_pool(bool long_format) { + json args{{"memory_pool", true}}; + if (long_format) args["tx_extra"] = true; + auto maybe_pool = try_running([this, &args] { return invoke(args); }, + "Failed to retrieve transaction pool details"); + if (!maybe_pool) return false; + auto& pool = *maybe_pool; + + print_pool(pool["txs"]); - print_pool(res.transactions, true); + if (long_format) { + // We used to have a warning here when we had transactions but no key_images; but that can + // happen on Oxen with 0-output tx state change transactions. - if (res.spent_key_images.empty()) - { - if (! res.transactions.empty()) - tools::msg_writer() << "WARNING: Inconsistent pool state - no spent key images"; - } - else - { - tools::msg_writer() << ""; // one newline - tools::msg_writer() << "Spent key images: "; - for (const auto& kinfo : res.spent_key_images) + if (!pool["mempool_key_images"].empty()) { - tools::msg_writer() << "key image: " << kinfo.id_hash; - if (kinfo.txs_hashes.size() == 1) - { - tools::msg_writer() << " tx: " << kinfo.txs_hashes[0]; - } - else if (kinfo.txs_hashes.size() == 0) + tools::msg_writer() << "\nSpent key images: "; + for (const auto& [key, tx_hashes] : pool["mempool_key_images"].items()) { - tools::msg_writer() << " WARNING: spent key image has no txs associated"; - } - else - { - tools::msg_writer() << " NOTE: key image for multiple txs: " << kinfo.txs_hashes.size(); - for (const std::string& tx_id : kinfo.txs_hashes) + tools::msg_writer() << "key image: " << key; + if (tx_hashes.size() == 1) + tools::msg_writer() << " tx: " << tx_hashes.front().get(); + else if (tx_hashes.empty()) + tools::msg_writer() << " WARNING: spent key image has no txs associated!"; + else { - tools::msg_writer() << " tx: " << tx_id; + tools::msg_writer() << fmt::format(" NOTE: key image for multiple transactions ({}):", tx_hashes.size()); + for (const auto& txid : tx_hashes) + tools::msg_writer() << " - " << txid.get(); } } - } - if (res.transactions.empty()) - { - tools::msg_writer() << "WARNING: Inconsistent pool state - no transactions"; + if (pool["txs"].empty()) + tools::msg_writer() << "WARNING: Inconsistent pool state - key images but no no transactions"; } } return true; } -bool rpc_command_executor::print_transaction_pool_short() { - GET_TRANSACTION_POOL::request req{}; - GET_TRANSACTION_POOL::response res{}; - - if (!invoke({}, res, "Failed to retrieve transaction pool details")) - return false; - - print_pool(res.transactions, false); - - return true; -} - bool rpc_command_executor::print_transaction_pool_stats() { auto full_reward_zone = try_running([this] { return invoke().at("block_size_limit").get() / 2; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 66dde31fd0d..480e95357ac 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -182,9 +182,7 @@ class rpc_command_executor final { bool is_key_image_spent(const crypto::key_image &ki); - bool print_transaction_pool_long(); - - bool print_transaction_pool_short(); + bool print_transaction_pool(bool long_format); bool print_transaction_pool_stats(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a782e652ecc..1c98affb6cc 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -51,6 +51,7 @@ #include "epee/string_tools.h" #include "core_rpc_server.h" #include "core_rpc_server_command_parser.h" +#include "bootstrap_daemon.h" #include "rpc_args.h" #include "core_rpc_server_error_codes.h" #include "common/command_line.h" @@ -596,8 +597,7 @@ namespace cryptonote::rpc { return res; } std::vector txs; - std::vector missed_txs; - m_core.get_transactions(blk.tx_hashes, txs, missed_txs); + m_core.get_transactions(blk.tx_hashes, txs); res.blocks.resize(res.blocks.size() + 1); res.blocks.back().block = block_to_blob(blk); for (auto& tx : txs) @@ -743,13 +743,13 @@ namespace cryptonote::rpc { // a single one we want just the value itself; this does that. Returns a reference to the // assigned value (whether as a top-level value or array element). template - json& set(const std::string& key, T&& value, bool binary = is_binary_parameter || is_binary_vector) { + json& set(const std::string& key, T&& value, bool binary = is_binary_parameter || is_binary_container) { auto* x = &entry[key]; if (!x->is_null() && !x->is_array()) x = &(entry[key] = json::array({std::move(*x)})); if (x->is_array()) x = &x->emplace_back(); - if constexpr (is_binary_parameter || is_binary_vector || std::is_convertible_v) { + if constexpr (is_binary_parameter || is_binary_container || std::is_convertible_v) { if (binary) return json_binary_proxy{*x, format} = std::forward(value); } @@ -896,23 +896,20 @@ namespace cryptonote::rpc { } struct tx_info { - crypto::hash id; // txid hash txpool_tx_meta_t meta; std::string tx_blob; // Blob containing the transaction data. bool flash; // True if this is a signed flash transaction - //std::optional extra; // Parsed tx_extra information (only if requested) - //std::optional stake_amount; // Will be set to the staked amount if the transaction is a staking transaction *and* stake amounts were requested. }; - static std::vector get_pool_txs_impl(cryptonote::core& core, std::function post_process) { + static std::unordered_map get_pool_txs_impl(cryptonote::core& core) { auto& bc = core.get_blockchain_storage(); auto& pool = core.get_pool(); - std::vector tx_infos; + std::unordered_map tx_infos; tx_infos.reserve(bc.get_txpool_tx_count()); bc.for_all_txpool_txes( - [&tx_infos, &pool, post_process=std::move(post_process)] + [&tx_infos, &pool] (const crypto::hash& txid, const txpool_tx_meta_t& meta, const cryptonote::blobdata* bd) { transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) @@ -921,22 +918,18 @@ namespace cryptonote::rpc { // continue return true; } - auto& txi = tx_infos.emplace_back(); - txi.id = txid; + auto& txi = tx_infos[txid]; txi.meta = meta; txi.tx_blob = *bd; tx.set_hash(txid); - //txi.tx_json = obj_to_json_str(tx); txi.flash = pool.has_flash(txid); - if (post_process) - post_process(tx, txi); return true; }, true); return tx_infos; } - auto pool_locks(cryptonote::core& core) { + static auto pool_locks(cryptonote::core& core) { auto& pool = core.get_pool(); std::unique_lock tx_lock{pool, std::defer_lock}; std::unique_lock bc_lock{core.get_blockchain_storage(), std::defer_lock}; @@ -945,17 +938,18 @@ namespace cryptonote::rpc { return std::make_tuple(std::move(tx_lock), std::move(bc_lock), std::move(flash_lock)); } - static std::pair, tx_memory_pool::key_images_container> get_pool_txs_kis( - cryptonote::core& core, std::function post_process = {}) { + static std::pair, tx_memory_pool::key_images_container> get_pool_txs_kis(cryptonote::core& core) { auto locks = pool_locks(core); - return {get_pool_txs_impl(core, std::move(post_process)), core.get_pool().get_spent_key_images(true)}; + return {get_pool_txs_impl(core), core.get_pool().get_spent_key_images(true)}; } - static std::vector get_pool_txs( + /* + static std::unordered_map get_pool_txs( cryptonote::core& core, std::function post_process = {}) { auto locks = pool_locks(core); return get_pool_txs_impl(core, std::move(post_process)); } + */ static tx_memory_pool::key_images_container get_pool_kis( cryptonote::core& core, std::function post_process = {}) { @@ -970,80 +964,84 @@ namespace cryptonote::rpc { if (use_bootstrap_daemon_if_necessary(req, res)) return res; */ - std::vector missed_txs; + std::unordered_set missed_txs; using split_tx = std::tuple; std::vector txs; - if (!m_core.get_split_transactions_blobs(get.request.tx_hashes, txs, missed_txs)) - { - get.response["status"] = STATUS_FAILED; - return; + if (!get.request.tx_hashes.empty()) { + if (!m_core.get_split_transactions_blobs(get.request.tx_hashes, txs, &missed_txs)) + { + get.response["status"] = STATUS_FAILED; + return; + } + LOG_PRINT_L2("Found " << txs.size() << "/" << get.request.tx_hashes.size() << " transactions on the blockchain"); } - LOG_PRINT_L2("Found " << txs.size() << "/" << get.request.tx_hashes.size() << " transactions on the blockchain"); // try the pool for any missing txes auto& pool = m_core.get_pool(); - size_t found_in_pool = 0; - std::unordered_map per_tx_pool_tx_info; - if (!missed_txs.empty()) + std::unordered_map found_in_pool; + if (!missed_txs.empty() || get.request.memory_pool) { try { - auto pool_tx_info = get_pool_txs(m_core); - // sort to match original request - std::vector sorted_txs; - unsigned txs_processed = 0; - for (const auto& h: get.request.tx_hashes) - { - auto missed_it = std::find(missed_txs.begin(), missed_txs.end(), h); - if (missed_it == missed_txs.end()) - { - if (txs.size() == txs_processed) - { - get.response["status"] = "Failed: internal error - txs is empty"; - return; - } - // core returns the ones it finds in the right order - if (std::get<0>(txs[txs_processed]) != h) - { - get.response["status"] = "Failed: tx hash mismatch"; - return; - } - sorted_txs.push_back(std::move(txs[txs_processed])); - ++txs_processed; - continue; + auto [pool_txs, pool_kis] = get_pool_txs_kis(m_core); + + auto split_mempool_tx = [](std::pair& info) { + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_from_blob(info.second.tx_blob, tx)) + throw std::runtime_error{"Unable to parse and validate tx from blob"}; + serialization::binary_string_archiver ba; + try { + tx.serialize_base(ba); + } catch (const std::exception& e) { + throw std::runtime_error{"Failed to serialize transaction base: "s + e.what()}; } + std::string pruned = ba.str(); + std::string pruned2{info.second.tx_blob, pruned.size()}; + return split_tx{info.first, std::move(pruned), get_transaction_prunable_hash(tx), std::move(pruned2)}; + }; - auto ptx_it = std::find_if(pool_tx_info.begin(), pool_tx_info.end(), - [&h](const auto& txi) { return h == txi.id; }); - if (ptx_it != pool_tx_info.end()) - { - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(ptx_it->tx_blob, tx)) - { - get.response["status"] = "Failed to parse and validate tx from blob"; - return; - } - serialization::binary_string_archiver ba; - try { - tx.serialize_base(ba); - } catch (const std::exception& e) { - get.response["status"] = "Failed to serialize transaction base: "s + e.what(); - return; + if (!get.request.tx_hashes.empty()) { + // sort to match original request + std::vector sorted_txs; + unsigned txs_processed = 0; + for (const auto& h: get.request.tx_hashes) { + if (auto missed_it = missed_txs.find(h); missed_it == missed_txs.end()) { + if (txs.size() == txs_processed) { + get.response["status"] = "Failed: internal error - txs is empty"; + return; + } + // core returns the ones it finds in the right order + if (std::get<0>(txs[txs_processed]) != h) { + get.response["status"] = "Failed: internal error - tx hash mismatch"; + return; + } + sorted_txs.push_back(std::move(txs[txs_processed])); + ++txs_processed; + } else if (auto ptx_it = pool_txs.find(h); ptx_it != pool_txs.end()) { + sorted_txs.push_back(split_mempool_tx(*ptx_it)); + missed_txs.erase(missed_it); + found_in_pool.emplace(h, std::move(ptx_it->second)); } - std::string pruned = ba.str(); - std::string pruned2{ptx_it->tx_blob, pruned.size()}; - sorted_txs.emplace_back(h, std::move(pruned), get_transaction_prunable_hash(tx), std::move(pruned2)); - missed_txs.erase(missed_it); - per_tx_pool_tx_info.emplace(h, *ptx_it); - ++found_in_pool; + } + txs = std::move(sorted_txs); + get.response_hex["missed_tx"] = missed_txs; // non-plural here intentional to not break existing clients + LOG_PRINT_L2("Found " << found_in_pool.size() << "/" << get.request.tx_hashes.size() << " transactions in the pool"); + } else if (get.request.memory_pool) { + txs.reserve(pool_txs.size()); + std::transform(pool_txs.begin(), pool_txs.end(), std::back_inserter(txs), split_mempool_tx); + found_in_pool = std::move(pool_txs); + + auto mki = get.response_hex["mempool_key_images"]; + for (auto& [ki, txids] : pool_kis) { + // The *key* is also binary (hex for json): + std::string key{get.is_bt() ? tools::view_guts(ki) : tools::type_to_hex(ki)}; + mki[key] = txids; } } - txs = std::move(sorted_txs); } catch (const std::exception& e) { - // Log error but continue MERROR(e.what()); + get.response["status"] = "Failed: "s + e.what(); + return; } - get.response_hex["missed_tx"] = missed_txs; // non-plural here intentional to not break existing clients - LOG_PRINT_L2("Found " << found_in_pool << "/" << get.request.tx_hashes.size() << " transactions in the pool"); } uint64_t immutable_height = m_core.get_blockchain_storage().get_immutable_height(); auto flash_lock = pool.flash_shared_lock(std::defer_lock); // Defer until/unless we actually need it @@ -1053,7 +1051,6 @@ namespace cryptonote::rpc { auto height = m_core.get_current_blockchain_height(); auto net = nettype(); auto hf_version = get_network_version(net, height); - cryptonote::blobdata tx_data; for (const auto& [tx_hash, unprunable_data, prunable_hash, prunable_data]: txs) { auto& e = txs_out.emplace_back(); @@ -1070,92 +1067,91 @@ namespace cryptonote::rpc { if (pruned || (prunable && (get.request.split || get.request.prune))) e_bin["prunable_hash"] = prunable_hash; - if (get.request.split || get.request.prune || pruned) + std::string tx_data = unprunable_data; + if (!get.request.prune) + tx_data += prunable_data; + + if (get.request.split || get.request.prune) { - if (get.request.decode_as_json) - { - tx_data = unprunable_data; - if (!get.request.prune) - tx_data += prunable_data; - } - else - { - e_bin["pruned"] = unprunable_data; - if (!get.request.prune && prunable && !pruned) - e_bin["prunable"] = prunable_data; - } + e_bin["pruned"] = unprunable_data; + if (get.request.split) + e_bin["prunable"] = prunable_data; } - else - { - // use non-splitted form, leaving pruned_as_hex and prunable_as_hex as empty - tx_data = unprunable_data; - tx_data += prunable_data; - if (!get.request.decode_as_json) + + if (get.request.data) { + if (pruned || get.request.prune) { + if (!e.count("pruned")) + e_bin["pruned"] = unprunable_data; + } else { e_bin["data"] = tx_data; + } } - cryptonote::transaction t; - if (get.request.decode_as_json || get.request.tx_extra || get.request.stake_info) + cryptonote::transaction tx; + if (get.request.prune || pruned) { - if (get.request.prune || pruned) + if (!cryptonote::parse_and_validate_tx_base_from_blob(tx_data, tx)) { - if (!cryptonote::parse_and_validate_tx_base_from_blob(tx_data, t)) - { - get.response["status"] = "Failed to parse and validate base tx data"; - return; - } - // I hate this because it goes deep into epee: - if (get.request.decode_as_json) - e["as_json"] = obj_to_json_str(pruned_transaction{t}); + get.response["status"] = "Failed to parse and validate base tx data"; + return; } - else + } + else + { + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx)) { - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) - { - get.response["status"] = "Failed to parse and validate tx data"; - return; - } - if (get.request.decode_as_json) - e["as_json"] = obj_to_json_str(t); + get.response["status"] = "Failed to parse and validate tx data"; + return; } - if (get.request.tx_extra) - load_tx_extra_data(e["extra"], t, nettype(), hf_version, get.is_bt()); } - auto ptx_it = per_tx_pool_tx_info.find(tx_hash); - bool in_pool = ptx_it != per_tx_pool_tx_info.end(); + if (get.request.tx_extra) + load_tx_extra_data(e["extra"], tx, nettype(), hf_version, get.is_bt()); + + auto ptx_it = found_in_pool.find(tx_hash); + bool in_pool = ptx_it != found_in_pool.end(); e["in_pool"] = in_pool; - bool might_be_flash = true; auto height = std::numeric_limits::max(); + + if (uint64_t fee, burned; get_tx_miner_fee(tx, fee, hf_version >= feature::FEE_BURNING, &burned)) { + e["fee"] = fee; + e["burned"] = burned; + } + if (in_pool) { - if (ptx_it->second.meta.double_spend_seen) - e["double_spend_seen"] = true; - e["relayed"] = ptx_it->second.meta.relayed; + const auto& meta = ptx_it->second.meta; + e["weight"] = meta.weight; + e["relayed"] = (bool) ptx_it->second.meta.relayed; e["received_timestamp"] = ptx_it->second.meta.receive_time; + e["flash"] = ptx_it->second.flash; + if (meta.double_spend_seen) e["double_spend_seen"] = true; + if (meta.do_not_relay) e["do_not_relay"] = true; + if (meta.last_relayed_time) e["last_relayed_time"] = meta.last_relayed_time; + if (meta.kept_by_block) e["kept_by_block"] = (bool) meta.kept_by_block; + if (meta.last_failed_id) e_bin["last_failed_block"] = meta.last_failed_id; + if (meta.last_failed_height) e["last_failed_height"] = meta.last_failed_height; + if (meta.max_used_block_id) e_bin["max_used_block"] = meta.max_used_block_id; + if (meta.max_used_block_height) e["max_used_height"] = meta.max_used_block_height; + } else { height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); e["block_height"] = height; e["block_timestamp"] = m_core.get_blockchain_storage().get_db().get_block_timestamp(height); - if (height <= immutable_height) - might_be_flash = false; + if (height > immutable_height) { + if (!flash_lock) flash_lock.lock(); + e["flash"] = pool.has_flash(tx_hash); + } } - if (get.request.stake_info) { - auto hf_version = get_network_version(nettype(), in_pool ? m_core.get_current_blockchain_height() : height); + { master_nodes::staking_components sc; - if (master_nodes::tx_get_staking_components_and_amounts(nettype(), hf_version, t, height, &sc) + if (master_nodes::tx_get_staking_components_and_amounts(nettype(), hf_version, tx, height, &sc) && sc.transferred > 0) e["stake_amount"] = sc.transferred; } - if (might_be_flash) - { - if (!flash_lock) flash_lock.lock(); - e["flash"] = pool.has_flash(tx_hash); - } - // output indices too if not in pool if (!in_pool) { @@ -1515,37 +1511,6 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_TRANSACTION_POOL::response core_rpc_server::invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context) - { - GET_TRANSACTION_POOL::response res{}; - - PERF_TIMER(on_get_transaction_pool); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - - std::function load_extra; - if (req.tx_extra || req.stake_info) - load_extra = [this, &req, net=nettype()](const transaction& tx, tx_info& txi) { - auto height = m_core.get_current_blockchain_height(); - auto hf_version = get_network_version(net, height); - if (req.tx_extra) - load_tx_extra_data(txi.extra.emplace(), tx, net, hf_version); - if (req.stake_info) { - - master_nodes::staking_components sc; - if (master_nodes::tx_get_staking_components_and_amounts(net, hf_version, tx, height, &sc) - && sc.transferred > 0) - txi.stake_amount = sc.transferred; - } - }; - - m_core.get_pool().get_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, load_extra, context.admin); - for (tx_info& txi : res.transactions) - txi.tx_blob = oxenc::to_hex(txi.tx_blob); - res.status = STATUS_OK; - return res; - } - //------------------------------------------------------------------------------------------------------------------------------ GET_TRANSACTION_POOL_HASHES_BIN::response core_rpc_server::invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context) { GET_TRANSACTION_POOL_HASHES_BIN::response res{}; @@ -3507,11 +3472,10 @@ namespace cryptonote::rpc { res.end_height = end_height; std::vector blobs; - std::vector missed_ids; for (const auto& block : blocks) { blobs.clear(); - if (!db.get_transactions_blobs(block.second.tx_hashes, blobs, missed_ids)) + if (!db.get_transactions_blobs(block.second.tx_hashes, blobs)) { MERROR("Could not query block at requested height: " << cryptonote::get_block_height(block.second)); continue; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 6df99ee0e24..0d4a5900a1d 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -37,7 +37,6 @@ #include #include -#include "bootstrap_daemon.h" #include "core_rpc_server_commands_defs.h" #include "cryptonote_core/cryptonote_core.h" #include "p2p/net_node.h" @@ -51,8 +50,12 @@ #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc" namespace boost::program_options { -class options_description; -class variables_map; + class options_description; + class variables_map; +} + +namespace cryptonote { + class bootstrap_daemon; } namespace cryptonote::rpc { @@ -238,7 +241,6 @@ namespace cryptonote::rpc { GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); - GET_TRANSACTION_POOL::response invoke(GET_TRANSACTION_POOL::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_LIMIT::response invoke(GET_LIMIT::request&& req, rpc_context context); SET_LIMIT::response invoke(SET_LIMIT::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index e6a2effb5b7..0ded50bbf4d 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -37,6 +37,11 @@ namespace cryptonote::rpc { template constexpr bool is_required_wrapper> = true; + template + constexpr bool is_std_optional = false; + template + constexpr bool is_std_optional> = true; + using oxenc::bt_dict_consumer; using json_range = std::pair; @@ -166,6 +171,8 @@ namespace cryptonote::rpc { else if (skip_until(in, name)) { if constexpr (is_required_wrapper) return load_value(in, val.value); + else if constexpr (is_std_optional) + return load_value(in, val.emplace()); else return load_value(in, val); } @@ -310,13 +317,23 @@ namespace cryptonote::rpc { if (auto it = json_in->find("txs_hashes"); it != json_in->end()) (*json_in)["tx_hashes"] = std::move(*it); + std::optional data; get_values(in, - "decode_as_json", get.request.decode_as_json, + "data", data, + "memory_pool", get.request.memory_pool, "prune", get.request.prune, "split", get.request.split, "stake_info", get.request.stake_info, "tx_extra", get.request.tx_extra, "tx_hashes", get.request.tx_hashes); + + if (data) + get.request.data = *data; + else + get.request.data = !(get.request.prune || get.request.split); + + if (get.request.memory_pool && !get.request.tx_hashes.empty()) + throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 2f561d58094..73e37dd0a62 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -566,46 +566,46 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(tx_info) // FIXME: delete -KV_SERIALIZE_MAP_CODE_BEGIN(old_tx_info) - KV_SERIALIZE(id_hash) - KV_SERIALIZE(tx_json) - KV_SERIALIZE(blob_size) - KV_SERIALIZE_OPT(weight, (uint64_t)0) - KV_SERIALIZE(fee) - KV_SERIALIZE(max_used_block_id_hash) - KV_SERIALIZE(max_used_block_height) - KV_SERIALIZE(kept_by_block) - KV_SERIALIZE(last_failed_height) - KV_SERIALIZE(last_failed_id_hash) - KV_SERIALIZE(receive_time) - KV_SERIALIZE(relayed) - KV_SERIALIZE(last_relayed_time) - KV_SERIALIZE(do_not_relay) - KV_SERIALIZE(double_spend_seen) - KV_SERIALIZE(tx_blob) - KV_SERIALIZE(extra) - // KV_SERIALIZE(stake_amount) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(old_tx_info) +// KV_SERIALIZE(id_hash) +// KV_SERIALIZE(tx_json) +// KV_SERIALIZE(blob_size) +// KV_SERIALIZE_OPT(weight, (uint64_t)0) +// KV_SERIALIZE(fee) +// KV_SERIALIZE(max_used_block_id_hash) +// KV_SERIALIZE(max_used_block_height) +// KV_SERIALIZE(kept_by_block) +// KV_SERIALIZE(last_failed_height) +// KV_SERIALIZE(last_failed_id_hash) +// KV_SERIALIZE(receive_time) +// KV_SERIALIZE(relayed) +// KV_SERIALIZE(last_relayed_time) +// KV_SERIALIZE(do_not_relay) +// KV_SERIALIZE(double_spend_seen) +// KV_SERIALIZE(tx_blob) +// KV_SERIALIZE(extra) +// // KV_SERIALIZE(stake_amount) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(spent_key_image_info) - KV_SERIALIZE(id_hash) - KV_SERIALIZE(txs_hashes) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(spent_key_image_info) +// KV_SERIALIZE(id_hash) +// KV_SERIALIZE(txs_hashes) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL::request) - KV_SERIALIZE(tx_extra) - KV_SERIALIZE(stake_info) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL::request) +// KV_SERIALIZE(tx_extra) +// KV_SERIALIZE(stake_info) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL::response) - KV_SERIALIZE(status) - KV_SERIALIZE(transactions) - KV_SERIALIZE(spent_key_images) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(transactions) +// KV_SERIALIZE(spent_key_images) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index f103f25b39f..0f673199941 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -343,8 +343,8 @@ namespace cryptonote::rpc { /// - /p status -- Generic RPC error code. "OK" is the success value. /// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to /// true, otherwise will be omitted. - /// - \p missed_tx -- list of transaction hashes that were not found. If all were found then this - /// field is omitted. + /// - \p missed_tx -- set of transaction hashes that were not found. If all were found then this + /// field is omitted. There is no particular ordering of hashes in this list. /// - \p txs -- list of transaction details; each element is a dict containing: /// - \p tx_hash -- Transaction hash. /// - \p size -- Size of the transaction, in bytes. Note that if the transaction has been pruned @@ -353,6 +353,9 @@ namespace cryptonote::rpc { /// and omitted if mined into a block. /// - \p flash -- True if this is an approved, flash transaction; this information is generally /// only available for approved in-pool transactions and txes in very recent blocks. + /// - \p fee -- the transaction fee (in atomic BELDEX) incurred in this transaction (not including + /// any burned amount). + /// - \p burned -- the amount of BELDEX (in atomic units) burned by this transaction. /// - \p block_height -- Block height including the transaction. Omitted for tx pool /// transactions. /// - \p block_timestamp -- Unix time at which the block has been added to the blockchain. @@ -365,6 +368,25 @@ namespace cryptonote::rpc { /// transactions. /// - \p received_timestamp -- Timestamp transaction was received in the pool. Omitted for /// mined blocks. + /// - \p max_used_block -- the hash of the highest block referenced by this transaction; only + /// for mempool transactions. + /// - \p max_used_height -- the height of the highest block referenced by this transaction; only + /// for mempool transactions. + /// - \p last_failed_block -- the hash of the last block where this transaction was attempted to + /// be mined (but failed). + /// - \p max_used_height -- the height of the last block where this transaction failed to be + /// acceptable for a block. + /// - \p weight -- the transaction "weight" which is the size of the transaction with padding + /// removed. Only included for mempool transactions (for mined transactions the size and + /// weight at the same and so only `size` is included). + /// - \p kept_by_block will be present and true if this is a mempool transaction that was added + /// to the mempool after being popped off a block (e.g. because of a blockchain + /// reorganization). + /// - \p last_relayed_time indicates the last time this block was relayed to the network; only + /// for mempool transactions. + /// - \p do_not_relay -- set to true for mempool blocks that are marked "do not relay" + /// - \p double_spend_seen -- set to true if one or more outputs in this mempool transaction + /// have already been spent (and thus the tx cannot currently be added to the blockchain). /// - \p data -- Full, unpruned transaction data. For a json request this is hex-encoded; for a /// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`, /// `split`, or `prune` is requested; or if the transaction has been pruned in the database. @@ -376,12 +398,11 @@ namespace cryptonote::rpc { /// - \p prunable_hash -- The hash of the prunable part of the transaction. Will be provided if /// either: the tx has been pruned; or the tx is prunable and either of `prune` or `split` are /// specified. - /// FIXME: drop this crap: - /// - \p as_json -- Transaction information parsed into json. Requires decode_as_json in request. - /// - \p extra -- Parsed "extra" transaction information; omitted unless specifically requested. - /// This is a dict containing one or more of the following keys. + /// - \p extra -- Parsed "extra" transaction information; omitted unless specifically requested + /// (via the `tx_extra` request parameter). This is a dict containing one or more of the + /// following keys. /// - \p pubkey -- The tx extra public key - /// - \p burn_amount -- The amount of OXEN that this transaction burns + /// - \p burn_amount -- The amount of BELDEX that this transaction burns, if any. /// - \p extra_nonce -- Optional extra nonce value (in hex); will be empty if nonce is /// recognized as a payment id /// - \p payment_id -- The payment ID, if present. This is either a 16 hex character (8-byte) @@ -458,29 +479,34 @@ namespace cryptonote::rpc { /// be a primary wallet address, wallet subaddress, or a plain public key. /// - \p backup_owner -- an optional backup owner who also has permission to edit the /// record. - /// - \p stake_amount -- If `stake_info` is explicitly requested then this field will be set to - /// the calculated transaction stake amount (only applicable if the transaction is a master - /// node registration or stake). + /// - \p stake_amount -- Set to the calculated transaction stake amount (only applicable if the + /// transaction is a master node registration or stake). + /// - \p mempool_key_images -- dict of spent key images of mempool transactions. Only included + /// when `memory_pool` is set to true. Each key is the key image (in hex, for json requests) + /// and each value is a list of transaction hashes that spend that key image (typically just + /// one, but in the case of conflicting transactions there can be multiple). struct GET_TRANSACTIONS : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("get_transactions", "gettransactions"); } struct request_parameters { /// List of transaction hashes to look up. (Will also be accepted as json input key - /// "txs_hashes" for backwards compatibility). + /// "txs_hashes" for backwards compatibility). Exclusive of `memory_pool`. std::vector tx_hashes; - /// If set to true, the returned transaction information will be decoded. - bool decode_as_json = false; + /// If true then return all transactions and spent key images currently in the memory pool. + /// This field is exclusive of `tx_hashes`. + bool memory_pool = false; /// If set to true then parse and return tx-extra information bool tx_extra = false; + /// Controls whether the `data` (or `pruned`, if pruned) field containing raw tx data is + /// included: if explicitly specified then the raw data will be included if true. Otherwise + /// the raw data is included only when neither of `split` nor `prune` are set to true. + bool data = true; /// If set to true then always split transactions into non-prunable and prunable parts in the /// response. bool split = false; - /// Like `split`, but also omits the prunable part (or details, for decode_as_json) of - /// transactions from the response. + /// Like `split`, but also omits the prunable part of transactions from the response details. bool prune = false; - /// If true then calculate staking amount for staking/registration transactions - bool stake_info = false; } request; }; @@ -1202,31 +1228,6 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - struct old_tx_info - { - std::string id_hash; // The transaction ID hash. - std::string tx_json; // JSON structure of all information in the transaction - uint64_t blob_size; // The size of the full transaction blob. - uint64_t weight; // The weight of the transaction. - uint64_t fee; // The amount of the mining fee included in the transaction, in atomic units. - std::string max_used_block_id_hash; // Tells the hash of the most recent block with an output used in this transaction. - uint64_t max_used_block_height; // Tells the height of the most recent block with an output used in this transaction. - bool kept_by_block; // States if the tx was included in a block at least once (`true`) or not (`false`). - uint64_t last_failed_height; // If the transaction validation has previously failed, this tells at what height that occured. - std::string last_failed_id_hash; // Like the previous, this tells the previous transaction ID hash. - uint64_t receive_time; // The Unix time that the transaction was first seen on the network by the node. - bool relayed; // States if this transaction has been relayed - uint64_t last_relayed_time; // Last unix time at which the transaction has been relayed. - bool do_not_relay; // States if this transaction should not be relayed. - bool double_spend_seen; // States if this transaction has been seen as double spend. - std::string tx_blob; // Hexadecimal blob represnting the transaction. - bool flash; // True if this is a signed flash transaction - // std::optional extra; // Parsed tx_extra information (only if requested) - std::optional stake_amount; // Will be set to the staked amount if the transaction is a staking transaction *and* stake amounts were requested. - - KV_MAP_SERIALIZABLE - }; BELDEX_RPC_DOC_INTROSPECT struct spent_key_image_info @@ -1237,32 +1238,6 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; - BELDEX_RPC_DOC_INTROSPECT - // Show information about valid transactions seen by the node but not yet mined into a block, - // as well as spent key image information for the txpool in the node's memory. - struct GET_TRANSACTION_POOL : PUBLIC, LEGACY - { - static constexpr auto names() { return NAMES("get_transaction_pool"); } - - struct request - { - bool tx_extra; // Parse tx-extra information and adds it to the `extra` field. - bool stake_info; // Calculate and include staking contribution amount for registration/staking transactions - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector transactions; // List of transactions in the mempool are not in a block on the main chain at the moment: - std::vector spent_key_images; // List of spent output key images: - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - BELDEX_RPC_DOC_INTROSPECT // Get hashes from transaction pool. Binary request. struct GET_TRANSACTION_POOL_HASHES_BIN : PUBLIC, BINARY @@ -2869,7 +2844,6 @@ namespace cryptonote::rpc { GET_PUBLIC_NODES, SET_LOG_LEVEL, SET_LOG_CATEGORIES, - GET_TRANSACTION_POOL, GET_BLOCK_HEADERS_RANGE, SET_BOOTSTRAP_DAEMON, GET_LIMIT, diff --git a/src/rpc/rpc_binary.h b/src/rpc/rpc_binary.h index b6651e588bd..75da319cf19 100644 --- a/src/rpc/rpc_binary.h +++ b/src/rpc/rpc_binary.h @@ -4,6 +4,7 @@ #include "crypto/crypto.h" #include #include +#include using namespace std::literals; @@ -21,15 +22,17 @@ namespace cryptonote::rpc { template <> inline constexpr bool is_binary_parameter = true; template - inline constexpr bool is_binary_vector = false; + inline constexpr bool is_binary_container = false; template - inline constexpr bool is_binary_vector> = is_binary_parameter; + inline constexpr bool is_binary_container> = is_binary_parameter; + template + inline constexpr bool is_binary_container> = is_binary_parameter; // De-referencing wrappers around the above: template inline constexpr bool is_binary_parameter = is_binary_parameter; template inline constexpr bool is_binary_parameter = is_binary_parameter; - template inline constexpr bool is_binary_vector = is_binary_vector; - template inline constexpr bool is_binary_vector = is_binary_vector; + template inline constexpr bool is_binary_container = is_binary_container; + template inline constexpr bool is_binary_container = is_binary_container; void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); @@ -85,8 +88,8 @@ namespace cryptonote::rpc { } /// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning /// each one into a new array of binary values. - template - nlohmann::json& operator=(const std::vector& vals) { + template , int> = 0> + nlohmann::json& operator=(const T& vals) { auto a = nlohmann::json::array(); for (auto& val : vals) json_binary_proxy{a.emplace_back(), format} = val; From a6e5342b731ea13e6c05a64a5a02c3989015fd12 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Sun, 20 Apr 2025 21:21:29 +0530 Subject: [PATCH 053/182] Wire up start_mining command Also remove some old autodetect handling which doesn't exist anymore. --- src/daemon/command_parser_executor.cpp | 23 ++++------------------- src/daemon/rpc_command_executor.cpp | 24 +++++++++--------------- src/daemon/rpc_command_executor.h | 2 +- 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 2d462b8c8ab..3146226e318 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -484,30 +484,15 @@ bool command_parser_executor::start_mining(const std::vector& args) tools::fail_msg_writer() << "subaddress for mining reward is not yet supported!"; return true; } - if(nettype != cryptonote::network_type::MAINNET) - std::cout << "Mining to a " << (nettype == cryptonote::network_type::TESTNET ? "testnet" : "devnet") << " address, make sure this is intentional!"; std::string_view threads_val = tools::find_prefixed_value(args.begin() + 1, args.end(), "threads="sv); std::string_view num_blocks_val = tools::find_prefixed_value(args.begin() + 1, args.end(), "num_blocks="sv); - int threads_count = 1; - uint32_t num_blocks = 0; - if (threads_val.size()) + unsigned int threads_count = 1, num_blocks = 0; + if (threads_val.size() && !tools::parse_int(threads_val, threads_count)) { - if (threads_val == "auto"sv || threads_val == "autodetect"sv) - { - threads_count = 0; - } - else - { - if (!tools::parse_int(threads_val, threads_count)) - { - tools::fail_msg_writer() << "Failed to parse threads value" << threads_val; - return false; - } - - threads_count = 0 < threads_count ? threads_count : 1; - } + tools::fail_msg_writer() << "Failed to parse threads value" << threads_val; + return false; } if (num_blocks_val.size()) tools::parse_int(num_blocks_val, num_blocks); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 3937292f557..638f8c8435c 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1091,23 +1091,17 @@ bool rpc_command_executor::print_transaction_pool_stats() { return true; } -bool rpc_command_executor::start_mining(const cryptonote::account_public_address& address, uint64_t num_threads, uint32_t num_blocks, cryptonote::network_type nettype) { - START_MINING::request req{}; - START_MINING::response res{}; - req.num_blocks = num_blocks; - req.miner_address = cryptonote::get_account_address_as_str(nettype, false, address); - req.threads_count = num_threads; - - if (!invoke(std::move(req), res, "Unable to start mining")) +bool rpc_command_executor::start_mining(const cryptonote::account_public_address& address, int num_threads, int num_blocks, cryptonote::network_type nettype) { + json args{ + {"num_blocks", num_blocks}, + {"threads_count", num_threads}, + {"miner_address", cryptonote::get_account_address_as_str(nettype, false, address)}}; + if (!try_running([this, &args] { return invoke(args); }, "Unable to start mining")) return false; - std::stringstream stream; - stream << "Mining started"; - if (num_threads) stream << " with " << num_threads << " thread(s)."; - else stream << ", auto detecting the number of threads to use."; - - if (num_blocks) stream << " Mining for " << num_blocks << " blocks before stopping or until manually stopped."; - tools::success_msg_writer() << stream.str(); + tools::success_msg_writer() + << fmt::format("Mining started with {} thread(s).", std::max(num_threads, 1)) + << (num_blocks ? fmt::format(" Will stop after {} blocks", num_blocks) : ""); return true; } diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 480e95357ac..f1cb67f175b 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -186,7 +186,7 @@ class rpc_command_executor final { bool print_transaction_pool_stats(); - bool start_mining(const cryptonote::account_public_address& address, uint64_t num_threads, uint32_t num_blocks, cryptonote::network_type nettype); + bool start_mining(const cryptonote::account_public_address& address, int num_threads, int num_blocks, cryptonote::network_type nettype); bool stop_mining(); From e2a6995eca42e0b31edaebc2c38a33202a84d659 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 10:54:48 +0530 Subject: [PATCH 054/182] removed unused save_limit_to_file() --- contrib/epee/include/epee/net/connection_basic.hpp | 1 - contrib/epee/src/connection_basic.cpp | 5 ----- contrib/epee/src/network_throttle-detail.cpp | 3 ++- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/contrib/epee/include/epee/net/connection_basic.hpp b/contrib/epee/include/epee/net/connection_basic.hpp index 1f11ed20c16..14f91f124ca 100755 --- a/contrib/epee/include/epee/net/connection_basic.hpp +++ b/contrib/epee/include/epee/net/connection_basic.hpp @@ -135,7 +135,6 @@ class connection_basic { // not-templated base class for rapid developmet of som // handlers and sleep void sleep_before_packet(size_t packet_size, int phase, int q_len); // execute a sleep ; phase is not really used now(?) - static void save_limit_to_file(int limit); ///< for dr-monero static double get_sleep_time(size_t cb); }; diff --git a/contrib/epee/src/connection_basic.cpp b/contrib/epee/src/connection_basic.cpp index 060abcd5b44..1b9da48887a 100755 --- a/contrib/epee/src/connection_basic.cpp +++ b/contrib/epee/src/connection_basic.cpp @@ -176,7 +176,6 @@ void connection_basic::set_rate_up_limit(uint64_t limit) { std::lock_guard lock{network_throttle_manager::m_lock_get_global_throttle_out}; network_throttle_manager::get_global_throttle_out().set_target_speed(limit); } - save_limit_to_file(limit); } void connection_basic::set_rate_down_limit(uint64_t limit) { @@ -189,7 +188,6 @@ void connection_basic::set_rate_down_limit(uint64_t limit) { std::lock_guard lock{network_throttle_manager::m_lock_get_global_throttle_inreq}; network_throttle_manager::get_global_throttle_inreq().set_target_speed(limit); } - save_limit_to_file(limit); } uint64_t connection_basic::get_rate_up_limit() { @@ -209,9 +207,6 @@ uint64_t connection_basic::get_rate_down_limit() { } return limit; } - -void connection_basic::save_limit_to_file(int limit) { -} void connection_basic::set_tos_flag(int tos) { connection_basic_pimpl::m_default_tos = tos; diff --git a/contrib/epee/src/network_throttle-detail.cpp b/contrib/epee/src/network_throttle-detail.cpp index 8f095971bb4..c4e54ae9e6a 100755 --- a/contrib/epee/src/network_throttle-detail.cpp +++ b/contrib/epee/src/network_throttle-detail.cpp @@ -133,7 +133,7 @@ void network_throttle::set_name(const std::string &name) void network_throttle::set_target_speed( network_speed_kbps target ) { - m_target_speed = target * 1024; + m_target_speed = target * 1024; MINFO("Setting LIMIT: " << target << " kbps"); } @@ -344,6 +344,7 @@ double network_throttle::get_current_speed() const { std::pair network_throttle::get_stats() const { return {m_total_packets, m_total_bytes}; +} } // namespace From 7b4058320157f236411e52843fee2971ef1dd527 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 11:36:34 +0530 Subject: [PATCH 055/182] support flag m_p2p->for_connection fix --- src/cryptonote_protocol/cryptonote_protocol_handler.inl | 8 ++++---- src/rpc/core_rpc_server_command_parser.cpp | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index cf5c68b80c9..8c6cba0e383 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -1567,7 +1567,7 @@ namespace cryptonote { if (parsed_txs[i].tvc.m_verifivation_failed) { - if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f) -> bool{ + if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id) -> bool{ cryptonote::transaction tx; parse_and_validate_tx_from_blob(block_entry.txs[i], tx); // must succeed if we got here LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_BLOCKS, tx_id = " @@ -1612,7 +1612,7 @@ namespace cryptonote if (bvc.m_verifivation_failed || bvc.m_marked_as_orphaned) { - if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ + if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool{ char const *ERR_MSG = bvc.m_verifivation_failed ? "Block verification failed, dropping connection" @@ -1822,7 +1822,7 @@ skip: // if we're at max out peers, and not enough are syncing if (n_synced + n_syncing >= m_max_out_peers && n_syncing < p2p::DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid()) { - if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ + if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id)->bool{ MINFO(ctx << "dropping synced peer, " << n_syncing << " syncing, " << n_synced << " synced"); drop_connection(ctx, false, false); return true; @@ -1901,7 +1901,7 @@ skip: if (standby && dt >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY && dl_speed > 0) { bool download = false; - if (m_p2p->for_connection(connection_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ + if (m_p2p->for_connection(connection_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id)->bool{ const auto last_activity = std::min(now - ctx.m_last_recv, dt); const bool stalled = last_activity > LAST_ACTIVITY_STALL_THRESHOLD; if (stalled) diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 0ded50bbf4d..83069f75313 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -262,8 +262,6 @@ namespace cryptonote::rpc { } void parse_request(MINING_STATUS& mining_status, rpc_input in) { } - void parse_request(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_input in) { - } void parse_request(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_input in) { } void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in) { From 32de093b3696f163d3e972250bf675f8188b8abd Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 12:02:43 +0530 Subject: [PATCH 056/182] RPC: GET_LIMIT, SET_LIMIT Updates to new RPC interface. Removes distinct `limit`, `limit_up`, `limit_down` command and makes them all go through `limit` by allowing limit to take the down and up parameters. limit_up 123 limit_down 456 becomes limit 0 123 limit 456 0 or to set both at once: limit 456 123 Also improves documentation, and allows "default" to be specified for either limit to return the limit to its default. (-1 is also accepted for this, but is a pain because on the command line it produces an invalid option error unless you also prefix the command with `--` to stop further arg parsing). Also makes the command actually print an error (instead of nothing at all) when given invalid arguments. Whoa such innovation! --- src/daemon/command_parser_executor.cpp | 63 ++++++------------- src/daemon/command_parser_executor.h | 4 -- src/daemon/command_server.cpp | 20 ++---- src/daemon/rpc_command_executor.cpp | 71 ++++++++++------------ src/daemon/rpc_command_executor.h | 2 +- src/rpc/core_rpc_server.cpp | 40 +++++------- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 9 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 30 ++++----- src/rpc/core_rpc_server_commands_defs.h | 55 +++++++---------- 11 files changed, 123 insertions(+), 176 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 3146226e318..2f16bc41595 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -31,6 +31,7 @@ #include "common/command_line.h" #include "common/hex.h" +#include "common/scoped_message_writer.h" #include "version.h" #include "daemon/command_parser_executor.h" #include "rpc/core_rpc_server_commands_defs.h" @@ -521,56 +522,32 @@ bool command_parser_executor::stop_daemon(const std::vector& args) bool command_parser_executor::set_limit(const std::vector& args) { - if(args.size()>1) return false; - if(args.size()==0) { + if (args.size() == 0) return m_executor.get_limit(); - } - int64_t limit; - try { - limit = std::stoll(args[0]); - } - catch(const std::exception& ex) { - std::cout << "failed to parse argument" << std::endl; - return false; - } - - return m_executor.set_limit(limit, limit); -} -bool command_parser_executor::set_limit_up(const std::vector& args) -{ - if(args.size()>1) return false; - if(args.size()==0) { - return m_executor.get_limit(true, false); - } - int64_t limit; - try { - limit = std::stoll(args[0]); + if (args.size() > 2) { + tools::fail_msg_writer() << "Too many arguments: expected 0-2 values"; + return false; } - catch(const std::exception& ex) { - std::cout << "failed to parse argument" << std::endl; - return false; + int64_t limit_down; + if (args[0] == "default") // Accept "default" as a string because getting -1 through the cli arg parsing is a nuissance + limit_down = -1; + else if (!tools::parse_int(args[0], limit_down)) { + tools::fail_msg_writer() << "Failed to parse '" << args[0] << "' as a limit"; + return false; } - return m_executor.set_limit(0, limit); -} - -bool command_parser_executor::set_limit_down(const std::vector& args) -{ - if(args.size()>1) return false; - if(args.size()==0) { - return m_executor.get_limit(false, true); - } - int64_t limit; - try { - limit = std::stoll(args[0]); - } - catch(const std::exception& ex) { - std::cout << "failed to parse argument" << std::endl; - return false; + int64_t limit_up; + if (args.size() == 1) + limit_up = limit_down; + else if (args[1] == "default") + limit_up = -1; + else if (!tools::parse_int(args[1], limit_up)) { + tools::fail_msg_writer() << "Failed to parse '" << args[1] << "' as a limit"; + return false; } - return m_executor.set_limit(limit, 0); + return m_executor.set_limit(limit_down, limit_up); } bool command_parser_executor::out_peers(const std::vector& args) diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 953815a6bcb..ed22ba2bdb8 100755 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -99,10 +99,6 @@ class command_parser_executor final bool set_limit(const std::vector& args); - bool set_limit_up(const std::vector& args); - - bool set_limit_down(const std::vector& args); - bool out_peers(const std::vector& args); bool in_peers(const std::vector& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index ff3f2b83fb2..f8aa175a650 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -208,20 +208,12 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) m_command_lookup.set_handler( "limit" , [this](const auto &x) { return m_parser.set_limit(x); } - , "limit []" - , "Get or set the download and upload limit." - ); - m_command_lookup.set_handler( - "limit_up" - , [this](const auto &x) { return m_parser.set_limit_up(x); } - , "limit_up []" - , "Get or set the upload limit." - ); - m_command_lookup.set_handler( - "limit_down" - , [this](const auto &x) { return m_parser.set_limit_down(x); } - , "limit_down []" - , "Get or set the download limit." + , "limit [ [({}, net_stats_res, "Unable to retrieve net statistics") || - !invoke({}, limit_res, "Unable to retrieve bandwidth limits")) + auto maybe_stats = try_running([this] { return invoke(); }, "Failed to retrieve net statistics"); + auto maybe_limit = try_running([this] { return invoke(); }, "Failed to retrieve bandwidth limits"); + if (!maybe_stats || !maybe_limit) return false; - - uint64_t seconds = (uint64_t)time(NULL) - net_stats_res.start_time; - uint64_t average = seconds > 0 ? net_stats_res.total_bytes_in / seconds : 0; - uint64_t limit = limit_res.limit_down * 1024; // convert to bytes, as limits are always kB/s - double percent = (double)average / (double)limit * 100.0; - tools::success_msg_writer() << fmt::format("Received {} bytes ({}) in {} packets, average {}/s = {:.2f}% of the limit of {}/s" - , net_stats_res.total_bytes_in - , tools::get_human_readable_bytes(net_stats_res.total_bytes_in) - , net_stats_res.total_packets_in - , tools::get_human_readable_bytes(average) - , percent - , tools::get_human_readable_bytes(limit)); - - average = seconds > 0 ? net_stats_res.total_bytes_out / seconds : 0; - limit = limit_res.limit_up * 1024; - percent = (double)average / (double)limit * 100.0; - tools::success_msg_writer() << fmt::format("Sent {} bytes ({}) in {} packets, average {}/s = {:.2f}% of the limit of {}/s" - , net_stats_res.total_bytes_out - , tools::get_human_readable_bytes(net_stats_res.total_bytes_out) - , net_stats_res.total_packets_out - , tools::get_human_readable_bytes(average) - , percent - , tools::get_human_readable_bytes(limit)); + auto& stats = *maybe_stats; + auto& limit = *maybe_limit; + auto uptime = time(nullptr) - stats["start_time"].get(); + + for (bool in : {true, false}) { + auto bytes = stats[in ? "total_bytes_in" : "total_bytes_out"].get(); + double average = uptime > 0 ? bytes / (double) uptime : 0.0; + uint64_t lim = limit[in ? "limit_down" : "limit_up"].get() * 1024; // convert to bytes, as limits are always kB/s + tools::success_msg_writer() << fmt::format("{} {} in {} packets, average {}/s = {:.2f}% of the limit of {}/s", + in ? "Received" : "Sent", + tools::get_human_readable_bytes(bytes), + stats[in ? "total_packets_in" : "total_packets_out"].get(), + tools::get_human_readable_bytes(average), + average / lim * 100.0, + tools::get_human_readable_bytes(lim)); + } return true; } @@ -1116,26 +1107,28 @@ bool rpc_command_executor::stop_daemon() bool rpc_command_executor::get_limit(bool up, bool down) { - GET_LIMIT::response res{}; - - if (!invoke({}, res, "Failed to retrieve current bandwidth limits")) + auto maybe_limit = try_running([this] { return invoke(); }, "Failed to retrieve current traffic limits"); + if (!maybe_limit) return false; + auto& limit = *maybe_limit; - if (down) - tools::msg_writer() << "limit-down is " << res.limit_down << " kB/s"; - if (up) - tools::msg_writer() << "limit-up is " << res.limit_up << " kB/s"; + tools::msg_writer() << fmt::format("Current limits are {} kiB/s down, {} kiB/s up", + limit["limit_down"].get(), limit["limit_up"].get()); return true; } bool rpc_command_executor::set_limit(int64_t limit_down, int64_t limit_up) { - SET_LIMIT::response res{}; - if (!invoke({limit_down, limit_up}, res, "Failed to set bandwidth limits")) + json args{ + {"limit_down", limit_down}, + {"limit_up", limit_up}}; + auto maybe_limit = try_running([this, &args] { return invoke(args); }, "Failed to set traffic limits"); + if (!maybe_limit) return false; + auto& limit = *maybe_limit; - tools::msg_writer() << "Set limit-down to " << res.limit_down << " kB/s"; - tools::msg_writer() << "Set limit-up to " << res.limit_up << " kB/s"; + tools::success_msg_writer() << fmt::format("New limits are {} kiB/s down, {} kiB/s up", + limit["limit_down"].get(), limit["limit_up"].get()); return true; } diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index f1cb67f175b..431a4572ab6 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -194,7 +194,7 @@ class rpc_command_executor final { bool stop_daemon(); - bool get_limit(bool up = true, bool down = true); + bool get_limit(); bool set_limit(int64_t limit_down, int64_t limit_up); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 1c98affb6cc..a47e3d29d70 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2479,42 +2479,34 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_LIMIT::response core_rpc_server::invoke(GET_LIMIT::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_LIMIT& limit, rpc_context context) { - GET_LIMIT::response res{}; - PERF_TIMER(on_get_limit); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit(); - res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit(); - res.status = STATUS_OK; - return res; + limit.response = { + {"limit_down", epee::net_utils::connection_basic::get_rate_down_limit()}, + {"limit_up", epee::net_utils::connection_basic::get_rate_up_limit()}, + {"status", STATUS_OK}}; } //------------------------------------------------------------------------------------------------------------------------------ - SET_LIMIT::response core_rpc_server::invoke(SET_LIMIT::request&& req, rpc_context context) + void core_rpc_server::invoke(SET_LIMIT& limit, rpc_context context) { - SET_LIMIT::response res{}; - PERF_TIMER(on_set_limit); + // -1 = reset to default // 0 = do not modify - - if (req.limit_down < -1 || req.limit_up < -1) - throw rpc_error{ERROR_WRONG_PARAM, "Invalid limit_down or limit_up value: value must be >= -1"}; - - if (req.limit_down != 0) + if (limit.request.limit_down != 0) epee::net_utils::connection_basic::set_rate_down_limit( - req.limit_down == -1 ? p2p::DEFAULT_LIMIT_RATE_DOWN : req.limit_down); - if (req.limit_up != 0) + limit.request.limit_down == -1 ? nodetool::default_limit_down : limit.request.limit_down); + + if (limit.request.limit_up != 0) epee::net_utils::connection_basic::set_rate_up_limit( - req.limit_up == -1 ? p2p::DEFAULT_LIMIT_RATE_UP : req.limit_up); + limit.request.limit_up == -1 ? nodetool::default_limit_up : limit.request.limit_up); - res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit(); - res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit(); - res.status = STATUS_OK; - return res; + limit.response = { + {"limit_down", epee::net_utils::connection_basic::get_rate_down_limit()}, + {"limit_up", epee::net_utils::connection_basic::get_rate_up_limit()}, + {"status", STATUS_OK}}; } //------------------------------------------------------------------------------------------------------------------------------ OUT_PEERS::response core_rpc_server::invoke(OUT_PEERS::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 0d4a5900a1d..6cdecb637f6 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -222,6 +222,8 @@ namespace cryptonote::rpc { void invoke(SYNC_INFO& sync, rpc_context context); void invoke(GET_MASTER_NODE_STATUS& sns, rpc_context context); void invoke(GET_MASTER_NODES& sns, rpc_context context); + void invoke(GET_LIMIT& limit, rpc_context context); + void invoke(SET_LIMIT& limit, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -242,8 +244,6 @@ namespace cryptonote::rpc { SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - GET_LIMIT::response invoke(GET_LIMIT::request&& req, rpc_context context); - SET_LIMIT::response invoke(SET_LIMIT::request&& req, rpc_context context); OUT_PEERS::response invoke(OUT_PEERS::request&& req, rpc_context context); IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 83069f75313..b3e2a784a9f 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -334,4 +334,13 @@ namespace cryptonote::rpc { throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } + void parse_request(SET_LIMIT& limit, rpc_input in) { + get_values(in, + "limit_down", limit.request.limit_down, + "limit_up", limit.request.limit_up); + if (limit.request.limit_down < -1) + throw std::domain_error{"limit_down must be >= -1"}; + if (limit.request.limit_down < -1) + throw std::domain_error{"limit_up must be >= -1"}; + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index fdae36ba9a6..7a23d6195c4 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -25,4 +25,5 @@ namespace cryptonote::rpc { void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); void parse_request(GET_TRANSACTIONS& hfinfo, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); + void parse_request(SET_LIMIT& limit, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 73e37dd0a62..f97c6ad7a10 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -692,25 +692,25 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SET_BOOTSTRAP_DAEMON::request) KV_SERIALIZE(password) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_LIMIT::response) - KV_SERIALIZE(status) - KV_SERIALIZE(limit_up) - KV_SERIALIZE(limit_down) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_LIMIT::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(limit_up) +// KV_SERIALIZE(limit_down) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_LIMIT::request) - KV_SERIALIZE(limit_down) - KV_SERIALIZE(limit_up) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_LIMIT::request) +// KV_SERIALIZE(limit_down) +// KV_SERIALIZE(limit_up) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_LIMIT::response) - KV_SERIALIZE(status) - KV_SERIALIZE(limit_up) - KV_SERIALIZE(limit_down) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_LIMIT::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(limit_up) +// KV_SERIALIZE(limit_down) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(OUT_PEERS::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 0f673199941..594f9f92bf0 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1460,47 +1460,34 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("stop_daemon"); } }; - BELDEX_RPC_DOC_INTROSPECT - // Get daemon bandwidth limits. - struct GET_LIMIT : LEGACY + /// Get daemon p2p bandwidth limits. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p limit_up Upload limit in kiB/s + /// - \p limit_down Download limit in kiB/s + struct GET_LIMIT : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_limit"); } - - struct request : EMPTY {}; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - uint64_t limit_up; // Upload limit in kBytes per second. - uint64_t limit_down; // Download limit in kBytes per second. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT - // Set daemon bandwidth limits. + /// Set daemon p2p bandwidth limits. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p limit_up The new (or existing, if unchanged) upload limit in kiB/s + /// - \p limit_down The new (or existing, if unchanged) download limit in kiB/s struct SET_LIMIT : LEGACY { static constexpr auto names() { return NAMES("set_limit"); } - struct request - { - int64_t limit_down; // Download limit in kBytes per second (-1 reset to default, 0 don't change the current limit) - int64_t limit_up; // Upload limit in kBytes per second (-1 reset to default, 0 don't change the current limit) - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - int64_t limit_up; // Upload limit in kBytes per second. - int64_t limit_down; // Download limit in kBytes per second. - - KV_MAP_SERIALIZABLE - }; + struct request_parameters { + int64_t limit_down = 0; ///< Download limit in kBytes per second. -1 means reset to default; 0 (or omitted) means don't change the current limit + int64_t limit_up = 0; ///< Upload limit in kBytes per second. -1 means reset to default; 0 (or omitted) means don't change the current limit + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -2803,6 +2790,8 @@ namespace cryptonote::rpc { GET_INFO, BNS_RESOLVE, GET_OUTPUTS, + GET_LIMIT, + SET_LIMIT, HARD_FORK_INFO, START_MINING, STOP_MINING, @@ -2846,8 +2835,6 @@ namespace cryptonote::rpc { SET_LOG_CATEGORIES, GET_BLOCK_HEADERS_RANGE, SET_BOOTSTRAP_DAEMON, - GET_LIMIT, - SET_LIMIT, OUT_PEERS, IN_PEERS, GETBANS, From f2718ceed7af5a6cc9d1795b8b5c94b17bc57370 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 12:23:42 +0530 Subject: [PATCH 057/182] Remove useless GET_TRANSACTION_POOL_BACKLOG endpoint --- src/cryptonote_core/tx_pool.cpp | 12 ----------- src/cryptonote_core/tx_pool.h | 10 --------- src/rpc/core_rpc_server.cpp | 13 ------------ src/rpc/core_rpc_server.h | 1 - src/rpc/core_rpc_server_command_parser.cpp | 2 -- src/rpc/core_rpc_server_command_parser.h | 1 - src/rpc/core_rpc_server_commands_defs.cpp | 7 ------- src/rpc/core_rpc_server_commands_defs.h | 24 ---------------------- 8 files changed, 70 deletions(-) diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index e7a6b80fd2d..28d4170a12f 100755 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1204,18 +1204,6 @@ namespace cryptonote LOG_PRINT_L2("get_transaction_hashes end"); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_backlog(std::vector& backlog, bool include_unrelayed_txes) const - { - auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); - - const uint64_t now = time(NULL); - backlog.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes)); - m_blockchain.for_all_txpool_txes([&backlog, now](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ - backlog.push_back({meta.weight, meta.fee, meta.receive_time - now}); - return true; - }, false, include_unrelayed_txes); - } - //------------------------------------------------------------------ tx_memory_pool::tx_stats tx_memory_pool::get_transaction_stats(bool include_unrelayed_txes) const { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 87ee2eb9588..7bf9e3812f6 100755 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -43,7 +43,6 @@ #include "cryptonote_basic/verification_context.h" #include "blockchain_db/blockchain_db.h" #include "crypto/hash.h" -#include "rpc/core_rpc_server_commands_defs.h" #include "tx_flash.h" #include "beldex_economy.h" @@ -409,15 +408,6 @@ namespace cryptonote */ void get_transaction_hashes(std::vector& txs, bool include_unrelayed_txes = true, bool include_only_flashed = false) const; - /** - * @brief get (weight, fee, receive time) for all transaction in the pool - * - * @param txs return-by-reference that data - * @param include_unrelayed_txes include unrelayed txes in the result - * - */ - void get_transaction_backlog(std::vector& backlog, bool include_unrelayed_txes = true) const; - /// Return type of get_transaction_stats() struct tx_stats { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a47e3d29d70..4da9142d1c5 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2616,19 +2616,6 @@ namespace cryptonote::rpc { sync.response["status"] = STATUS_OK; } - //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context) - { - PERF_TIMER(on_get_txpool_backlog); - //TODO handle bootstrap daemon - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; - - std::vector backlog; - m_core.get_pool().get_transaction_backlog(backlog); - get_transaction_pool_backlog.response["backlog"] = json::parse(backlog.begin(), backlog.end()); - get_transaction_pool_backlog.response["status"] = STATUS_OK; - } namespace { output_distribution_data process_distribution( diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 6cdecb637f6..c970bd0e306 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -215,7 +215,6 @@ namespace cryptonote::rpc { void invoke(GETBLOCKCOUNT& getblockcount, rpc_context context); void invoke(MINING_STATUS& mining_status, rpc_context context); void invoke(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_context context); - void invoke(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_context context); void invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context); void invoke(GET_TRANSACTIONS& req, rpc_context context); void invoke(GET_CONNECTIONS& get_connections, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index b3e2a784a9f..faaef00991b 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -262,8 +262,6 @@ namespace cryptonote::rpc { } void parse_request(MINING_STATUS& mining_status, rpc_input in) { } - void parse_request(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_input in) { - } void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in) { } void parse_request(GETBLOCKCOUNT& getblockcount, rpc_input in) { diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 7a23d6195c4..a9f463808e2 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -16,7 +16,6 @@ namespace cryptonote::rpc { void parse_request(STOP_MINING& stop_mining, rpc_input in); void parse_request(MINING_STATUS& mining_status, rpc_input in); void parse_request(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_input in); - void parse_request(GET_TRANSACTION_POOL_BACKLOG& get_transaction_pool_backlog, rpc_input in); void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in); void parse_request(GETBLOCKCOUNT& getblockcount, rpc_input in); void parse_request(STOP_DAEMON& stop_daemon, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index f97c6ad7a10..b29ddb5a10e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -629,13 +629,6 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_BACKLOG::response) -// KV_SERIALIZE(status) -// KV_SERIALIZE_CONTAINER_POD_AS_BLOB(backlog) -// KV_SERIALIZE(untrusted) -// KV_SERIALIZE_MAP_CODE_END() - - // KV_SERIALIZE_MAP_CODE_BEGIN(txpool_histo) // KV_SERIALIZE(txs) // KV_SERIALIZE(bytes) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 594f9f92bf0..525ba416dee 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1279,29 +1279,6 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } }; - BELDEX_RPC_DOC_INTROSPECT - struct tx_backlog_entry - { - uint64_t weight; // - uint64_t fee; // Fee in Beldex measured in atomic units. - uint64_t time_in_pool; - }; - - //----------------------------------------------- - /// Get all transaction pool backlog. - /// - /// Inputs: none - /// - /// Output values available from a public RPC endpoint: - /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p backlog Array of structures tx_backlog_entry (in binary form): - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - struct GET_TRANSACTION_POOL_BACKLOG : PUBLIC - { - static constexpr auto names() { return NAMES("get_txpool_backlog"); } - }; - BELDEX_RPC_DOC_INTROSPECT struct txpool_histo { @@ -2801,7 +2778,6 @@ namespace cryptonote::rpc { GETBLOCKCOUNT, MINING_STATUS, GET_TRANSACTION_POOL_HASHES, - GET_TRANSACTION_POOL_BACKLOG, GET_TRANSACTION_POOL_STATS, GET_TRANSACTIONS, GET_MASTER_NODES, From a9cb439f9491f09ef715697a43043d7ea9eb8bef Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 13:08:00 +0530 Subject: [PATCH 058/182] Split core_rpc_server_commands_defs into 2 Keep the current one for the json/bt RPC endpoints, and move all the binary endpoints into a new .h/.cpp. --- src/cryptonote_core/blockchain.h | 1 + src/rpc/CMakeLists.txt | 1 + src/rpc/bootstrap_daemon.h | 9 +- src/rpc/core_rpc_server.cpp | 120 ++++---- src/rpc/core_rpc_server.h | 3 +- src/rpc/core_rpc_server_binary_commands.cpp | 126 +++++++++ src/rpc/core_rpc_server_binary_commands.h | 298 ++++++++++++++++++++ src/rpc/core_rpc_server_commands_defs.cpp | 122 -------- src/rpc/core_rpc_server_commands_defs.h | 266 +---------------- 9 files changed, 498 insertions(+), 448 deletions(-) create mode 100644 src/rpc/core_rpc_server_binary_commands.cpp create mode 100644 src/rpc/core_rpc_server_binary_commands.h diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 07618d15c41..59b16b3728c 100755 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -57,6 +57,7 @@ #include "common/util.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/core_rpc_server_binary_commands.h" #include "cryptonote_basic/difficulty.h" #include "cryptonote_tx_utils.h" #include "cryptonote_basic/verification_context.h" diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 98b2c783892..fbec5c31bb9 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -29,6 +29,7 @@ add_library(rpc_commands core_rpc_server_commands_defs.cpp + core_rpc_server_binary_commands.cpp core_rpc_server_command_parser.cpp rpc_binary.cpp ) diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 5bac73c5c39..9924e66b53f 100755 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -5,7 +5,7 @@ #include "rpc/http_client.h" #include "rpc/core_rpc_server_commands_defs.h" - +#include "rpc/core_rpc_server_binary_commands.h" namespace cryptonote { @@ -28,12 +28,7 @@ namespace cryptonote return false; try { - if constexpr (std::is_base_of_v) - // TODO: post-8.x hard fork we can remove this one and let everything go through the - // non-binary json_rpc version instead (because all legacy json commands are callable via - // json_rpc as of daemon 8.x). - res = m_http_client.json(RPC::names().front(), req); - else if constexpr (std::is_base_of_v) + if constexpr (std::is_base_of_v) res = m_http_client.binary(RPC::names().front(), req); else res = m_http_client.json_rpc(RPC::names().front(), req); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 4da9142d1c5..2d5f30bf6ca 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -31,7 +31,6 @@ #include #include -#include #include #include #include @@ -41,6 +40,7 @@ #include #include "epee/net/network_throttle.hpp" #include "common/string_util.h" +#include "bootstrap_daemon.h" #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/tx_extra.h" @@ -50,10 +50,10 @@ #include "beldex_economy.h" #include "epee/string_tools.h" #include "core_rpc_server.h" +#include "core_rpc_server_binary_commands.h" #include "core_rpc_server_command_parser.h" -#include "bootstrap_daemon.h" -#include "rpc_args.h" #include "core_rpc_server_error_codes.h" +#include "rpc_args.h" #include "common/command_line.h" #include "common/beldex.h" #include "common/sha256sum.h" @@ -108,75 +108,89 @@ namespace cryptonote::rpc { throw std::domain_error{"internal error: encountered some unhandled/invalid type in json-to-bt translation"}; } - template , int> = 0> + template void register_rpc_command(std::unordered_map>& regs) { + static_assert(std::is_base_of_v && !std::is_base_of_v); auto cmd = std::make_shared(); cmd->is_public = std::is_base_of_v; - cmd->is_binary = std::is_base_of_v; cmd->is_legacy = std::is_base_of_v; - if constexpr (!std::is_base_of_v) { - static_assert(!FIXME_has_nested_response_v); - cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { - RPC rpc{}; - try { - if (auto body = request.body_view()) { - if (body->front() == 'd') { // Looks like a bt dict - rpc.set_bt(); - parse_request(rpc, oxenc::bt_dict_consumer{*body}); - } - else - parse_request(rpc, json::parse(*body)); - } else if (auto* j = std::get_if(&request.body)) { - parse_request(rpc, std::move(*j)); - } else { - assert(std::holds_alternative(request.body)); - parse_request(rpc, std::monostate{}); + + // Temporary: remove once RPC conversion is complete + static_assert(!FIXME_has_nested_response_v); + + cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { + RPC rpc; + try { + if (auto body = request.body_view()) { + if (body->front() == 'd') { // Looks like a bt dict + rpc.set_bt(); + parse_request(rpc, oxenmq::bt_dict_consumer{*body}); } - } catch (const std::exception& e) { - throw parse_error{"Failed to parse request parameters: "s + e.what()}; + else + parse_request(rpc, json::parse(*body)); + } else if (auto* j = std::get_if(&request.body)) { + parse_request(rpc, std::move(*j)); + } else { + assert(std::holds_alternative(request.body)); + parse_request(rpc, std::monostate{}); } + } catch (const std::exception& e) { + throw parse_error{"Failed to parse request parameters: "s + e.what()}; + } - server.invoke(rpc, std::move(request.context)); + server.invoke(rpc, std::move(request.context)); - if (rpc.response.is_null()) - rpc.response = json::object(); + if (rpc.response.is_null()) + rpc.response = json::object(); - if (rpc.is_bt()) - return json_to_bt(std::move(rpc.response)); - else - return std::move(rpc.response); - }; - } else { - // Legacy binary request; these still use epee serialization, and should be considered - // deprecated (tentatively to be removed in Oxen 11). - cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { - typename RPC::request req{}; - std::string_view data; - if (auto body = request.body_view()) - data = *body; - else - throw std::runtime_error{"Internal error: can't load binary a RPC command with non-string body"}; - if (!epee::serialization::load_t_from_binary(req, data)) - throw parse_error{"Failed to parse binary data parameters"}; + if (rpc.is_bt()) + return json_to_bt(std::move(rpc.response)); + else + return std::move(rpc.response); + }; - auto res = server.invoke(std::move(req), std::move(request.context)); + for (const auto& name : RPC::names()) + regs.emplace(name, cmd); + } - std::string response; - epee::serialization::store_t_to_binary(res, response); - return response; - }; - } + template + void register_binary_rpc_command(std::unordered_map>& regs) + { + static_assert(std::is_base_of_v && !std::is_base_of_v); + auto cmd = std::make_shared(); + cmd->is_public = std::is_base_of_v; + cmd->is_binary = true; + + // Legacy binary request; these still use epee serialization, and should be considered + // deprecated (tentatively to be removed in Oxen 11). + cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { + typename RPC::request req{}; + std::string_view data; + if (auto body = request.body_view()) + data = *body; + else + throw std::runtime_error{"Internal error: can't load binary a RPC command with non-string body"}; + if (!epee::serialization::load_t_from_binary(req, data)) + throw parse_error{"Failed to parse binary data parameters"}; + + auto res = server.invoke(std::move(req), std::move(request.context)); + + std::string response; + epee::serialization::store_t_to_binary(res, response); + return response; + }; for (const auto& name : RPC::names()) regs.emplace(name, cmd); } - template - std::unordered_map> register_rpc_commands(tools::type_list) { + template + std::unordered_map> register_rpc_commands(tools::type_list, tools::type_list) { std::unordered_map> regs; (register_rpc_command(regs), ...); + (register_binary_rpc_command(regs), ...); return regs; } @@ -186,7 +200,7 @@ namespace cryptonote::rpc { } - const std::unordered_map> rpc_commands = register_rpc_commands(rpc::core_rpc_types{}); + const std::unordered_map> rpc_commands = register_rpc_commands(rpc::core_rpc_types{}, rpc::core_rpc_binary_types{}); const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { "bootstrap-daemon-address" diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index c970bd0e306..424d0d08fa6 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -37,8 +37,9 @@ #include #include -#include "core_rpc_server_commands_defs.h" #include "cryptonote_core/cryptonote_core.h" +#include "core_rpc_server_commands_defs.h" +#include "core_rpc_server_binary_commands.h" #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" diff --git a/src/rpc/core_rpc_server_binary_commands.cpp b/src/rpc/core_rpc_server_binary_commands.cpp new file mode 100644 index 00000000000..55a058e01b4 --- /dev/null +++ b/src/rpc/core_rpc_server_binary_commands.cpp @@ -0,0 +1,126 @@ +#include "core_rpc_server_binary_commands.h" + +namespace cryptonote::rpc { + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::request) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) + KV_SERIALIZE(start_height) + KV_SERIALIZE(prune) + KV_SERIALIZE_OPT(no_miner_tx, false) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::tx_output_indices) + KV_SERIALIZE(indices) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::block_output_indices) + KV_SERIALIZE(indices) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::response) + KV_SERIALIZE(blocks) + KV_SERIALIZE(start_height) + KV_SERIALIZE(current_height) + KV_SERIALIZE(status) + KV_SERIALIZE(output_indices) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT_BIN::request) + KV_SERIALIZE(heights) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT_BIN::response) + KV_SERIALIZE(blocks) + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALT_BLOCKS_HASHES_BIN::response) + KV_SERIALIZE(blks_hashes) + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::request) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) + KV_SERIALIZE(start_height) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::response) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) + KV_SERIALIZE(start_height) + KV_SERIALIZE(current_height) + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request) + KV_SERIALIZE_VAL_POD_AS_BLOB(txid) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response) + KV_SERIALIZE(o_indexes) + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(get_outputs_out) + KV_SERIALIZE(amount) + KV_SERIALIZE(index) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::request) + KV_SERIALIZE(outputs) + KV_SERIALIZE_OPT(get_txid, true) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::outkey) + KV_SERIALIZE_VAL_POD_AS_BLOB(key) + KV_SERIALIZE_VAL_POD_AS_BLOB(mask) + KV_SERIALIZE(unlocked) + KV_SERIALIZE(height) + KV_SERIALIZE_VAL_POD_AS_BLOB(txid) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::response) + KV_SERIALIZE(outs) + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::request) + KV_SERIALIZE_OPT(flashed_txs_only, false) + KV_SERIALIZE_OPT(long_poll, false) + KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(tx_pool_checksum, crypto::hash{}) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::response) + KV_SERIALIZE(status) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(tx_hashes) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_BLACKLIST_BIN::response) + KV_SERIALIZE(blacklist) + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() + +} diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h new file mode 100644 index 00000000000..97cc103d18a --- /dev/null +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -0,0 +1,298 @@ +// Copyright (c) 2018-2021, The Loki Project +// Copyright (c) 2014-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "core_rpc_server_commands_defs.h" + +namespace cryptonote::rpc { + + /// Specifies that the RPC call is legacy, deprecated Monero custom binary input/ouput. If not + /// given then the command is JSON/bt-encoded values. For HTTP RPC this also means the command is + /// *not* available via the HTTP JSON RPC. + struct BINARY : virtual RPC_COMMAND {}; + + /// Get all blocks info. Binary request. + /// + /// Inputs: + /// block_ids -- descending list of block IDs used to detect reorganizations and network status: + /// the first 10 are the 10 most recent blocks, after which height decreases by a power of 2. + struct GET_BLOCKS_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_blocks.bin", "getblocks.bin"); } + + static constexpr size_t MAX_COUNT = 1000; + + struct request { + std::list block_ids; // Descending list of block IDs used to detect reorganizations and network: the first 10 blocks id are sequential, then height drops by a power of 2 (2, 4, 8, 16, etc.) down to height 1, and then finally the genesis block id. + uint64_t start_height; // The height of the first block to fetch. + bool prune; // Prunes the blockchain, dropping off 7/8ths of the blocks. + bool no_miner_tx; // If specified and true, don't include miner transactions in transaction results. + + KV_MAP_SERIALIZABLE + }; + + struct tx_output_indices + { + std::vector indices; // Array of unsigned int. + + KV_MAP_SERIALIZABLE + }; + + struct block_output_indices + { + std::vector indices; // Array of TX output indices: + + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::vector blocks; // Array of block complete entries + uint64_t start_height; // The starting block's height. + uint64_t current_height; // The current block height. + std::string status; // General RPC error code. "OK" means everything looks good. + std::vector output_indices; // Array of indices. + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + OXEN_RPC_DOC_INTROSPECT + // Get blocks by height. Binary request. + struct GET_BLOCKS_BY_HEIGHT_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_blocks_by_height.bin", "getblocks_by_height.bin"); } + + struct request + { + std::vector heights; // List of block heights + + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::vector blocks; // Array of block complete entries + std::string status; // General RPC error code. "OK" means everything looks good. + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + + OXEN_RPC_DOC_INTROSPECT + // Get the known blocks hashes which are not on the main chain. + struct GET_ALT_BLOCKS_HASHES_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_alt_blocks_hashes.bin"); } + + struct request : EMPTY {}; + struct response + { + std::vector blks_hashes; // List of alternative blocks hashes to main chain. + std::string status; // General RPC error code. "OK" means everything looks good. + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + OXEN_RPC_DOC_INTROSPECT + // Get hashes. Binary request. + struct GET_HASHES_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_hashes.bin", "gethashes.bin"); } + + struct request + { + std::list block_ids; // First 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ + uint64_t start_height; // The starting block's height. + + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::vector m_block_ids; // Binary array of hashes, See block_ids above. + uint64_t start_height; // The starting block's height. + uint64_t current_height; // The current block height. + std::string status; // General RPC error code. "OK" means everything looks good. + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + OXEN_RPC_DOC_INTROSPECT + // Get global outputs of transactions. Binary request. + struct GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_o_indexes.bin"); } + + struct request + { + crypto::hash txid; // Binary txid. + + KV_MAP_SERIALIZABLE + }; + + + struct response + { + std::vector o_indexes; // List of output indexes + std::string status; // General RPC error code. "OK" means everything looks good. + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + OXEN_RPC_DOC_INTROSPECT + struct get_outputs_out + { + uint64_t amount; // Amount of Loki in TXID. + uint64_t index; + + KV_MAP_SERIALIZABLE + }; + + OXEN_RPC_DOC_INTROSPECT + // Get outputs. Binary request. + struct GET_OUTPUTS_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_outs.bin"); } + + /// Maximum outputs that may be requested in a single request (unless admin) + static constexpr size_t MAX_COUNT = 5000; + + struct request + { + std::vector outputs; // Array of structure `get_outputs_out`. + bool get_txid; // TXID + + KV_MAP_SERIALIZABLE + }; + + struct outkey + { + crypto::public_key key; // The public key of the output. + rct::key mask; + bool unlocked; // States if output is locked (`false`) or not (`true`). + uint64_t height; // Block height of the output. + crypto::hash txid; // Transaction id. + + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::vector outs; // List of outkey information. + std::string status; // General RPC error code. "OK" means everything looks good. + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + OXEN_RPC_DOC_INTROSPECT + // Get hashes from transaction pool. Binary request. + struct GET_TRANSACTION_POOL_HASHES_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_transaction_pool_hashes.bin"); } + + static constexpr std::chrono::seconds long_poll_timeout{15}; + + struct request + { + bool flashed_txs_only; // Optional: If true only transactions that were sent via flash and approved are queried. + bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using LMQ RPC. + crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using LMQ RPC. + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::string status; // General RPC error code. "OK" means everything looks good. + std::vector tx_hashes; // List of transaction hashes, + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; + }; + + OXEN_RPC_DOC_INTROSPECT + // Exactly like GET_OUTPUT_DISTRIBUTION, but does a binary RPC transfer instead of JSON + struct GET_OUTPUT_DISTRIBUTION_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_output_distribution.bin"); } + + struct request : GET_OUTPUT_DISTRIBUTION::request {}; + using response = GET_OUTPUT_DISTRIBUTION::response; + }; + + OXEN_RPC_DOC_INTROSPECT + // Get information on output blacklist. + struct GET_OUTPUT_BLACKLIST_BIN : PUBLIC, BINARY + { + static constexpr auto names() { return NAMES("get_output_blacklist.bin"); } + struct request : EMPTY {}; + + struct response + { + std::vector blacklist; // (Developer): Array of indexes from the global output list, corresponding to blacklisted key images. + std::string status; // Generic RPC error code. "OK" is the success value. + bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. + + KV_MAP_SERIALIZABLE + }; + }; + + /// List of all supported rpc command structs to allow compile-time enumeration of all supported + /// RPC types. Every type added above that has an RPC endpoint needs to be added here, and needs + /// a core_rpc_server::invoke() overload that takes a ::request and returns a + /// ::response. The ::request has to be unique (for overload resolution); + /// ::response does not. + using core_rpc_binary_types = tools::type_list< + GET_ALT_BLOCKS_HASHES_BIN, + GET_BLOCKS_BIN, + GET_BLOCKS_BY_HEIGHT_BIN, + GET_HASHES_BIN, + GET_OUTPUTS_BIN, + GET_OUTPUT_BLACKLIST_BIN, + GET_OUTPUT_DISTRIBUTION_BIN, + GET_TRANSACTION_POOL_HASHES_BIN, + GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN + >; + +} // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index b29ddb5a10e..06529871a2b 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -29,67 +29,6 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::request) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) - KV_SERIALIZE(start_height) - KV_SERIALIZE(prune) - KV_SERIALIZE_OPT(no_miner_tx, false) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::tx_output_indices) - KV_SERIALIZE(indices) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::block_output_indices) - KV_SERIALIZE(indices) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::response) - KV_SERIALIZE(blocks) - KV_SERIALIZE(start_height) - KV_SERIALIZE(current_height) - KV_SERIALIZE(status) - KV_SERIALIZE(output_indices) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT_BIN::request) - KV_SERIALIZE(heights) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BY_HEIGHT_BIN::response) - KV_SERIALIZE(blocks) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALT_BLOCKS_HASHES_BIN::response) - KV_SERIALIZE(blks_hashes) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::request) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) - KV_SERIALIZE(start_height) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_HASHES_BIN::response) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) - KV_SERIALIZE(start_height) - KV_SERIALIZE(current_height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - // KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::request) // KV_SERIALIZE(txs_hashes) // KV_SERIALIZE(decode_as_json) @@ -199,46 +138,6 @@ KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request) - KV_SERIALIZE_VAL_POD_AS_BLOB(txid) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response) - KV_SERIALIZE(o_indexes) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(get_outputs_out) - KV_SERIALIZE(amount) - KV_SERIALIZE(index) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::request) - KV_SERIALIZE(outputs) - KV_SERIALIZE_OPT(get_txid, true) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::outkey) - KV_SERIALIZE_VAL_POD_AS_BLOB(key) - KV_SERIALIZE_VAL_POD_AS_BLOB(mask) - KV_SERIALIZE(unlocked) - KV_SERIALIZE(height) - KV_SERIALIZE_VAL_POD_AS_BLOB(txid) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS_BIN::response) - KV_SERIALIZE(outs) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - // KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::request) // KV_SERIALIZE(outputs) // KV_SERIALIZE(get_txid) @@ -608,20 +507,6 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::request) - KV_SERIALIZE_OPT(flashed_txs_only, false) - KV_SERIALIZE_OPT(long_poll, false) - KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(tx_pool_checksum, crypto::hash{}) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES_BIN::response) - KV_SERIALIZE(status) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(tx_hashes) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - // KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTION_POOL_HASHES::response) // KV_SERIALIZE(status) // KV_SERIALIZE(tx_hashes) @@ -1265,13 +1150,6 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_BLACKLIST_BIN::response) - KV_SERIALIZE(blacklist) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() - - KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::request) KV_SERIALIZE_OPT(start_height, HEIGHT_SENTINEL_VALUE) KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 525ba416dee..3c9b677f03f 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -176,11 +176,6 @@ namespace cryptonote::rpc { /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). struct PUBLIC : virtual RPC_COMMAND {}; - /// Specifies that the RPC call is legacy, deprecated Monero custom binary input/ouput. If not - /// given then the command is JSON/bt-encoded values. For HTTP RPC this also means the command is - /// *not* available via the HTTP JSON RPC. - struct BINARY : virtual RPC_COMMAND {}; - /// Specifies that the RPC call takes no input arguments. (A dictionary of parameters may still /// be passed, but will be ignored). struct NO_ARGS : virtual RPC_COMMAND {}; @@ -221,121 +216,6 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_height", "getheight"); } }; - /// Get all blocks info. Deprecated, Monero custom binary request. See the (FIXME) RPC endpoint - /// instead. - /// - /// Inputs: - /// block_ids -- descending list of block IDs used to detect reorganizations and network status: - /// the first 10 are the 10 most recent blocks, after which height decreases by a power of 2. - struct GET_BLOCKS_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_blocks.bin", "getblocks.bin"); } - - static constexpr size_t MAX_COUNT = 1000; - - struct request { - std::list block_ids; // Descending list of block IDs used to detect reorganizations and network: the first 10 blocks id are sequential, then height drops by a power of 2 (2, 4, 8, 16, etc.) down to height 1, and then finally the genesis block id. - uint64_t start_height; // The height of the first block to fetch. - bool prune; // Prunes the blockchain, dropping off 7/8ths of the blocks. - bool no_miner_tx; // If specified and true, don't include miner transactions in transaction results. - - KV_MAP_SERIALIZABLE - }; - - struct tx_output_indices - { - std::vector indices; // Array of unsigned int. - - KV_MAP_SERIALIZABLE - }; - - struct block_output_indices - { - std::vector indices; // Array of TX output indices: - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::vector blocks; // Array of block complete entries - uint64_t start_height; // The starting block's height. - uint64_t current_height; // The current block height. - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector output_indices; // Array of indices. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - - BELDEX_RPC_DOC_INTROSPECT - // Get blocks by height. Binary request. - struct GET_BLOCKS_BY_HEIGHT_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_blocks_by_height.bin", "getblocks_by_height.bin"); } - - struct request - { - std::vector heights; // List of block heights - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::vector blocks; // Array of block complete entries - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - - - BELDEX_RPC_DOC_INTROSPECT - // Get the known blocks hashes which are not on the main chain. - struct GET_ALT_BLOCKS_HASHES_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_alt_blocks_hashes.bin"); } - - struct request : EMPTY {}; - struct response - { - std::vector blks_hashes; // List of alternative blocks hashes to main chain. - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - - BELDEX_RPC_DOC_INTROSPECT - // Get hashes. Binary request. - struct GET_HASHES_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_hashes.bin", "gethashes.bin"); } - - struct request - { - std::list block_ids; // First 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ - uint64_t start_height; // The starting block's height. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::vector m_block_ids; // Binary array of hashes, See block_ids above. - uint64_t start_height; // The starting block's height. - uint64_t current_height; // The current block height. - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - /// Look up one or more transactions by hash. /// /// Outputs: @@ -542,78 +422,6 @@ namespace cryptonote::rpc { }; }; - - BELDEX_RPC_DOC_INTROSPECT - // Get global outputs of transactions. Binary request. - struct GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_o_indexes.bin"); } - - struct request - { - crypto::hash txid; // Binary txid. - - KV_MAP_SERIALIZABLE - }; - - - struct response - { - std::vector o_indexes; // List of output indexes - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - - BELDEX_RPC_DOC_INTROSPECT - struct get_outputs_out - { - uint64_t amount; // Amount of Beldex in TXID. - uint64_t index; - - KV_MAP_SERIALIZABLE - }; - - BELDEX_RPC_DOC_INTROSPECT - // Get outputs. Binary request. - struct GET_OUTPUTS_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_outs.bin"); } - - /// Maximum outputs that may be requested in a single request (unless admin) - static constexpr size_t MAX_COUNT = 5000; - - struct request - { - std::vector outputs; // Array of structure `get_outputs_out`. - bool get_txid; // TXID - - KV_MAP_SERIALIZABLE - }; - - struct outkey - { - crypto::public_key key; // The public key of the output. - rct::key mask; - bool unlocked; // States if output is locked (`false`) or not (`true`). - uint64_t height; // Block height of the output. - crypto::hash txid; // Transaction id. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::vector outs; // List of outkey information. - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - //----------------------------------------------- /// Retrieve outputs /// @@ -1229,41 +1037,6 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - struct spent_key_image_info - { - std::string id_hash; // Key image. - std::vector txs_hashes; // List of tx hashes of the txes (usually one) spending that key image. - - KV_MAP_SERIALIZABLE - }; - - BELDEX_RPC_DOC_INTROSPECT - // Get hashes from transaction pool. Binary request. - struct GET_TRANSACTION_POOL_HASHES_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_transaction_pool_hashes.bin"); } - - static constexpr std::chrono::seconds long_poll_timeout{15}; - - struct request - { - bool flashed_txs_only; // Optional: If true only transactions that were sent via flash and approved are queried. - bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using LMQ RPC. - crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using LMQ RPC. - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector tx_hashes; // List of transaction hashes, - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - //----------------------------------------------- /// Get hashes from transaction pool. /// @@ -1860,16 +1633,6 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Exactly like GET_OUTPUT_DISTRIBUTION, but does a binary RPC transfer instead of JSON - struct GET_OUTPUT_DISTRIBUTION_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_output_distribution.bin"); } - - struct request : GET_OUTPUT_DISTRIBUTION::request {}; - using response = GET_OUTPUT_DISTRIBUTION::response; - }; - BELDEX_RPC_DOC_INTROSPECT struct POP_BLOCKS : LEGACY { @@ -2401,23 +2164,6 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get information on output blacklist. - struct GET_OUTPUT_BLACKLIST_BIN : PUBLIC, BINARY - { - static constexpr auto names() { return NAMES("get_output_blacklist.bin"); } - struct request : EMPTY {}; - - struct response - { - std::vector blacklist; // (Developer): Array of indexes from the global output list, corresponding to blacklisted key images. - std::string status; // Generic RPC error code. "OK" is the success value. - bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. - - KV_MAP_SERIALIZABLE - }; - }; - BELDEX_RPC_DOC_INTROSPECT // Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. struct GET_CHECKPOINTS : PUBLIC @@ -2781,17 +2527,7 @@ namespace cryptonote::rpc { GET_TRANSACTION_POOL_STATS, GET_TRANSACTIONS, GET_MASTER_NODES, - GET_MASTER_NODE_STATUS, - // Deprecated Monero NIH binary endpoints: - GET_ALT_BLOCKS_HASHES_BIN, - GET_BLOCKS_BIN, - GET_BLOCKS_BY_HEIGHT_BIN, - GET_HASHES_BIN, - GET_OUTPUTS_BIN, - GET_OUTPUT_BLACKLIST_BIN, - GET_OUTPUT_DISTRIBUTION_BIN, - GET_TRANSACTION_POOL_HASHES_BIN, - GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN + GET_MASTER_NODE_STATUS >; using FIXME_old_rpc_types = tools::type_list< IS_KEY_IMAGE_SPENT, From d34ede4cb023502a0a09ce199ce6f72e3d30e642 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 14:00:18 +0530 Subject: [PATCH 059/182] RPC: IS_KEY_IMAGE_SPENT updated RPC. Also adds the ability to look up more than one key image at a time, and shows errors through a proper fail_msg_writer(). --- src/daemon/command_parser_executor.cpp | 18 +++--- src/daemon/command_server.cpp | 4 +- src/daemon/rpc_command_executor.cpp | 28 ++++++---- src/daemon/rpc_command_executor.h | 2 +- src/rpc/core_rpc_server.cpp | 65 ++++++++++------------ src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 2 + src/rpc/core_rpc_server_commands_defs.cpp | 16 +++--- src/rpc/core_rpc_server_commands_defs.h | 47 ++++++++-------- 9 files changed, 93 insertions(+), 91 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 2f16bc41595..991ecedb6fc 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -420,20 +420,18 @@ bool command_parser_executor::is_key_image_spent(const std::vector& { if (args.empty()) { - std::cout << "expected: is_key_image_spent " << std::endl; + tools::fail_msg_writer() << "Invalid arguments. Expected: is_key_image_spent [ ...]\n"; return true; } - const std::string& str = args.front(); - crypto::key_image ki; - crypto::hash hash; - if (tools::hex_to_type(str, hash)) - { - memcpy(&ki, &hash, sizeof(ki)); - m_executor.is_key_image_spent(ki); + std::vector kis; + for (const auto& hex : args) { + if (!tools::hex_to_type(hex, kis.emplace_back())) { + tools::fail_msg_writer() << "Invalid key image: '" << hex << "'"; + return true; + } } - else - MERROR("invalid key image hash: " << str); + m_executor.is_key_image_spent(kis); return true; } diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index f8aa175a650..3a7706d22fa 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -135,8 +135,8 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) m_command_lookup.set_handler( "is_key_image_spent" , [this](const auto &x) { return m_parser.is_key_image_spent(x); } - , "is_key_image_spent " - , "Print whether a given key image is in the spent key images set." + , "is_key_image_spent [ ...]" + , "Queries whether one or more key images have been spent on the blockchain or in the memory pool." ); m_command_lookup.set_handler( "start_mining" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index bc746e1b83c..f8f3755e47e 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -910,20 +910,28 @@ bool rpc_command_executor::print_transaction(const crypto::hash& transaction_has return true; } -bool rpc_command_executor::is_key_image_spent(const crypto::key_image &ki) { - IS_KEY_IMAGE_SPENT::response res{}; - if (!invoke({{tools::type_to_hex(ki)}}, res, "Failed to retrieve key image status")) +bool rpc_command_executor::is_key_image_spent(const std::vector& ki) +{ + auto maybe_spent = try_running([this, &ki] { + auto kis = json::array(); + for (auto& k : ki) kis.push_back(tools::type_to_hex(k)); + return invoke(json{{"key_images", std::move(kis)}}); }, + "Failed to retrieve key image status"); + if (!maybe_spent) return false; + auto& spent_status = (*maybe_spent)["spent_status"]; - if (1 == res.spent_status.size()) - { - // first as hex - tools::success_msg_writer() << ki << ": " << (res.spent_status.front() ? "spent" : "unspent") << (res.spent_status.front() == IS_KEY_IMAGE_SPENT::SPENT_IN_POOL ? " (in pool)" : ""); - return true; + if (spent_status.size() != ki.size()) { + tools::fail_msg_writer() << "key image status could not be determined\n"; + return false; } - tools::fail_msg_writer() << "key image status could not be determined" << std::endl; - return false; + for (size_t i = 0; i < ki.size(); i++) { + int status = spent_status[i].get(); + tools::success_msg_writer() << ki[i] << ": " + << (status == 0 ? "unspent" : status == 1 ? "spent" : status == 2 ? "spent (in pool)" : "unknown"); + } + return true; } static void print_pool(const json& txs) { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 431a4572ab6..b9d5c14e952 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -180,7 +180,7 @@ class rpc_command_executor final { bool print_transaction(const crypto::hash& transaction_hash, bool include_metadata, bool include_hex, bool include_json); - bool is_key_image_spent(const crypto::key_image &ki); + bool is_key_image_spent(const std::vector& ki); bool print_transaction_pool(bool long_format); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 2d5f30bf6ca..8ad56576c8d 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1184,47 +1184,40 @@ namespace cryptonote::rpc { get.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - IS_KEY_IMAGE_SPENT::response core_rpc_server::invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context) + void core_rpc_server::invoke(IS_KEY_IMAGE_SPENT& spent, rpc_context context) { - IS_KEY_IMAGE_SPENT::response res{}; PERF_TIMER(on_is_key_image_spent); + /* if (use_bootstrap_daemon_if_necessary(req, res)) return res; - - std::vector key_images; - for(const auto& ki_hex_str: req.key_images) - { - if (!tools::hex_to_type(ki_hex_str, key_images.emplace_back())) - { - res.status = "Failed to parse hex representation of key image"; - return res; - } - } - std::vector spent_status; - bool r = m_core.are_key_images_spent(key_images, spent_status); - if(!r) - { - res.status = "Failed"; - return res; - } - res.spent_status.clear(); - for (size_t n = 0; n < spent_status.size(); ++n) - res.spent_status.push_back(spent_status[n] ? IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN : IS_KEY_IMAGE_SPENT::UNSPENT); - - // check the pool too - try { - auto kis = get_pool_kis(m_core); - for (size_t n = 0; n < res.spent_status.size(); ++n) - if (res.spent_status[n] == IS_KEY_IMAGE_SPENT::UNSPENT && kis.count(key_images[n])) - res.spent_status[n] = IS_KEY_IMAGE_SPENT::SPENT_IN_POOL; - } catch (const std::exception& e) { - MERROR("Failed to get pool key images: " << e.what()); - res.status = "Failed"; - return res; - } - res.status = STATUS_OK; - return res; + */ + spent.response["status"] = STATUS_FAILED; + + std::vector blockchain_spent; + if (!m_core.are_key_images_spent(spent.request.key_images, blockchain_spent)) + return; + std::optional kis; + auto spent_status = json::array(); + for (size_t n = 0; n < spent.request.key_images.size(); n++) { + if (blockchain_spent[n]) + spent_status.push_back(IS_KEY_IMAGE_SPENT::SPENT::BLOCKCHAIN); + else { + if (!kis) { + try { + kis = get_pool_kis(m_core); + } catch (const std::exception& e) { + MERROR("Failed to get pool key images: " << e.what()); + return; + } + } + spent_status.push_back(kis->count(spent.request.key_images[n]) + ? IS_KEY_IMAGE_SPENT::SPENT::POOL : IS_KEY_IMAGE_SPENT::SPENT::UNSPENT); + } + } + + spent.response["status"] = STATUS_OK; + spent.response["spent_status"] = std::move(spent_status); } //------------------------------------------------------------------------------------------------------------------------------ SEND_RAW_TX::response core_rpc_server::invoke(SEND_RAW_TX::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 424d0d08fa6..502da7ba982 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -224,6 +224,7 @@ namespace cryptonote::rpc { void invoke(GET_MASTER_NODES& sns, rpc_context context); void invoke(GET_LIMIT& limit, rpc_context context); void invoke(SET_LIMIT& limit, rpc_context context); + void invoke(IS_KEY_IMAGE_SPENT& spent, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -237,7 +238,6 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); // FIXME: unconverted JSON RPC endpoints: - IS_KEY_IMAGE_SPENT::response invoke(IS_KEY_IMAGE_SPENT::request&& req, rpc_context context); SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index faaef00991b..d402acf795e 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -341,4 +341,6 @@ namespace cryptonote::rpc { if (limit.request.limit_down < -1) throw std::domain_error{"limit_up must be >= -1"}; } + + void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 06529871a2b..f6651d4c72d 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -126,16 +126,16 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::request) - KV_SERIALIZE(key_images) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::request) +// KV_SERIALIZE(key_images) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::response) - KV_SERIALIZE(spent_status) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(IS_KEY_IMAGE_SPENT::response) +// KV_SERIALIZE(spent_status) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUTS::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 3c9b677f03f..e17f293c367 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -391,35 +391,36 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Check if outputs have been spent using the key image associated with the output. + /// Queries whether outputs have been spent using the key image associated with the output. + /// + /// Inputs: + /// + /// - \p key_images list of key images to check. For json requests these must be hex or + /// base64-encoded; for bt-requests they can be hex/base64 or raw bytes. + /// + /// Outputs + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore + /// untrusted ('true'), or when the daemon is fully synced ('false'). + /// - \p spent_status array of status codes returned in the same order as the `key_images` input. + /// Each value is one of: + /// - \p 0 the key image is unspent + /// - \p 1 the key image is spent in a mined block + /// - \p 2 the key image is spent in a transaction currently in the mempool struct IS_KEY_IMAGE_SPENT : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("is_key_image_spent"); } - enum STATUS - { + enum class SPENT : uint8_t { UNSPENT = 0, - SPENT_IN_BLOCKCHAIN = 1, - SPENT_IN_POOL = 2, - }; - - struct request - { - std::vector key_images; // List of key image hex strings to check. - - KV_MAP_SERIALIZABLE + BLOCKCHAIN = 1, + POOL = 2, }; - - struct response - { - std::vector spent_status; // List of statuses for each image checked. Statuses are follows: 0 = unspent, 1 = spent in blockchain, 2 = spent in transaction pool - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + struct request_parameters { + std::vector key_images; + } request; }; //----------------------------------------------- @@ -2526,11 +2527,11 @@ namespace cryptonote::rpc { GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_STATS, GET_TRANSACTIONS, + IS_KEY_IMAGE_SPENT, GET_MASTER_NODES, GET_MASTER_NODE_STATUS >; using FIXME_old_rpc_types = tools::type_list< - IS_KEY_IMAGE_SPENT, SEND_RAW_TX, GET_NET_STATS, GETBLOCKHASH, From e6c07ecffa092b1addf41ed71422fa5fe2ef37f6 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 14:44:16 +0530 Subject: [PATCH 060/182] RPC: SUBMIT_TRANSACTION new rpc update - Rename endpoint from "send_raw_transaction" to "submit_transaction" and rename the RPC struct similarly. - Rename tx_as_hex to just tx, and start accepting it as hex, base64, or raw bytes. (Transactions formats are consistent enough that we can easily tell which one we have from the first couple bytes). - Drop `do_not_relay` parameter as I can see no purpose to ever submitting a transaction to a daemon that shouldn't be relayed. (The only use I can see would be to try to exploit a PoW network with double-spends). - Drop `do_sanity_checks` parameter; the checks done here should be (and are!) done by the wallet, not the beldexd receiving the completed transaction. --- .../cryptonote_format_utils.cpp | 24 ++++- .../cryptonote_format_utils.h | 22 +++++ src/rpc/core_rpc_server.cpp | 96 ++++++++----------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 59 +++++++++++- src/rpc/core_rpc_server_command_parser.h | 2 + src/rpc/core_rpc_server_commands_defs.cpp | 28 +++--- src/rpc/core_rpc_server_commands_defs.h | 89 ++++++++++++----- 8 files changed, 224 insertions(+), 98 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 0a70a9e86d2..91a03187bfb 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1125,7 +1125,7 @@ namespace cryptonote if (tvc.m_verifivation_failed) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection if (tvc.m_verifivation_impossible) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain - if (tvc.m_should_be_relayed) os << "TX should be relayed, "; + if (!tvc.m_should_be_relayed) os << "TX should NOT be relayed, "; if (tvc.m_added_to_pool) os << "TX added to pool, "; if (tvc.m_low_mixin) os << "Insufficient mixin, "; if (tvc.m_double_spend) os << "Double spend TX, "; @@ -1149,6 +1149,28 @@ namespace cryptonote return buf; } + + std::unordered_set tx_verification_failure_codes(const tx_verification_context& tvc) { + std::unordered_set reasons; + + if (tvc.m_verifivation_failed) reasons.insert("failed"); + if (tvc.m_verifivation_impossible) reasons.insert("altchain"); + if (tvc.m_low_mixin) reasons.insert("mixin"); + if (tvc.m_double_spend) reasons.insert("double_spend"); + if (tvc.m_invalid_input) reasons.insert("invalid_input"); + if (tvc.m_invalid_output) reasons.insert("invalid_output"); + if (tvc.m_too_few_outputs) reasons.insert("too_few_outputs"); + if (tvc.m_too_big) reasons.insert("too_big"); + if (tvc.m_overspend) reasons.insert("overspend"); + if (tvc.m_fee_too_low) reasons.insert("fee_too_low"); + if (tvc.m_invalid_version) reasons.insert("invalid_version"); + if (tvc.m_invalid_type) reasons.insert("invalid_type"); + if (tvc.m_key_image_locked_by_mnode) reasons.insert("mnode_locked"); + if (tvc.m_key_image_blacklisted) reasons.insert("blacklisted"); + if (!tvc.m_should_be_relayed) reasons.insert("not_relayed"); + + return reasons; + } //--------------------------------------------------------------- std::string print_vote_verification_context(vote_verification_context const &vvc, master_nodes::quorum_vote_t const *vote) { diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index a98855df0a5..b1b029bb0e3 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -224,6 +224,28 @@ namespace cryptonote std::string print_tx_verification_context (tx_verification_context const &tvc, transaction const *tx = nullptr); std::string print_vote_verification_context(vote_verification_context const &vvc, master_nodes::quorum_vote_t const *vote = nullptr); + + // Returns code strings for various tx verification failures: + // - "failed" -- general "bad transaction" code + // - "altchain" -- the transaction is spending outputs that exist on an altchain. + // - "mixin" -- the transaction has the wrong number of decoys + // - "double_spend" -- the transaction is spending outputs that are already spent + // - "invalid_input" -- one or more inputs in the transaction are invalid + // - "invalid_output" -- out or more outputs in the transaction are invalid + // - "too_few_outputs" -- the transaction does not create enough outputs (at least two are + // required, currently). + // - "too_big" -- the transaction is too large + // - "overspend" -- the transaction spends (via outputs + fees) more than the inputs + // - "fee_too_low" -- the transaction fee is insufficient + // - "invalid_version" -- the transaction version is invalid (the wallet likely needs an update). + // - "invalid_type" -- the transaction type is invalid + // - "mnode_locked" -- one or more outputs are currently staked to a registred master node and + // thus are not currently spendable on the blockchain. + // - "blacklisted" -- the outputs are currently blacklisted (from being in the 30-day penalty + // period following a master node deregistration). + // - "not_relayed" -- the transaction cannot be relayed to the network for some reason but may + // still have been accepted by this node. + std::unordered_set tx_verification_failure_codes(const tx_verification_context& tvc); bool is_valid_address(const std::string address, cryptonote::network_type nettype, bool allow_subaddress = true, bool allow_integrated = true); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 8ad56576c8d..10ca4164fba 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -120,7 +120,7 @@ namespace cryptonote::rpc { static_assert(!FIXME_has_nested_response_v); cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { - RPC rpc; + RPC rpc{}; try { if (auto body = request.body_view()) { if (body->front() == 'd') { // Looks like a bt dict @@ -1220,88 +1220,72 @@ namespace cryptonote::rpc { spent.response["spent_status"] = std::move(spent_status); } //------------------------------------------------------------------------------------------------------------------------------ - SEND_RAW_TX::response core_rpc_server::invoke(SEND_RAW_TX::request&& req, rpc_context context) + void core_rpc_server::invoke(SUBMIT_TRANSACTION& tx, rpc_context context) { - SEND_RAW_TX::response res{}; - - PERF_TIMER(on_send_raw_tx); - if (use_bootstrap_daemon_if_necessary(req, res)) + PERF_TIMER(on_submit_transaction); + /* + if (use_bootstrap_daemon_if_necessary(req, res)) return res; + */ - CHECK_CORE_READY(); - - if (!oxenc::is_hex(req.tx_as_hex)) { - LOG_PRINT_L0("[on_send_raw_tx]: Failed to parse tx from hexbuff: " << req.tx_as_hex); - res.status = "Failed"; - return res; + if (!check_core_ready()) { + tx.response["status"] = STATUS_BUSY; + return; } - auto tx_blob = oxenc::from_hex(req.tx_as_hex); - if (req.flash) + if (tx.request.flash) { - auto future = m_core.handle_flash_tx(tx_blob); + auto future = m_core.handle_flash_tx(tx.request.tx); + // FIXME: blocking here for 10s is nasty; we need to stash this request and come back to it + // when the flash tx result comes back, and wait for longer (maybe 30s). + // + // FIXME 2: on timeout, we should check the mempool to see if it arrived that way so that we + // return success if it got out to the network, even if we didn't get the flash quorum reply + // for some reason. auto status = future.wait_for(10s); if (status != std::future_status::ready) { - res.status = "Failed"; - res.reason = "Flash quorum timeout"; - res.flash_status = flash_result::timeout; - return res; + tx.response["status"] = STATUS_FAILED; + tx.response["reason"] = "Flash quorum timeout"; + tx.response["flash_status"] = flash_result::timeout; + return; } try { auto result = future.get(); - res.flash_status = result.first; + tx.response["flash_status"] = result.first; if (result.first == flash_result::accepted) { - res.status = STATUS_OK; + tx.response["status"] = STATUS_OK; } else { - res.status = "Failed"; - res.reason = !result.second.empty() ? result.second : result.first == flash_result::timeout ? "Flash quorum timeout" : "Transaction rejected by flash quorum"; + tx.response["status"] = STATUS_FAILED; + tx.response["reason"] = !result.second.empty() ? result.second : result.first == flash_result::timeout ? "Flash quorum timeout" : "Transaction rejected by flash quorum"; } } catch (const std::exception &e) { - res.flash_status = flash_result::rejected; - res.status = "Failed"; - res.reason = std::string{"Transaction failed: "} + e.what(); + tx.response["flash_status"] = flash_result::rejected; + tx.response["status"] = STATUS_FAILED; + tx.response["reason"] = "Transaction failed: "s + e.what(); } - return res; + return; } tx_verification_context tvc{}; - if(!m_core.handle_incoming_tx(tx_blob, tvc, tx_pool_options::new_tx(req.do_not_relay)) || tvc.m_verifivation_failed) - { - const vote_verification_context &vvc = tvc.m_vote_ctx; - res.status = "Failed"; - std::string reason = print_tx_verification_context (tvc); - reason += print_vote_verification_context(vvc); - res.tvc = tvc; - const std::string punctuation = res.reason.empty() ? "" : ": "; - if (tvc.m_verifivation_failed) - { - LOG_PRINT_L0("[on_send_raw_tx]: tx verification failed" << punctuation << reason); - } - else - { - LOG_PRINT_L0("[on_send_raw_tx]: Failed to process tx" << punctuation << reason); - } - return res; - } - - if(!tvc.m_should_be_relayed) + if (!m_core.handle_incoming_tx(tx.request.tx, tvc, tx_pool_options::new_tx()) || tvc.m_verifivation_failed || !tvc.m_should_be_relayed) { - LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed"); - res.reason = "Not relayed"; - res.not_relayed = true; - res.status = STATUS_OK; - return res; + tx.response["status"] = STATUS_FAILED; + auto reason = print_tx_verification_context(tvc); + LOG_PRINT_L0("[on_send_raw_tx]: " << (tvc.m_verifivation_failed ? "tx verification failed" : "Failed to process tx") << reason); + tx.response["reason"] = std::move(reason); + tx.response["reason_codes"] = tx_verification_failure_codes(tvc); + return; } + // Why is is the RPC handler's responsibility to tell the p2p protocol to relay a transaction?! NOTIFY_NEW_TRANSACTIONS::request r{}; - r.txs.push_back(tx_blob); + r.txs.push_back(std::move(tx.request.tx)); cryptonote_connection_context fake_context{}; m_core.get_protocol()->relay_transactions(r, fake_context); - //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes - res.status = STATUS_OK; - return res; + tx.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(START_MINING& start_mining, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 502da7ba982..41d8302f13a 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -225,6 +225,7 @@ namespace cryptonote::rpc { void invoke(GET_LIMIT& limit, rpc_context context); void invoke(SET_LIMIT& limit, rpc_context context); void invoke(IS_KEY_IMAGE_SPENT& spent, rpc_context context); + void invoke(SUBMIT_TRANSACTION& tx, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -238,7 +239,6 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); // FIXME: unconverted JSON RPC endpoints: - SEND_RAW_TX::response invoke(SEND_RAW_TX::request&& req, rpc_context context); GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index d402acf795e..e3d99efb19e 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -342,5 +342,62 @@ namespace cryptonote::rpc { throw std::domain_error{"limit_up must be >= -1"}; } - void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); + void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in){ + get_values(in, "key_images", spent.request.key_images); + } + + void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in) { + if (auto* json_in = std::get_if(&in)) + if (auto it = json_in->find("tx_as_hex"); it != json_in->end()) + (*json_in)["tx"] = std::move(*it); + + auto& tx_data = tx.request.tx; + get_values(in, + "flash", tx.request.flash, + "tx", required{tx_data}); + + if (tx_data.empty()) // required above will make sure it's specified, but doesn't guarantee against an empty value + throw std::domain_error{"Invalid 'tx' value: cannot be empty"}; + + // tx can be specified as base64, hex, or binary, so try to figure out which one we have by + // looking at the beginning. + // + // An encoded transaction always starts with the version byte, currently 0-4 (though 0 isn't + // actually used), with higher future values possible. That means in hex we get something like: + // `04...` and in base64 we get `B` (because the first 6 bits are 000001, and the b64 alphabet + // begins at `A` for 0). Thus the first bytes, for tx versions 0 through 48, are thus: + // + // binary: (31 binary control characters through 0x1f ... ) (space) ! " # $ % & ' ( ) * + , - . / 0 + // base64: A A A A B B B B C C C C D D D D E E E E F F F F G G G G H H H H I I I I J J J J K K K K L L L L M + // hex: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 + // + // and so we run into the first ambiguity at version 48. Since we are currently only at version + // 4 (and Oxen started at version 2) this is likely to be sufficient for an extremely long time. + // + // Thus our heuristic: + // 'A'-'L' => base64 + // '0'-'2' => hex + // \x00-\x2f => bytes + // anything else we reject as garbage. + auto tx0 = tx_data.front(); + bool good = false; + if (tx0 <= 0x2f) { + good = true; + } else if (tx0 >= 'A' && tx0 <= 'L') { + if (oxenc::is_base64(tx_data)) { + auto end = oxenc::from_base64(tx_data.begin(), tx_data.end(), tx_data.begin()); + tx_data.erase(end, tx_data.end()); + good = true; + } + } else if (tx0 >= '0' && tx0 <= '2') { + if (oxenc::is_hex(tx_data)) { + auto end = oxenc::from_hex(tx_data.begin(), tx_data.end(), tx_data.begin()); + tx_data.erase(end, tx_data.end()); + good = true; + } + } + + if (!good) + throw std::domain_error{"Invalid 'tx' value: expected hex, base64, or bytes"}; + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index a9f463808e2..99bfc6c97af 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -25,4 +25,6 @@ namespace cryptonote::rpc { void parse_request(GET_TRANSACTIONS& hfinfo, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); void parse_request(SET_LIMIT& limit, rpc_input in); + void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); + void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index f6651d4c72d..5d862ac1e5e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -160,22 +160,22 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SEND_RAW_TX::request) - KV_SERIALIZE(tx_as_hex) - KV_SERIALIZE_OPT(do_not_relay, false) - KV_SERIALIZE_OPT(flash, false) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SEND_RAW_TX::request) +// KV_SERIALIZE(tx_as_hex) +// KV_SERIALIZE_OPT(do_not_relay, false) +// KV_SERIALIZE_OPT(flash, false) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SEND_RAW_TX::response) - KV_SERIALIZE(status) - KV_SERIALIZE(reason) - KV_SERIALIZE(not_relayed) - KV_SERIALIZE(sanity_check_failed) - KV_SERIALIZE(untrusted) - KV_SERIALIZE(tvc) - KV_SERIALIZE_ENUM(flash_status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SEND_RAW_TX::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(reason) +// KV_SERIALIZE(not_relayed) +// KV_SERIALIZE(sanity_check_failed) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE(tvc) +// KV_SERIALIZE_ENUM(flash_status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(START_MINING::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e17f293c367..d009eaf2e9f 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -462,33 +462,72 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Broadcast a raw transaction to the network. - struct SEND_RAW_TX : PUBLIC, LEGACY - { - static constexpr auto names() { return NAMES("send_raw_transaction", "sendrawtransaction"); } + /// Submit a transaction to be broadcast to the network. + /// + /// Inputs: + /// + /// - \p tx the full transaction data itself. Can be hex- or base64-encoded for json requests; + /// can also be those or raw bytes for bt-encoded requests. For backwards compatibility, + /// hex-encoded data can also be passed in a json request via the parameter \p tx_as_hex but + /// that is deprecated and will eventually be removed. + /// - \p flash Should be set to true if this transaction is a flash transaction that should be + /// submitted to a flash quorum rather than distributed through the mempool. + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore + /// untrusted ('true'), or when the daemon is fully synced ('false'). + /// - \p reason String containing additional information on why a transaction failed. + /// - \p flash_status Set to the result of submitting this transaction to the flash quorum. 1 + /// means the quorum rejected the transaction; 2 means the quorum accepted it; 3 means there was + /// a timeout connecting to or waiting for a response from the flash quorum. Note that a + /// timeout response does *not* necessarily mean the transaction has not made it to the network. + /// - \p not_relayed will be set to true if some problem with the transactions prevents it from + /// being relayed to the network, omitted otherwise. + /// - \p reason_codes If the transaction was rejected this will be set to a set of reason string + /// codes indicating why the transaction failed: + /// - \c "failed" -- general "bad transaction" code + /// - \c "altchain" -- the transaction is spending outputs that exist on an altchain. + /// - \c "mixin" -- the transaction has the wrong number of decoys + /// - \c "double_spend" -- the transaction is spending outputs that are already spent + /// - \c "invalid_input" -- one or more inputs in the transaction are invalid + /// - \c "invalid_output" -- out or more outputs in the transaction are invalid + /// - \c "too_few_outputs" -- the transaction does not create enough outputs (at least two are + /// required, currently). + /// - \c "too_big" -- the transaction is too large + /// - \c "overspend" -- the transaction spends (via outputs + fees) more than the inputs + /// - \c "fee_too_low" -- the transaction fee is insufficient + /// - \c "invalid_version" -- the transaction version is invalid (the wallet likely needs an + /// update). + /// - \c "invalid_type" -- the transaction type is invalid + /// - \c "mnode_locked" -- one or more outputs are currently staked to a registred master node + /// and thus are not currently spendable on the blockchain. + /// - \c "blacklisted" -- the outputs are currently blacklisted (from being in the 30-day + /// penalty period following a master node deregistration). + /// - \c "flash" -- the flash transaction failed (see `flash_status`) + struct SUBMIT_TRANSACTION : PUBLIC, LEGACY + { + static constexpr auto names() { return NAMES("submit_transaction", "send_raw_transaction", "sendrawtransaction"); } - struct request + struct request_parameters { - std::string tx_as_hex; // Full transaction information as hexidecimal string. - bool do_not_relay; // (Optional: Default false) Stop relaying transaction to other nodes. Ignored if `flash` is true. - bool flash; // (Optional: Default false) Submit this as a flash tx rather than into the mempool. + std::string tx; + bool flash = false; + } request; - KV_MAP_SERIALIZABLE - }; + /*struct response + { + std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. + std::string reason; // Additional information. Currently empty, "Not relayed" if transaction was accepted but not relayed, or some descriptive message of why the tx failed. + bool not_relayed; // Transaction was not relayed (true) or relayed (false). + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + tx_verification_context tvc; + bool sanity_check_failed; + blink_result blink_status; // 0 for a non-blink tx. For a blink tx: 1 means rejected, 2 means accepted, 3 means timeout. - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - std::string reason; // Additional information. Currently empty, "Not relayed" if transaction was accepted but not relayed, or some descriptive message of why the tx failed. - bool not_relayed; // Transaction was not relayed (true) or relayed (false). - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - tx_verification_context tvc; - bool sanity_check_failed; - flash_result flash_status; // 0 for a non-flash tx. For a flash tx: 1 means rejected, 2 means accepted, 3 means timeout. - - KV_MAP_SERIALIZABLE - }; + KV_MAP_SERIALIZABLE + };*/ }; //----------------------------------------------- @@ -2529,10 +2568,10 @@ namespace cryptonote::rpc { GET_TRANSACTIONS, IS_KEY_IMAGE_SPENT, GET_MASTER_NODES, - GET_MASTER_NODE_STATUS + GET_MASTER_NODE_STATUS, + SUBMIT_TRANSACTION >; using FIXME_old_rpc_types = tools::type_list< - SEND_RAW_TX, GET_NET_STATS, GETBLOCKHASH, GETBLOCKTEMPLATE, From 8036aba1b6c29f9f1d0202f7a119e7fe74c512d0 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 15:10:34 +0530 Subject: [PATCH 061/182] RPC: GET_BLOCK_HASH updated Old:height: [h] and New:heights: [123, 456, ...] --- src/rpc/core_rpc_server.cpp | 26 ++++++------ src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 8 +++- src/rpc/core_rpc_server_command_parser.h | 3 +- src/rpc/core_rpc_server_commands_defs.cpp | 18 ++++---- src/rpc/core_rpc_server_commands_defs.h | 49 ++++++++++++---------- 6 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 10ca4164fba..250063eea82 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1609,7 +1609,7 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GETBLOCKCOUNT& getblockcount, rpc_context context) + void core_rpc_server::invoke(GET_BLOCK_COUNT& getblockcount, rpc_context context) { PERF_TIMER(on_getblockcount); { @@ -1624,29 +1624,27 @@ namespace cryptonote::rpc { getblockcount.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GETBLOCKHASH::response core_rpc_server::invoke(GETBLOCKHASH::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_BLOCK_HASH& getblockhash, rpc_context context) { - GETBLOCKHASH::response res{}; - PERF_TIMER(on_getblockhash); { std::shared_lock lock{m_bootstrap_daemon_mutex}; if (m_should_use_bootstrap_daemon) { - res = "This command is unsupported for bootstrap daemon"; - return res; + getblockhash.response["status"] = "This command is unsupported for bootstrap daemon"; + return; } } - if(req.height.size() != 1) - throw rpc_error{ERROR_WRONG_PARAM, "Wrong parameters, expected height"}; - uint64_t h = req.height[0]; - if(m_core.get_current_blockchain_height() <= h) - throw rpc_error{ERROR_TOO_BIG_HEIGHT, - "Requested block height: " + std::to_string(h) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1)}; + auto curr_height = m_core.get_current_blockchain_height(); + for (auto h : getblockhash.request.heights) { + if (h >= curr_height) + throw rpc_error{ERROR_TOO_BIG_HEIGHT, + "Requested block height: " + tools::int_to_string(h) + " greater than current top block height: " + tools::int_to_string(curr_height - 1)}; - res = tools::type_to_hex(m_core.get_block_id_by_height(h)); - return res; + getblockhash.response_hex[tools::int_to_string(h)] = m_core.get_block_id_by_height(h); + } + getblockhash.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ GETBLOCKTEMPLATE::response core_rpc_server::invoke(GETBLOCKTEMPLATE::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 41d8302f13a..b4314b5affe 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -213,7 +213,7 @@ namespace cryptonote::rpc { void invoke(STOP_MINING& stop_mining, rpc_context context); void invoke(SAVE_BC& save_bc, rpc_context context); void invoke(STOP_DAEMON& stop_daemon, rpc_context context); - void invoke(GETBLOCKCOUNT& getblockcount, rpc_context context); + void invoke(GET_BLOCK_COUNT& getblockcount, rpc_context context); void invoke(MINING_STATUS& mining_status, rpc_context context); void invoke(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_context context); void invoke(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_context context); @@ -226,6 +226,7 @@ namespace cryptonote::rpc { void invoke(SET_LIMIT& limit, rpc_context context); void invoke(IS_KEY_IMAGE_SPENT& spent, rpc_context context); void invoke(SUBMIT_TRANSACTION& tx, rpc_context context); + void invoke(GET_BLOCK_HASH& req, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -248,7 +249,6 @@ namespace cryptonote::rpc { IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); POP_BLOCKS::response invoke(POP_BLOCKS::request&& req, rpc_context context); - GETBLOCKHASH::response invoke(GETBLOCKHASH::request&& req, rpc_context context); GETBLOCKTEMPLATE::response invoke(GETBLOCKTEMPLATE::request&& req, rpc_context context); SUBMITBLOCK::response invoke(SUBMITBLOCK::request&& req, rpc_context context); GENERATEBLOCKS::response invoke(GENERATEBLOCKS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index e3d99efb19e..3d8fc9e75c3 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -264,7 +264,7 @@ namespace cryptonote::rpc { } void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in) { } - void parse_request(GETBLOCKCOUNT& getblockcount, rpc_input in) { + void parse_request(GET_BLOCK_COUNT& getblockcount, rpc_input in) { } void parse_request(STOP_DAEMON& stop_daemon, rpc_input in) { } @@ -400,4 +400,10 @@ namespace cryptonote::rpc { if (!good) throw std::domain_error{"Invalid 'tx' value: expected hex, base64, or bytes"}; } + + void parse_request(GET_BLOCK_HASH& bh, rpc_input in) { + get_values(in, "heights", bh.request.heights); + if (bh.request.heights.size() > bh.MAX_HEIGHTS) + throw std::domain_error{"Error: too many block heights requested at once"}; + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 99bfc6c97af..0a85b509d75 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -17,7 +17,7 @@ namespace cryptonote::rpc { void parse_request(MINING_STATUS& mining_status, rpc_input in); void parse_request(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_input in); void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in); - void parse_request(GETBLOCKCOUNT& getblockcount, rpc_input in); + void parse_request(GET_BLOCK_COUNT& getblockcount, rpc_input in); void parse_request(STOP_DAEMON& stop_daemon, rpc_input in); void parse_request(SAVE_BC& save_bc, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); @@ -27,4 +27,5 @@ namespace cryptonote::rpc { void parse_request(SET_LIMIT& limit, rpc_input in); void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); + void parse_request(GET_BLOCK_HASH& bh, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 5d862ac1e5e..b8da3f47d5e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -253,20 +253,20 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKCOUNT::response) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_COUNT::response) // KV_SERIALIZE(count) // KV_SERIALIZE(status) // KV_SERIALIZE_MAP_CODE_END() -bool GETBLOCKHASH::request::load(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) -{ - return epee::serialization::perform_serialize(height, ps, hparent_section, "height"); -} -bool GETBLOCKHASH::request::store(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) -{ - return epee::serialization::perform_serialize(height, ps, hparent_section, "height"); -} +// bool GETBLOCKHASH::request::load(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) +// { +// return epee::serialization::perform_serialize(height, ps, hparent_section, "height"); +// } +// bool GETBLOCKHASH::request::store(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) +// { +// return epee::serialization::perform_serialize(height, ps, hparent_section, "height"); +// } KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKTEMPLATE::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d009eaf2e9f..d93c6f8f126 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -563,7 +563,7 @@ namespace cryptonote::rpc { /// Output values available from a restricted/admin RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - struct STOP_MINING : LEGACY + struct STOP_MINING : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("stop_mining"); } }; @@ -584,7 +584,7 @@ namespace cryptonote::rpc { /// - \p block_target The expected time to solve per block, i.e. TARGET_BLOCK_TIME /// - \p block_reward Block reward for the current block being mined. /// - \p difficulty The difficulty for the current block being mined. - struct MINING_STATUS : LEGACY + struct MINING_STATUS : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("mining_status"); } }; @@ -694,7 +694,7 @@ namespace cryptonote::rpc { /// Output values available from a restricted/admin RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - struct SAVE_BC : LEGACY + struct SAVE_BC : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("save_bc"); } }; @@ -708,26 +708,29 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p count Number of blocks in logest chain seen by the node. - struct GETBLOCKCOUNT : PUBLIC + struct GET_BLOCK_COUNT : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_block_count", "getblockcount"); } }; - BELDEX_RPC_DOC_INTROSPECT - // Look up a block's hash by its height. - struct GETBLOCKHASH : PUBLIC + /// Look up one or more blocks' hashes by their height. + /// + /// Inputs: + /// - heights array of block heights of which to look up the block hashes. Accepts at most 1000 + /// heights per request. + /// + /// Output values are pairs of heights as keys to block hashes as values: + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p height the current blockchain height of this node + /// - \p the block hash of the block with the given height. Note that each height key is + /// the stringified integer value, e.g. "3456" rather than 3456. + struct GET_BLOCK_HASH : PUBLIC { static constexpr auto names() { return NAMES("get_block_hash", "on_get_block_hash", "on_getblockhash"); } - - struct request { - std::vector height; // Block height (int array of length 1). - - // epee serialization; this is a bit hacky because epee serialization makes things hacky. - bool load(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section = nullptr); - bool store(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section = nullptr); - }; - - using response = std::string; // Block hash (string). + static constexpr size_t MAX_HEIGHTS = 1000; + struct request_parameters { + std::vector heights; + } request; // Block hash (string). }; BELDEX_RPC_DOC_INTROSPECT @@ -1087,7 +1090,7 @@ namespace cryptonote::rpc { /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p tx_hashes List of transaction hashes, /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY + struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } }; @@ -1140,7 +1143,7 @@ namespace cryptonote::rpc { /// - \p histo_max See `histo` for details. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not /// trusted (`true`), or when the daemon is fully synced (`false`). - struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY + struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } @@ -1245,7 +1248,7 @@ namespace cryptonote::rpc { /// Output values available from a restricted/admin RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - struct STOP_DAEMON : LEGACY + struct STOP_DAEMON : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("stop_daemon"); } }; @@ -2561,7 +2564,7 @@ namespace cryptonote::rpc { SAVE_BC, STOP_DAEMON, SYNC_INFO, - GETBLOCKCOUNT, + GET_BLOCK_COUNT, MINING_STATUS, GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_STATS, @@ -2569,11 +2572,11 @@ namespace cryptonote::rpc { IS_KEY_IMAGE_SPENT, GET_MASTER_NODES, GET_MASTER_NODE_STATUS, - SUBMIT_TRANSACTION + SUBMIT_TRANSACTION, + GET_BLOCK_HASH >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, - GETBLOCKHASH, GETBLOCKTEMPLATE, SUBMITBLOCK, GENERATEBLOCKS, From a335c800c1c472d75ac5fd9b5b07238c07047a0e Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 15:16:32 +0530 Subject: [PATCH 062/182] Remove external block generation/submission support We have no need for any of this, so just remove it rather than convert it. (This doesn't drop block generation support, which we can still do via start/stop mining calls, but rather just the ability to hook up an external miner, which we are unlikely to ever need again). --- src/rpc/core_rpc_server.cpp | 181 ---------------------- src/rpc/core_rpc_server.h | 3 - src/rpc/core_rpc_server_commands_defs.cpp | 48 ------ src/rpc/core_rpc_server_commands_defs.h | 79 ---------- 4 files changed, 311 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 250063eea82..4f312d395da 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1647,187 +1647,6 @@ namespace cryptonote::rpc { getblockhash.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GETBLOCKTEMPLATE::response core_rpc_server::invoke(GETBLOCKTEMPLATE::request&& req, rpc_context context) - { - GETBLOCKTEMPLATE::response res{}; - - PERF_TIMER(on_getblocktemplate); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - - if(!check_core_ready()) - throw rpc_error{ERROR_CORE_BUSY, "Core is busy"}; - - if(req.reserve_size > 255) - throw rpc_error{ERROR_TOO_BIG_RESERVE_SIZE, "Too big reserved size, maximum 255"}; - - if(req.reserve_size && !req.extra_nonce.empty()) - throw rpc_error{ERROR_WRONG_PARAM, "Cannot specify both a reserve_size and an extra_nonce"}; - - if(req.extra_nonce.size() > 510) - throw rpc_error{ERROR_TOO_BIG_RESERVE_SIZE, "Too big extra_nonce size, maximum 510 hex chars"}; - - cryptonote::address_parse_info info; - - if(!req.wallet_address.size() || !cryptonote::get_account_address_from_str(info, m_core.get_nettype(), req.wallet_address)) - throw rpc_error{ERROR_WRONG_WALLET_ADDRESS, "Failed to parse wallet address"}; - if (info.is_subaddress) - throw rpc_error{ERROR_MINING_TO_SUBADDRESS, "Mining to subaddress is not supported yet"}; - - block b; - cryptonote::blobdata blob_reserve; - if(!req.extra_nonce.empty()) - { - if(!oxenc::is_hex(req.extra_nonce)) - throw rpc_error{ERROR_WRONG_PARAM, "Parameter extra_nonce should be a hex string"}; - blob_reserve = oxenc::from_hex(req.extra_nonce); - } - else - blob_reserve.resize(req.reserve_size, 0); - cryptonote::difficulty_type diff; - crypto::hash prev_block; - if (!req.prev_block.empty()) - { - if (!tools::hex_to_type(req.prev_block, prev_block)) - throw rpc_error{ERROR_INTERNAL, "Invalid prev_block"}; - } - if(!m_core.create_miner_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, diff, res.height, res.expected_reward, blob_reserve)) - { - LOG_ERROR("Failed to create block template"); - throw rpc_error{ERROR_INTERNAL, "Internal error: failed to create block template"}; - } - - if (b.major_version >= hf::hf13_checkpointing) - { - uint64_t seed_height, next_height; - crypto::hash seed_hash; - crypto::rx_seedheights(res.height, &seed_height, &next_height); - seed_hash = m_core.get_block_id_by_height(seed_height); - res.seed_hash = tools::type_to_hex(seed_hash); - if (next_height != seed_height) { - seed_hash = m_core.get_block_id_by_height(next_height); - res.next_seed_hash = tools::type_to_hex(seed_hash); - } - } - res.difficulty = diff; - - blobdata block_blob = t_serializable_object_to_blob(b); - crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); - if(tx_pub_key == crypto::null_pkey) - { - LOG_ERROR("Failed to get tx pub key in coinbase extra"); - throw rpc_error{ERROR_INTERNAL, "Internal error: failed to create block template"}; - } - res.reserved_offset = block_blob.find(tx_pub_key.data, 0, sizeof(tx_pub_key.data)); - if (res.reserved_offset == block_blob.npos) - { - LOG_ERROR("Failed to find tx pub key in blockblob"); - throw rpc_error{ERROR_INTERNAL, "Internal error: failed to create block template"}; - } - if (req.reserve_size) - res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) - else - res.reserved_offset = 0; - if(res.reserved_offset + req.reserve_size > block_blob.size()) - { - LOG_ERROR("Failed to calculate offset for "); - throw rpc_error{ERROR_INTERNAL, "Internal error: failed to create block template"}; - } - blobdata hashing_blob = get_block_hashing_blob(b); - res.prev_hash = tools::type_to_hex(b.prev_id); - res.blocktemplate_blob = oxenc::to_hex(block_blob); - res.blockhashing_blob = oxenc::to_hex(hashing_blob); - res.status = STATUS_OK; - return res; - } - //------------------------------------------------------------------------------------------------------------------------------ - SUBMITBLOCK::response core_rpc_server::invoke(SUBMITBLOCK::request&& req, rpc_context context) - { - SUBMITBLOCK::response res{}; - - PERF_TIMER(on_submitblock); - { - std::shared_lock lock{m_bootstrap_daemon_mutex}; - if (m_should_use_bootstrap_daemon) - { - res.status = "This command is unsupported for bootstrap daemon"; - return res; - } - } - CHECK_CORE_READY(); - if(req.blob.size()!=1) - throw rpc_error{ERROR_WRONG_PARAM, "Wrong param"}; - if(!oxenc::is_hex(req.blob[0])) - throw rpc_error{ERROR_WRONG_BLOCKBLOB, "Wrong block blob"}; - auto blockblob =oxenc::from_hex(req.blob[0]); - // Fixing of high orphan issue for most pools - // Thanks Boolberry! - block b; - if(!parse_and_validate_block_from_blob(blockblob, b)) - throw rpc_error{ERROR_WRONG_BLOCKBLOB, "Wrong block blob"}; - - // Fix from Boolberry neglects to check block - // size, do that with the function below - if(!m_core.check_incoming_block_size(blockblob)) - throw rpc_error{ERROR_WRONG_BLOCKBLOB_SIZE, "Block blob size is too big, rejecting block"}; - - block_verification_context bvc; - if(!m_core.handle_block_found(b, bvc)) - throw rpc_error{ERROR_BLOCK_NOT_ACCEPTED, "Block not accepted"}; - res.status = STATUS_OK; - return res; - } - //------------------------------------------------------------------------------------------------------------------------------ - GENERATEBLOCKS::response core_rpc_server::invoke(GENERATEBLOCKS::request&& req, rpc_context context) - { - GENERATEBLOCKS::response res{}; - - PERF_TIMER(on_generateblocks); - - CHECK_CORE_READY(); - - res.status = STATUS_OK; - - if(m_core.get_nettype() != network_type::FAKECHAIN) - throw rpc_error{ERROR_REGTEST_REQUIRED, "Regtest required when generating blocks"}; - - SUBMITBLOCK::request submit_req{}; - submit_req.blob.emplace_back(); // string vector containing exactly one block blob - - res.height = m_core.get_blockchain_storage().get_current_blockchain_height(); - - for(size_t i = 0; i < req.amount_of_blocks; i++) - { - GETBLOCKTEMPLATE::request template_req{}; - template_req.reserve_size = 1; - template_req.wallet_address = req.wallet_address; - template_req.prev_block = i == 0 ? req.prev_block : res.blocks.back(); - auto template_res = invoke(std::move(template_req), context); - res.status = template_res.status; - - blobdata blockblob; - if(!oxenc::is_hex(template_res.blocktemplate_blob)) - throw rpc_error{ERROR_WRONG_BLOCKBLOB, "Wrong block blob"}; - block b; - if(!parse_and_validate_block_from_blob(oxenc::from_hex(template_res.blocktemplate_blob), b)) - throw rpc_error{ERROR_WRONG_BLOCKBLOB, "Wrong block blob"}; - b.nonce = req.starting_nonce; - miner::find_nonce_for_given_block([this](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash) { - hash = cryptonote::get_block_longhash_w_blockchain(cryptonote::network_type::FAKECHAIN, &(m_core.get_blockchain_storage()), b, height, threads); - return true; - }, b, template_res.difficulty, template_res.height); - - submit_req.blob[0] = oxenc::to_hex(block_to_blob(b)); - auto submit_res = invoke(std::move(submit_req), context); - res.status = submit_res.status; - - res.blocks.push_back(tools::type_to_hex(get_block_hash(b))); - res.height = template_res.height; - } - - return res; - } - //------------------------------------------------------------------------------------------------------------------------------ uint64_t core_rpc_server::get_block_reward(const block& blk) { uint64_t reward = 0; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b4314b5affe..eb560f2d1f4 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -249,9 +249,6 @@ namespace cryptonote::rpc { IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); POP_BLOCKS::response invoke(POP_BLOCKS::request&& req, rpc_context context); - GETBLOCKTEMPLATE::response invoke(GETBLOCKTEMPLATE::request&& req, rpc_context context); - SUBMITBLOCK::response invoke(SUBMITBLOCK::request&& req, rpc_context context); - GENERATEBLOCKS::response invoke(GENERATEBLOCKS::request&& req, rpc_context context); GET_LAST_BLOCK_HEADER::response invoke(GET_LAST_BLOCK_HEADER::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HASH::response invoke(GET_BLOCK_HEADER_BY_HASH::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index b8da3f47d5e..fd1a19f7b58 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -269,54 +269,6 @@ KV_SERIALIZE_MAP_CODE_END() // } -KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKTEMPLATE::request) - KV_SERIALIZE(reserve_size) - KV_SERIALIZE(wallet_address) - KV_SERIALIZE(prev_block) - KV_SERIALIZE(extra_nonce) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GETBLOCKTEMPLATE::response) - KV_SERIALIZE(difficulty) - KV_SERIALIZE(height) - KV_SERIALIZE(reserved_offset) - KV_SERIALIZE(expected_reward) - KV_SERIALIZE(prev_hash) - KV_SERIALIZE(blocktemplate_blob) - KV_SERIALIZE(blockhashing_blob) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) - KV_SERIALIZE(seed_hash) - KV_SERIALIZE(next_seed_hash) -KV_SERIALIZE_MAP_CODE_END() - - -bool SUBMITBLOCK::request::load(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) -{ - return epee::serialization::perform_serialize(blob, ps, hparent_section, "blob"); -} -bool SUBMITBLOCK::request::store(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section) -{ - return epee::serialization::perform_serialize(blob, ps, hparent_section, "blob"); -} - - -KV_SERIALIZE_MAP_CODE_BEGIN(GENERATEBLOCKS::request) - KV_SERIALIZE(amount_of_blocks) - KV_SERIALIZE(wallet_address) - KV_SERIALIZE(prev_block) - KV_SERIALIZE_OPT(starting_nonce, (uint32_t)0) -KV_SERIALIZE_MAP_CODE_END() - - -KV_SERIALIZE_MAP_CODE_BEGIN(GENERATEBLOCKS::response) - KV_SERIALIZE(height) - KV_SERIALIZE(blocks) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() - - KV_SERIALIZE_MAP_CODE_BEGIN(block_header_response) KV_SERIALIZE(major_version) KV_SERIALIZE(minor_version) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d93c6f8f126..f8fd6630fa8 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -733,82 +733,6 @@ namespace cryptonote::rpc { } request; // Block hash (string). }; - BELDEX_RPC_DOC_INTROSPECT - // Get a block template on which mining a new block. - struct GETBLOCKTEMPLATE : PUBLIC - { - static constexpr auto names() { return NAMES("get_block_template", "getblocktemplate"); } - - struct request - { - uint64_t reserve_size; // Max 255 bytes - std::string wallet_address; // Address of wallet to receive coinbase transactions if block is successfully mined. - std::string prev_block; - std::string extra_nonce; - - KV_MAP_SERIALIZABLE - }; - - struct response - { - uint64_t difficulty; // Difficulty of next block. - uint64_t height; // Height on which to mine. - uint64_t reserved_offset; // Reserved offset. - uint64_t expected_reward; // Coinbase reward expected to be received if block is successfully mined. - std::string prev_hash; // Hash of the most recent block on which to mine the next block. - std::string seed_hash; // RandomX current seed hash - std::string next_seed_hash; // RandomX upcoming seed hash - blobdata blocktemplate_blob; // Blob on which to try to mine a new block. - blobdata blockhashing_blob; // Blob on which to try to find a valid nonce. - std::string status; // General RPC error code. "OK" means everything looks good. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; - }; - - BELDEX_RPC_DOC_INTROSPECT - // Submit a mined block to the network. - struct SUBMITBLOCK : PUBLIC - { - static constexpr auto names() { return NAMES("submit_block", "submitblock"); } - - struct request { - std::vector blob; // Block blob data - array containing exactly one block blob string which has been mined. See get_block_template to get a blob on which to mine. - - // epee serialization; this is a bit hacky because epee serialization makes things hacky. - bool load(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section = nullptr); - bool store(epee::serialization::portable_storage& ps, epee::serialization::section* hparent_section = nullptr); - }; - struct response : STATUS {}; - }; - - BELDEX_RPC_DOC_INTROSPECT - // Developer only. - struct GENERATEBLOCKS : RPC_COMMAND - { - static constexpr auto names() { return NAMES("generateblocks"); } - - struct request - { - uint64_t amount_of_blocks; - std::string wallet_address; - std::string prev_block; - uint32_t starting_nonce; - - KV_MAP_SERIALIZABLE - }; - - struct response - { - uint64_t height; - std::vector blocks; - std::string status; // General RPC error code. "OK" means everything looks good. - - KV_MAP_SERIALIZABLE - }; - }; - BELDEX_RPC_DOC_INTROSPECT struct block_header_response { @@ -2577,9 +2501,6 @@ namespace cryptonote::rpc { >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, - GETBLOCKTEMPLATE, - SUBMITBLOCK, - GENERATEBLOCKS, GET_LAST_BLOCK_HEADER, GET_BLOCK_HEADER_BY_HASH, GET_BLOCK_HEADER_BY_HEIGHT, From 42609b2e8cb75609a38e9443db150b745a3e4167 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 15:35:20 +0530 Subject: [PATCH 063/182] RPC: +peerlist, -publicnodes Updates GET_PEER_LIST to new RPC format --- .../epee/include/epee/net/net_utils_base.h | 3 + src/daemon/rpc_command_executor.cpp | 87 ++++++------ src/rpc/core_rpc_server.cpp | 128 +++--------------- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 4 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 51 +++---- src/rpc/core_rpc_server_commands_defs.h | 105 ++++---------- 8 files changed, 115 insertions(+), 268 deletions(-) diff --git a/contrib/epee/include/epee/net/net_utils_base.h b/contrib/epee/include/epee/net/net_utils_base.h index 67145a9e004..c1a10900e17 100755 --- a/contrib/epee/include/epee/net/net_utils_base.h +++ b/contrib/epee/include/epee/net/net_utils_base.h @@ -225,6 +225,7 @@ namespace net_utils virtual std::string str() const = 0; virtual std::string host_str() const = 0; + virtual uint16_t port() const = 0; virtual bool is_loopback() const = 0; virtual bool is_local() const = 0; virtual address_type get_type_id() const = 0; @@ -255,6 +256,7 @@ namespace net_utils virtual std::string str() const override { return value.str(); } virtual std::string host_str() const override { return value.host_str(); } + virtual uint16_t port() const override { return value.port(); } virtual bool is_loopback() const override { return value.is_loopback(); } virtual bool is_local() const override { return value.is_local(); } virtual address_type get_type_id() const override { return value.get_type_id(); } @@ -303,6 +305,7 @@ namespace net_utils bool is_same_host(const network_address &other) const; std::string str() const { return self ? self->str() : ""; } std::string host_str() const { return self ? self->host_str() : ""; } + uint16_t port() const { return self ? self->port() : 0; } bool is_loopback() const { return self ? self->is_loopback() : false; } bool is_local() const { return self ? self->is_local() : false; } address_type get_type_id() const { return self ? self->get_type_id() : address_type::invalid; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index f8f3755e47e..5a07465b766 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -131,24 +131,6 @@ namespace { return input_line_result::yes; } - void print_peer(std::string const & prefix, GET_PEER_LIST::peer const & peer, bool pruned_only, bool publicrpc_only) - { - if (pruned_only && peer.pruning_seed == 0) - return; - if (publicrpc_only && peer.rpc_port == 0) - return; - - time_t now = std::time(nullptr); - time_t last_seen = static_cast(peer.last_seen); - - std::string elapsed = peer.last_seen == 0 ? "never" : epee::misc_utils::get_time_interval_string(now - last_seen); - std::string id_str = epee::string_tools::pad_string(epee::string_tools::to_string_hex(peer.id), 16, '0', true); - std::string addr_str = peer.host + ":" + std::to_string(peer.port); - std::string rpc_port = peer.rpc_port ? std::to_string(peer.rpc_port) : "-"; - std::string pruning_seed = epee::string_tools::to_string_hex(peer.pruning_seed); - tools::msg_writer() << fmt::format("{:<10} {:<25} {:<25} {:<5} {:<4} {}", prefix, id_str, addr_str, rpc_port, pruning_seed, elapsed); - } - void print_block_header(block_header_response const & header) { tools::success_msg_writer() @@ -198,6 +180,34 @@ namespace { return get_human_time_ago(std::chrono::seconds{now - t}, abbreviate); } + bool print_peer(std::string_view prefix, const json& peer, bool pruned_only, bool publicrpc_only) + { + auto pruning_seed = peer.value("pruning_seed", 0); + if (pruned_only && pruning_seed == 0) + return false; + auto rpc_port = peer.value("rpc_port", 0); + if (publicrpc_only && rpc_port == 0) + return false; + + time_t now = std::time(nullptr); + time_t last_seen = peer.value("last_seen", 0); + + tools::msg_writer() << fmt::format("{:<10} {:016x} {:<30} {:<5} {:<4x} {}", + prefix, + peer["id"].get(), + fmt::format("{}:{}", peer["host"].get(), peer["ip"].get()), + rpc_port == 0 ? "-" : tools::int_to_string(rpc_port), + pruning_seed, + last_seen == 0 ? "never" : get_human_time_ago(last_seen, now)); + return true; + } + + template + void print_peers(std::string_view prefix, const json& peers, size_t& limit, Args&&... args) { + for (auto it = peers.begin(); it != peers.end() && limit > 0; it++) + if (print_peer(prefix, *it, std::forward(args)...)) + limit--; + } } rpc_command_executor::rpc_command_executor( @@ -353,44 +363,35 @@ bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, uint64_ } bool rpc_command_executor::print_peer_list(bool white, bool gray, size_t limit, bool pruned_only, bool publicrpc_only) { - GET_PEER_LIST::response res{}; - - if (!invoke({}, res, "Couldn't retrieve peer list")) + auto maybe_pl = try_running([this] { return invoke(); }, "Failed to retrieve peer list"); + if (!maybe_pl) return false; + auto& pl = *maybe_pl; if (white) - { - auto peer = res.white_list.cbegin(); - const auto end = limit ? peer + std::min(limit, res.white_list.size()) : res.white_list.cend(); - for (; peer != end; ++peer) - { - print_peer("white", *peer, pruned_only, publicrpc_only); - } - } - + print_peers("white", pl["white_list"], limit, pruned_only, publicrpc_only); if (gray) - { - auto peer = res.gray_list.cbegin(); - const auto end = limit ? peer + std::min(limit, res.gray_list.size()) : res.gray_list.cend(); - for (; peer != end; ++peer) - { - print_peer("gray", *peer, pruned_only, publicrpc_only); - } - } + print_peers("gray", pl["gray_list"], limit, pruned_only, publicrpc_only); return true; } bool rpc_command_executor::print_peer_list_stats() { - GET_PEER_LIST::response res{}; + auto maybe_info = try_running([this] { return invoke(); }, "Failed to retrieve node info"); + if (!maybe_info) + return false; + auto& info = *maybe_info; - if (!invoke({}, res, "Couldn't retrieve peer list")) + auto wls = info.find("white_peerlist_size"); + auto gls = info.find("grey_peerlist_size"); + if (wls == info.end() || gls == info.end()) { + tools::fail_msg_writer() << "Failed to retrieve whitelist info"; return false; + } tools::msg_writer() - << "White list size: " << res.white_list.size() << "/" << cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT << " (" << res.white_list.size() * 100.0 / cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT << "%)" << std::endl - << "Gray list size: " << res.gray_list.size() << "/" << cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT << " (" << res.gray_list.size() * 100.0 / cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT << "%)"; - + << "White list size: " << wls->get() << "/" << cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT << " (" << wls->get() * 100.0 / cryptonote::p2p::LOCAL_WHITE_PEERLIST_LIMIT << "%)\n" + << "Gray list size: " << gls->get() << "/" << cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT << " (" << gls->get() * 100.0 / cryptonote::p2p::LOCAL_GRAY_PEERLIST_LIMIT << "%)"; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 4f312d395da..0e7ddc82f78 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -259,8 +259,6 @@ namespace cryptonote::rpc { if (address.empty()) m_bootstrap_daemon.reset(); - else if (address == "auto") - m_bootstrap_daemon = std::make_unique([this]{ return get_random_public_node(); }); else m_bootstrap_daemon = std::make_unique(address, credentials); @@ -269,46 +267,6 @@ namespace cryptonote::rpc { return true; } //------------------------------------------------------------------------------------------------------------------------------ - std::optional core_rpc_server::get_random_public_node() - { - GET_PUBLIC_NODES::response response{}; - try - { - GET_PUBLIC_NODES::request request{}; - request.gray = true; - request.white = true; - - rpc_context context = {}; - context.admin = true; - response = invoke(std::move(request), context); - } - catch(const std::exception &e) - { - return std::nullopt; - } - - const auto get_random_node_address = [](const std::vector& public_nodes) -> std::string { - const auto& random_node = public_nodes[crypto::rand_idx(public_nodes.size())]; - const auto address = random_node.host + ":" + std::to_string(random_node.rpc_port); - return address; - }; - - if (!response.white.empty()) - { - return get_random_node_address(response.white); - } - - MDEBUG("No white public node found, checking gray peers"); - - if (!response.gray.empty()) - { - return get_random_node_address(response.gray); - } - - MERROR("Failed to find any suitable public node"); - return std::nullopt; - } - //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::init(const boost::program_options::variables_map& vm) { if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), @@ -1399,81 +1357,35 @@ namespace cryptonote::rpc { } save_bc.response["status"] = STATUS_OK; } + + static nlohmann::json json_peer_info(const nodetool::peerlist_entry& peer) { + auto addr_type = peer.adr.get_type_id(); + nlohmann::json p{ + {"id", peer.id}, + {"host", peer.adr.host_str()}, + {"port", peer.adr.port()}, + {"last_seen", peer.last_seen} + }; + if (peer.pruning_seed) p["pruning_seed"] = peer.pruning_seed; + if (peer.rpc_port) p["rpc_port"] = peer.rpc_port; + return p; + } + //------------------------------------------------------------------------------------------------------------------------------ - GET_PEER_LIST::response core_rpc_server::invoke(GET_PEER_LIST::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_PEER_LIST& pl, rpc_context context) { - GET_PEER_LIST::response res{}; - PERF_TIMER(on_get_peer_list); - std::vector white_list; - std::vector gray_list; + std::vector white_list, gray_list; - if (req.public_only) - { + if (pl.request.public_only) m_p2p.get_public_peerlist(gray_list, white_list); - } else - { m_p2p.get_peerlist(gray_list, white_list); - } - for (auto & entry : white_list) - { - if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) - res.white_list.emplace_back(entry.id, entry.adr.as().ip(), - entry.adr.as().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); - else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) - res.white_list.emplace_back(entry.id, entry.adr.as().host_str(), - entry.adr.as().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); - else - res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); - } - - for (auto & entry : gray_list) - { - if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) - res.gray_list.emplace_back(entry.id, entry.adr.as().ip(), - entry.adr.as().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); - else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) - res.gray_list.emplace_back(entry.id, entry.adr.as().host_str(), - entry.adr.as().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); - else - res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); - } - - res.status = STATUS_OK; - return res; - } - //------------------------------------------------------------------------------------------------------------------------------ - GET_PUBLIC_NODES::response core_rpc_server::invoke(GET_PUBLIC_NODES::request&& req, rpc_context context) - { - PERF_TIMER(on_get_public_nodes); - - GET_PEER_LIST::response peer_list_res = invoke(GET_PEER_LIST::request{}, context); - GET_PUBLIC_NODES::response res{}; - res.status = std::move(peer_list_res.status); + std::transform(white_list.begin(), white_list.end(), std::back_inserter(pl.response["white_list"]), json_peer_info); + std::transform(gray_list.begin(), gray_list.end(), std::back_inserter(pl.response["gray_list"]), json_peer_info); - const auto collect = [](const std::vector &peer_list, std::vector &public_nodes) - { - for (const auto &entry : peer_list) - { - if (entry.rpc_port != 0) - { - public_nodes.emplace_back(entry); - } - } - }; - - if (req.white) - { - collect(peer_list_res.white_list, res.white); - } - if (req.gray) - { - collect(peer_list_res.gray_list, res.gray); - } - - return res; + pl.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ SET_LOG_LEVEL::response core_rpc_server::invoke(SET_LOG_LEVEL::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index eb560f2d1f4..c72f8216814 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -227,6 +227,7 @@ namespace cryptonote::rpc { void invoke(IS_KEY_IMAGE_SPENT& spent, rpc_context context); void invoke(SUBMIT_TRANSACTION& tx, rpc_context context); void invoke(GET_BLOCK_HASH& req, rpc_context context); + void invoke(GET_PEER_LIST& pl, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -240,8 +241,6 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); // FIXME: unconverted JSON RPC endpoints: - GET_PEER_LIST::response invoke(GET_PEER_LIST::request&& req, rpc_context context); - GET_PUBLIC_NODES::response invoke(GET_PUBLIC_NODES::request&& req, rpc_context context); SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); @@ -335,7 +334,6 @@ namespace cryptonote::rpc { //utils uint64_t get_block_reward(const block& blk); - std::optional get_random_public_node(); bool set_bootstrap_daemon(const std::string &address, std::string_view username_password); bool set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password); void fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash, bool get_tx_hashes); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 3d8fc9e75c3..316db46aad2 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -406,4 +406,8 @@ namespace cryptonote::rpc { if (bh.request.heights.size() > bh.MAX_HEIGHTS) throw std::domain_error{"Error: too many block heights requested at once"}; } + + void parse_request(GET_PEER_LIST& pl, rpc_input in) { + get_values(in, "public_only", pl.request.public_only); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 0a85b509d75..0ffba231e1d 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -28,4 +28,5 @@ namespace cryptonote::rpc { void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); void parse_request(GET_BLOCK_HASH& bh, rpc_input in); + void parse_request(GET_PEER_LIST& bh, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index fd1a19f7b58..a34141a9806 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -356,42 +356,25 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::peer) - KV_SERIALIZE(id) - KV_SERIALIZE(host) - KV_SERIALIZE(ip) - KV_SERIALIZE(port) - KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) - KV_SERIALIZE(last_seen) - KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) -KV_SERIALIZE_MAP_CODE_END() - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::request) - KV_SERIALIZE_OPT(public_only, true) -KV_SERIALIZE_MAP_CODE_END() - -KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::response) - KV_SERIALIZE(status) - KV_SERIALIZE(white_list) - KV_SERIALIZE(gray_list) -KV_SERIALIZE_MAP_CODE_END() - -KV_SERIALIZE_MAP_CODE_BEGIN(public_node) - KV_SERIALIZE(host) - KV_SERIALIZE(last_seen) - KV_SERIALIZE(rpc_port) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::peer) +// KV_SERIALIZE(id) +// KV_SERIALIZE(host) +// KV_SERIALIZE(ip) +// KV_SERIALIZE(port) +// KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) +// KV_SERIALIZE(last_seen) +// KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_PUBLIC_NODES::request) - KV_SERIALIZE_OPT(gray, false) - KV_SERIALIZE_OPT(white, true) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::request) +// KV_SERIALIZE_OPT(public_only, true) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_PUBLIC_NODES::response) - KV_SERIALIZE(status) - KV_SERIALIZE(gray) - KV_SERIALIZE(white) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(white_list) +// KV_SERIALIZE(gray_list) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_HASH_RATE::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index f8fd6630fa8..88602ff818d 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -328,7 +328,7 @@ namespace cryptonote::rpc { /// - \p "storage" -- the master node's storage server was unreachable for too long /// - \p "belnet" -- the master node's belnet router was unreachable for too long /// - \p "timecheck" -- the master node's beldexd was not reachable for too many recent - /// time synchronization checks. (This generally means oxend's quorumnet port is not + /// time synchronization checks. (This generally means beldexd's quorumnet port is not /// reachable). /// - \p "timesync" -- the master node's clock was too far out of sync /// The list is omitted entirely if there are no reasons at all or if there are no reasons @@ -868,88 +868,34 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get the known peers list. + /// Get the list of current network peers known to this node. + /// + /// Inputs: none + /// + /// Output values (requires a restricted/admin RPC endpoint): + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p white_list list of "whitelist" peers (see below), that is, peers that were recorded + /// reachable the last time this node connected to them. Peers that are unreachable or not + /// synchronized with the network are moved to the graylist. + /// - \p gray_list list of peers (see below) that this node knows of but has not (recently) tried + /// to connect to. + /// + /// Each peer list is an array of dicts containing the following fields: + /// - \p id a unique integer locally identifying the peer + /// - \p host the peer's IP address (as a string) + /// - \p port the port on which the peer is reachable + /// - \p last_seen unix timestamp when this node last connected to the peer. Will be omitted if + /// never connected (e.g. for a peer we received from another node but haven't yet tried). struct GET_PEER_LIST : LEGACY { static constexpr auto names() { return NAMES("get_peer_list"); } - struct request - { - bool public_only; - KV_MAP_SERIALIZABLE - }; - - struct peer - { - uint64_t id; // Peer id. - std::string host; // IP address in string format. - uint32_t ip; // IP address in integer format. - uint16_t port; // TCP port the peer is using to connect to beldex network. - uint16_t rpc_port; // RPC port the peer is using - uint64_t last_seen; // Unix time at which the peer has been seen for the last time - uint32_t pruning_seed; // - - peer() = default; - - peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(host), ip(0), port(0), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) - {} - peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) - {} - peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) - {} - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - std::vector white_list; // Array of online peer structure. - std::vector gray_list; // Array of offline peer structure. - - KV_MAP_SERIALIZABLE - }; - }; - - BELDEX_RPC_DOC_INTROSPECT - struct public_node - { - std::string host; - uint64_t last_seen; - uint16_t rpc_port; - - public_node() = default; - public_node(const GET_PEER_LIST::peer &peer) : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port) {} - - KV_MAP_SERIALIZABLE - }; - - BELDEX_RPC_DOC_INTROSPECT - // Query the daemon's peerlist and retrieve peers who have set their public rpc port. - struct GET_PUBLIC_NODES : PUBLIC - { - static constexpr auto names() { return NAMES("get_public_nodes"); } - - struct request - { - bool gray; // Get peers that have recently gone offline. - bool white; // Get peers that are online - - KV_MAP_SERIALIZABLE - }; - - struct response + struct request_parameters { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - std::vector gray; // Graylist peers - std::vector white; // Whitelist peers + bool public_only = false; // Hidden option: can be set to false to also include non-public-zone peers (Tor, I2P), but since Beldex currently only really exists in public zones, we don't put this in the RPC docs. + } request; - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT @@ -2497,7 +2443,8 @@ namespace cryptonote::rpc { GET_MASTER_NODES, GET_MASTER_NODE_STATUS, SUBMIT_TRANSACTION, - GET_BLOCK_HASH + GET_BLOCK_HASH, + GET_PEER_LIST >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2505,8 +2452,6 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HASH, GET_BLOCK_HEADER_BY_HEIGHT, GET_BLOCK, - GET_PEER_LIST, - GET_PUBLIC_NODES, SET_LOG_LEVEL, SET_LOG_CATEGORIES, GET_BLOCK_HEADERS_RANGE, From 72a155298c39c3ea8243844e766a5aab9cdffae7 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 17:34:25 +0530 Subject: [PATCH 064/182] Remove p2p rpc_port; p2p code clean-ups --- src/cryptonote_basic/connection_context.h | 1 - src/cryptonote_core/blockchain.cpp | 1 + src/cryptonote_core/uptime_proof.cpp | 1 + .../cryptonote_protocol_defs.h | 1 - .../cryptonote_protocol_handler.h | 5 +- .../cryptonote_protocol_handler.inl | 6 +- src/daemon/command_parser_executor.cpp | 38 ++-- src/daemon/command_server.cpp | 14 +- src/daemon/rpc_command_executor.cpp | 27 +-- src/daemon/rpc_command_executor.h | 2 +- src/debug_utilities/object_sizes.cpp | 2 - src/p2p/CMakeLists.txt | 1 + src/p2p/net_node.cpp | 4 +- src/p2p/net_node.h | 14 +- src/p2p/net_node.inl | 25 +-- src/p2p/net_peerlist.h | 9 +- src/p2p/net_peerlist_boost_serialization.h | 12 +- src/p2p/p2p_protocol_defs.cpp | 97 ++++++++++ src/p2p/p2p_protocol_defs.h | 170 ++++-------------- src/rpc/core_rpc_server.cpp | 7 +- 20 files changed, 202 insertions(+), 235 deletions(-) create mode 100644 src/p2p/p2p_protocol_defs.cpp diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 3a2d97fade5..2c9480b4334 100755 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -62,7 +62,6 @@ namespace cryptonote epee::copyable_atomic m_callback_request_count{0}; //in debug purpose: problem with double callback rise crypto::hash m_last_known_hash{crypto::null_hash}; uint32_t m_pruning_seed{0}; - uint16_t m_rpc_port{0}; bool m_anchor{false}; //size_t m_score{0}; TODO: add score calculations }; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index fac08606077..65d8b32977c 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -50,6 +50,7 @@ #include "cryptonote_config.h" #include "cryptonote_basic/miner.h" #include "epee/int-util.h" +#include "epee/time_helper.h" #include "epee/string_tools.h" #include "common/threadpool.h" #include "common/boost_serialization_helper.h" diff --git a/src/cryptonote_core/uptime_proof.cpp b/src/cryptonote_core/uptime_proof.cpp index c75a613476c..08c57c4481c 100644 --- a/src/cryptonote_core/uptime_proof.cpp +++ b/src/cryptonote_core/uptime_proof.cpp @@ -1,5 +1,6 @@ #include "uptime_proof.h" #include "common/string_util.h" +#include "epee/string_tools.h" #include "version.h" extern "C" diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 436985f9249..1e733cd40f4 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -63,7 +63,6 @@ namespace cryptonote std::string host; std::string ip; std::string port; - uint16_t rpc_port; std::string peer_id; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 0f92c4e1e1f..03318c5cd6c 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -62,9 +62,8 @@ namespace cryptonote class t_cryptonote_protocol_handler: public i_cryptonote_protocol { public: - typedef cryptonote_connection_context connection_context; - typedef t_cryptonote_protocol_handler cryptonote_protocol_handler; - typedef CORE_SYNC_DATA payload_type; + using connection_context = cryptonote_connection_context; + using cryptonote_protocol_handler = t_cryptonote_protocol_handler; t_cryptonote_protocol_handler(t_core& rcore, bool offline = false); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 8c6cba0e383..2e8b16d11d4 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -229,7 +229,7 @@ namespace cryptonote seconds_f connection_time{now - cntxt.m_started}; ss << std::setw(30) << std::left << std::string(cntxt.m_is_income ? " [INC]":"[OUT]") + cntxt.m_remote_address.str() - << std::setw(20) << nodetool::peerid_to_string(peer_id) + << std::setw(20) << fmt::format("{:016x}", peer_id) << std::setw(30) << std::to_string(cntxt.m_recv_cnt) + "(" + std::to_string(tools::to_seconds(now - cntxt.m_last_recv)) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(tools::to_seconds(now - cntxt.m_last_send)) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) @@ -286,9 +286,7 @@ namespace cryptonote cnx.ip = cnx.host; cnx.port = std::to_string(cntxt.m_remote_address.as().port()); } - cnx.rpc_port = cntxt.m_rpc_port; - - cnx.peer_id = nodetool::peerid_to_string(peer_id); + cnx.peer_id = fmt::format("{:016x}", peer_id); cnx.live_time = std::chrono::duration_cast(now - cntxt.m_started); cnx.recv_idle_time = std::chrono::duration_cast(now - std::max(cntxt.m_started, cntxt.m_last_recv)); diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 991ecedb6fc..39c924fbf7e 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -32,6 +32,7 @@ #include "common/command_line.h" #include "common/hex.h" #include "common/scoped_message_writer.h" +#include "common/string_util.h" #include "version.h" #include "daemon/command_parser_executor.h" #include "rpc/core_rpc_server_commands_defs.h" @@ -120,44 +121,29 @@ bool command_parser_executor::print_mn_state_changes(const std::vector& args) { - if (args.size() > 3) - { - std::cout << "use: print_pl [white] [gray] [] [pruned] [publicrpc]" << std::endl; - return true; - } - bool white = false; bool gray = false; bool pruned = false; - bool publicrpc = false; size_t limit = 0; - for (size_t i = 0; i < args.size(); ++i) + for (const auto& arg : args) { - if (args[i] == "white") - { + if (arg == "white") white = true; - } - else if (args[i] == "gray") - { + else if (arg == "gray") gray = true; - } - else if (args[i] == "pruned") - { + else if (arg == "pruned") pruned = true; - } - else if (args[i] == "publicrpc") - { - publicrpc = true; - } - else if (!epee::string_tools::get_xtype_from_string(limit, args[i])) - { - std::cout << "unexpected argument: " << args[i] << std::endl; + else if (tools::parse_int(arg, limit)) + /*limit already set*/; + else { + std::cout << "Unexpected argument: " << arg << "\n"; return true; } } - const bool print_both = !white && !gray; - return m_executor.print_peer_list(white | print_both, gray | print_both, limit, pruned, publicrpc); + if (!white && !gray) + white = gray = true; + return m_executor.print_peer_list(white, gray, limit, pruned); } bool command_parser_executor::print_peer_list_stats(const std::vector& args) diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 3a7706d22fa..ae3b3a5bd3b 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -60,7 +60,7 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) m_command_lookup.set_handler( "print_pl" , [this](const auto &x) { return m_parser.print_peer_list(x); } - , "print_pl [white] [gray] [pruned] [publicrpc] []" + , "print_pl [white] [gray] [pruned] []" , "Print the current peer list." ); m_command_lookup.set_handler( @@ -141,13 +141,8 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) m_command_lookup.set_handler( "start_mining" , [this](const auto &x) { return m_parser.start_mining(x); } -#if defined NDEBUG - , "start_mining [threads=(|auto)" - , "Start mining for specified address. Defaults to 1 thread; use \"auto\" to autodetect optimal number of threads." -#else - , "start_mining [threads=(|auto) [num_blocks=]" - , "Start mining for specified address. Defaults to 1 thread; use \"auto\" to autodetect optimal number of threads. When num_blocks is set, continue mining until the (current_height + num_blocks) is met, irrespective of if this Daemon found those block(s) or not." -#endif + , "start_mining [threads=N] [num_blocks=B]" + , "Start mining for specified address, primarily for debug and testing purposes as Oxen is proof-of-stake. Defaults to 1 thread. When num_blocks is set, continue mining until the blockchain reaches height (current + B)." ); m_command_lookup.set_handler( "stop_mining" @@ -339,8 +334,7 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) "set_bootstrap_daemon" , [this](const auto &x) { return m_parser.set_bootstrap_daemon(x); } , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])" - , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n" - "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching" + , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." ); m_command_lookup.set_handler( "flush_cache" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 5a07465b766..f250e5ef034 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -185,25 +185,23 @@ namespace { auto pruning_seed = peer.value("pruning_seed", 0); if (pruned_only && pruning_seed == 0) return false; - auto rpc_port = peer.value("rpc_port", 0); - if (publicrpc_only && rpc_port == 0) - return false; time_t now = std::time(nullptr); time_t last_seen = peer.value("last_seen", 0); - tools::msg_writer() << fmt::format("{:<10} {:016x} {:<30} {:<5} {:<4x} {}", + tools::msg_writer() << fmt::format("{:<10} {:016x} {:<30} {}", prefix, peer["id"].get(), - fmt::format("{}:{}", peer["host"].get(), peer["ip"].get()), - rpc_port == 0 ? "-" : tools::int_to_string(rpc_port), - pruning_seed, + fmt::format("{}:{}", peer["host"].get(), peer["port"].get()), last_seen == 0 ? "never" : get_human_time_ago(last_seen, now)); return true; } template void print_peers(std::string_view prefix, const json& peers, size_t& limit, Args&&... args) { + if (limit > 0) + tools::msg_writer() << fmt::format("{:<10} {:<16} {:<30} {}", "Type", "Peer id", "Remote address", "Last seen"); + for (auto it = peers.begin(); it != peers.end() && limit > 0; it++) if (print_peer(prefix, *it, std::forward(args)...)) limit--; @@ -362,16 +360,21 @@ bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, uint64_ return true; } -bool rpc_command_executor::print_peer_list(bool white, bool gray, size_t limit, bool pruned_only, bool publicrpc_only) { +bool rpc_command_executor::print_peer_list(bool white, bool gray, size_t limit, bool pruned_only) { auto maybe_pl = try_running([this] { return invoke(); }, "Failed to retrieve peer list"); if (!maybe_pl) return false; auto& pl = *maybe_pl; - if (white) - print_peers("white", pl["white_list"], limit, pruned_only, publicrpc_only); - if (gray) - print_peers("gray", pl["gray_list"], limit, pruned_only, publicrpc_only); + if (!limit) limit = std::numeric_limits::max(); + if (white) { + tools::success_msg_writer() << pl["white_list"].size() << " whitelist peers:"; + print_peers("white", pl["white_list"], limit, pruned_only); + } + if (gray) { + tools::success_msg_writer() << pl["gray_list"].size() << " graylist peers:"; + print_peers("gray", pl["gray_list"], limit, pruned_only); + } return true; } diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index b9d5c14e952..268fc2e7d61 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -148,7 +148,7 @@ class rpc_command_executor final { bool print_mn_state_changes(uint64_t start_height, uint64_t end_height); - bool print_peer_list(bool white = true, bool gray = true, size_t limit = 0, bool pruned_only = false, bool publicrpc_only = false); + bool print_peer_list(bool white = true, bool gray = true, size_t limit = 0, bool pruned_only = false); bool print_peer_list_stats(); diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp index c2d9652d5fc..d41cb4e535f 100755 --- a/src/debug_utilities/object_sizes.cpp +++ b/src/debug_utilities/object_sizes.cpp @@ -95,8 +95,6 @@ int main(int argc, char* argv[]) SL(nodetool::anchor_peerlist_entry); SL(nodetool::node_server>); SL(nodetool::p2p_connection_context_t::connection_context>); - SL(nodetool::network_address_old); - SL(nodetool::peerlist_entry_base); SL(nodetool::network_config); SL(nodetool::basic_node_data); diff --git a/src/p2p/CMakeLists.txt b/src/p2p/CMakeLists.txt index 3be1386f086..53c39a30d26 100755 --- a/src/p2p/CMakeLists.txt +++ b/src/p2p/CMakeLists.txt @@ -28,6 +28,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. add_library(p2p + p2p_protocol_defs.cpp net_node.cpp net_node.inl net_peerlist.cpp diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index 6c55f1fc72e..9ee975526ed 100755 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -294,8 +294,8 @@ namespace nodetool { switch (command) { - case nodetool::COMMAND_HANDSHAKE_T::ID: - case nodetool::COMMAND_TIMED_SYNC_T::ID: + case nodetool::COMMAND_HANDSHAKE::ID: + case nodetool::COMMAND_TIMED_SYNC::ID: case cryptonote::NOTIFY_NEW_TRANSACTIONS::ID: return false; default: diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index b2c621640f5..01945ada6f0 100755 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -125,12 +125,9 @@ namespace nodetool struct by_peer_id{}; struct by_addr{}; - typedef p2p_connection_context_t p2p_connection_context; + using p2p_connection_context = p2p_connection_context_t; - typedef COMMAND_HANDSHAKE_T COMMAND_HANDSHAKE; - typedef COMMAND_TIMED_SYNC_T COMMAND_TIMED_SYNC; - - typedef epee::net_utils::boosted_tcp_server> net_server; + using net_server = epee::net_utils::boosted_tcp_server>; struct network_zone; using connect_func = std::optional(network_zone&, epee::net_utils::network_address const&); @@ -216,7 +213,6 @@ namespace nodetool node_server(t_payload_net_handler& payload_handler) : m_payload_handler(payload_handler), m_external_port(0), - m_rpc_port(0), m_allow_local_ip(false), m_hide_my_port(false), m_offline(false), @@ -385,11 +381,6 @@ namespace nodetool public: - void set_rpc_port(uint16_t rpc_port) - { - m_rpc_port = rpc_port; - } - void reset_peer_handshake_timer() { m_peer_handshake_idle_maker_interval.reset(); @@ -403,7 +394,6 @@ namespace nodetool uint32_t m_listening_port; uint32_t m_listening_port_ipv6; uint32_t m_external_port; - uint16_t m_rpc_port; bool m_allow_local_ip; bool m_hide_my_port; bool m_offline; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index a4d08bb1766..f6254badf67 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -44,6 +44,7 @@ #include "cryptonote_config.h" #include "version.h" #include "epee/string_tools.h" +#include "epee/time_helper.h" #include "common/file.h" #include "common/pruning.h" #include "net/error.h" @@ -56,6 +57,7 @@ #include "cryptonote_core/cryptonote_core.h" #include "net/parse.h" +#include #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "net.p2p" @@ -913,9 +915,8 @@ namespace nodetool } pi = context.peer_id = rsp.node_data.peer_id; - context.m_rpc_port = rsp.node_data.rpc_port; network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone()); - zone.m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port); + zone.m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed); // move if(rsp.node_data.peer_id == zone.m_config.m_peer_id) @@ -975,7 +976,7 @@ namespace nodetool add_host_fail(context.m_remote_address); } if(!context.m_is_income) - m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port); + m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed); if (!m_payload_handler.process_payload_sync_data(std::move(rsp.payload_data), context, false)) { m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id ); @@ -1147,7 +1148,6 @@ namespace nodetool time(&last_seen); pe_local.last_seen = static_cast(last_seen); pe_local.pruning_seed = con->m_pruning_seed; - pe_local.rpc_port = con->m_rpc_port; zone.m_peerlist.append_with_peer_white(pe_local); //update last seen and push it to peerlist manager @@ -1257,6 +1257,12 @@ namespace nodetool return false; } + + static std::string peerid_to_string(peerid_type peer_id) + { + return fmt::format("{:016x}", peer_id); + } + //----------------------------------------------------------------------------------- template bool node_server::make_new_connection_from_peerlist(network_zone& zone, bool use_white_list) @@ -1753,8 +1759,6 @@ namespace nodetool const epee::net_utils::ipv4_network_address &ipv4 = na.as(); if (ipv4.ip() == 0) ignore = true; - else if (ipv4.port() == be.rpc_port) - ignore = true; } if (be.pruning_seed && (be.pruning_seed < tools::make_pruning_seed(1, cryptonote::PRUNING_LOG_STRIPES) || be.pruning_seed > tools::make_pruning_seed(1ul << cryptonote::PRUNING_LOG_STRIPES, cryptonote::PRUNING_LOG_STRIPES))) ignore = true; @@ -1805,7 +1809,6 @@ namespace nodetool node_data.my_port = m_external_port ? m_external_port : m_listening_port; else node_data.my_port = 0; - node_data.rpc_port = zone.m_can_pingback ? m_rpc_port : 0; node_data.network_id = m_network_id; return true; } @@ -2013,7 +2016,7 @@ namespace nodetool } network_zone& zone = m_network_zones.at(address.get_zone()); - if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) + if(rsp.status != COMMAND_PING::OK_RESPONSE || pr != rsp.peer_id) { LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << peerid_to_string(rsp.peer_id)); zone.m_net_server.get_config_object().close(ping_context.m_connection_id); @@ -2145,7 +2148,6 @@ namespace nodetool //associate peer_id with this connection context.peer_id = arg.node_data.peer_id; context.m_in_timedsync = false; - context.m_rpc_port = arg.node_data.rpc_port; if(arg.node_data.my_port && zone.m_can_pingback) { @@ -2173,7 +2175,6 @@ namespace nodetool pe.last_seen = static_cast(last_seen); pe.id = peer_id_l; pe.pruning_seed = context.m_pruning_seed; - pe.rpc_port = context.m_rpc_port; this->m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.append_with_peer_white(pe); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); }); @@ -2198,7 +2199,7 @@ namespace nodetool int node_server::handle_ping(int command, COMMAND_PING::request& arg, COMMAND_PING::response& rsp, p2p_connection_context& context) { LOG_PRINT_CC_L0(context,"COMMAND_PING received"); - rsp.status = PING_OK_RESPONSE_STATUS_TEXT; + rsp.status = COMMAND_PING::OK_RESPONSE;; rsp.peer_id = m_network_zones.at(context.m_remote_address.get_zone()).m_config.m_peer_id; return 1; } @@ -2494,7 +2495,7 @@ namespace nodetool } else { - zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port); + zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed); LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_to_string(pe.id)); } } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 54f311e8b52..86e7405a36b 100755 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -113,7 +113,7 @@ namespace nodetool bool append_with_peer_white(const peerlist_entry& pr); bool append_with_peer_gray(const peerlist_entry& pr); bool append_with_peer_anchor(const anchor_peerlist_entry& ple); - bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port); + bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed); bool is_host_allowed(const epee::net_utils::network_address &address); bool get_random_gray_peer(peerlist_entry& pe); bool remove_from_peer_gray(const peerlist_entry& pe); @@ -317,7 +317,7 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port) + bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed) { TRY_ENTRY(); std::lock_guard lock{m_peerlist_lock}; @@ -327,7 +327,6 @@ namespace nodetool ple.id = peer; ple.last_seen = time(NULL); ple.pruning_seed = pruning_seed; - ple.rpc_port = rpc_port; return append_with_peer_white(ple); CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false); } @@ -353,8 +352,6 @@ namespace nodetool peerlist_entry new_ple = ple; if (by_addr_it_wt->pruning_seed && ple.pruning_seed == 0) // guard against older nodes not passing pruning info around new_ple.pruning_seed = by_addr_it_wt->pruning_seed; - if (by_addr_it_wt->rpc_port && ple.rpc_port == 0) // guard against older nodes not passing RPC port around - new_ple.rpc_port = by_addr_it_wt->rpc_port; new_ple.last_seen = by_addr_it_wt->last_seen; // do not overwrite the last seen timestamp, incoming peer list are untrusted m_peers_white.replace(by_addr_it_wt, new_ple); } @@ -394,8 +391,6 @@ namespace nodetool peerlist_entry new_ple = ple; if (by_addr_it_gr->pruning_seed && ple.pruning_seed == 0) // guard against older nodes not passing pruning info around new_ple.pruning_seed = by_addr_it_gr->pruning_seed; - if (by_addr_it_gr->rpc_port && ple.rpc_port == 0) // guard against older nodes not passing RPC port around - new_ple.rpc_port = by_addr_it_gr->rpc_port; new_ple.last_seen = by_addr_it_gr->last_seen; // do not overwrite the last seen timestamp, incoming peer list are untrusted m_peers_gray.replace(by_addr_it_gr, new_ple); } diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 02f742961fc..6c4ddc55ad1 100755 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -39,7 +39,7 @@ #include "p2p/p2p_protocol_defs.h" #include "common/pruning.h" -BOOST_CLASS_VERSION(nodetool::peerlist_entry, 2) +BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3) namespace boost { @@ -231,11 +231,15 @@ namespace boost } if (ver < 2) { - if (!typename Archive::is_saving()) - pl.rpc_port = 0; return; } - a & pl.rpc_port; + + if (ver < 3) + { + // Unused, but don't break + uint16_t rpc_port = 0; + a & rpc_port; + } } template diff --git a/src/p2p/p2p_protocol_defs.cpp b/src/p2p/p2p_protocol_defs.cpp new file mode 100644 index 00000000000..22d0355fe49 --- /dev/null +++ b/src/p2p/p2p_protocol_defs.cpp @@ -0,0 +1,97 @@ +#include "p2p_protocol_defs.h" +#include "epee/misc_language.h" +#include "epee/string_tools.h" +#include "epee/time_helper.h" +#include "net/tor_address.h" // needed for serialization +#include "net/i2p_address.h" // needed for serialization +#include + +namespace nodetool { + + std::string print_peerlist_to_string(const std::vector& pl) + { + time_t now = time(nullptr); + std::string result; + for (const auto& pe : pl) { + result += fmt::format("{:016x}\t{}\tpruning seed {}\tlast_seen {}", + pe.id, pe.adr.str(), pe.pruning_seed, + (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now - pe.last_seen))); + } + return result; + } + + +KV_SERIALIZE_MAP_CODE_BEGIN(peerlist_entry) + KV_SERIALIZE(adr) + KV_SERIALIZE(id) + KV_SERIALIZE_OPT(last_seen, (int64_t)0) + KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) + // rpc_port is unused, but pass it along anyway to avoid breaking the protocol + uint16_t rpc_port = 0; + KV_SERIALIZE_VALUE(rpc_port); +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(anchor_peerlist_entry) + KV_SERIALIZE(adr) + KV_SERIALIZE(id) + KV_SERIALIZE(first_seen) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(connection_entry) + KV_SERIALIZE(adr) + KV_SERIALIZE(id) + KV_SERIALIZE(is_income) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(network_config) + KV_SERIALIZE(max_out_connection_count) + KV_SERIALIZE(max_in_connection_count) + KV_SERIALIZE(handshake_interval) + KV_SERIALIZE(packet_max_size) + KV_SERIALIZE(config_id) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(basic_node_data) + KV_SERIALIZE_VAL_POD_AS_BLOB(network_id) + KV_SERIALIZE(peer_id) + KV_SERIALIZE(my_port) + // Unused, but pass a 0 to avoid breaking the protocol + uint16_t rpc_port = 0; + KV_SERIALIZE_VALUE(rpc_port); +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_HANDSHAKE::request) + KV_SERIALIZE(node_data) + KV_SERIALIZE(payload_data) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_HANDSHAKE::response) + KV_SERIALIZE(node_data) + KV_SERIALIZE(payload_data) + KV_SERIALIZE(local_peerlist_new) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_TIMED_SYNC::request) + KV_SERIALIZE(payload_data) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_TIMED_SYNC::response) + KV_SERIALIZE(payload_data) + KV_SERIALIZE(local_peerlist_new) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_PING::request) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_PING::response) + KV_SERIALIZE(status) + KV_SERIALIZE(peer_id) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_REQUEST_SUPPORT_FLAGS::request) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(COMMAND_REQUEST_SUPPORT_FLAGS::response) + KV_SERIALIZE(support_flags) +KV_SERIALIZE_MAP_CODE_END() +} \ No newline at end of file diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index b38cb86d026..a78dd3c38d5 100755 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -31,121 +31,51 @@ #pragma once #include -#include #include "epee/serialization/keyvalue_serialization.h" #include "epee/net/net_utils_base.h" -#include "net/tor_address.h" // needed for serialization -#include "net/i2p_address.h" // needed for serialization -#include "epee/string_tools.h" -#include "epee/time_helper.h" -#include "cryptonote_config.h" -#include "crypto/crypto.h" +#include "cryptonote_protocol/cryptonote_protocol_defs.h" namespace nodetool { - typedef boost::uuids::uuid uuid; - typedef uint64_t peerid_type; + using boost::uuids::uuid; + using peerid_type = uint64_t; - static inline std::string peerid_to_string(peerid_type peer_id) + #pragma pack (push, 1) + struct peerlist_entry { - std::ostringstream s; - s << std::hex << peer_id; - return epee::string_tools::pad_string(s.str(), 16, '0', true); - } - -#pragma pack (push, 1) - - struct network_address_old - { - uint32_t ip; - uint32_t port; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(ip) - KV_SERIALIZE(port) - END_KV_SERIALIZE_MAP() - }; - - template - struct peerlist_entry_base - { - AddressType adr; + epee::net_utils::network_address adr; peerid_type id; int64_t last_seen; uint32_t pruning_seed; - uint16_t rpc_port; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(adr) - KV_SERIALIZE(id) - KV_SERIALIZE_OPT(last_seen, (int64_t)0) - KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) - KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) - END_KV_SERIALIZE_MAP() + + KV_MAP_SERIALIZABLE }; - typedef peerlist_entry_base peerlist_entry; - template - struct anchor_peerlist_entry_base + struct anchor_peerlist_entry { - AddressType adr; + epee::net_utils::network_address adr; peerid_type id; int64_t first_seen; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(adr) - KV_SERIALIZE(id) - KV_SERIALIZE(first_seen) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; - typedef anchor_peerlist_entry_base anchor_peerlist_entry; - template - struct connection_entry_base + struct connection_entry { - AddressType adr; + epee::net_utils::network_address adr; peerid_type id; bool is_income; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(adr) - KV_SERIALIZE(id) - KV_SERIALIZE(is_income) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; - typedef connection_entry_base connection_entry; -#pragma pack(pop) + #pragma pack(pop) - inline - std::string print_peerlist_to_string(const std::vector& pl) - { - time_t now_time = 0; - time(&now_time); - std::stringstream ss; - ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase; - for(const peerlist_entry& pe: pl) - { - ss << peerid_to_string(pe.id) << "\t" << pe.adr.str() - << " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-") - << " \tpruning seed " << pe.pruning_seed - << " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen)) - << std::endl; - } - return ss.str(); - } + std::string print_peerlist_to_string(const std::vector& pl); struct network_config { - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(max_out_connection_count) - KV_SERIALIZE(max_in_connection_count) - KV_SERIALIZE(handshake_interval) - KV_SERIALIZE(packet_max_size) - KV_SERIALIZE(config_id) - END_KV_SERIALIZE_MAP() - uint32_t max_out_connection_count; uint32_t max_in_connection_count; std::chrono::milliseconds connection_timeout; @@ -154,21 +84,17 @@ namespace nodetool uint32_t packet_max_size; uint32_t config_id; uint32_t send_peerlist_sz; + + KV_MAP_SERIALIZABLE }; struct basic_node_data { - uuid network_id; + uuid network_id; uint32_t my_port; - uint16_t rpc_port; peerid_type peer_id; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_VAL_POD_AS_BLOB(network_id) - KV_SERIALIZE(peer_id) - KV_SERIALIZE(my_port) - KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0)) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; @@ -177,33 +103,25 @@ namespace nodetool /************************************************************************/ /* */ /************************************************************************/ - template - struct COMMAND_HANDSHAKE_T - { - const static int ID = P2P_COMMANDS_POOL_BASE + 1; + struct COMMAND_HANDSHAKE + { + const static int ID = P2P_COMMANDS_POOL_BASE + 1; struct request { basic_node_data node_data; - Payload payload_data; + cryptonote::CORE_SYNC_DATA payload_data; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(node_data) - KV_SERIALIZE(payload_data) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; struct response { basic_node_data node_data; - Payload payload_data; + cryptonote::CORE_SYNC_DATA payload_data; std::vector local_peerlist_new; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(node_data) - KV_SERIALIZE(payload_data) - KV_SERIALIZE(local_peerlist_new) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; }; @@ -211,29 +129,24 @@ namespace nodetool /************************************************************************/ /* */ /************************************************************************/ - template - struct COMMAND_TIMED_SYNC_T + struct COMMAND_TIMED_SYNC { const static int ID = P2P_COMMANDS_POOL_BASE + 2; struct request { - Payload payload_data; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(payload_data) - END_KV_SERIALIZE_MAP() + cryptonote::CORE_SYNC_DATA payload_data; + + KV_MAP_SERIALIZABLE }; struct response { uint64_t local_time; - Payload payload_data; + cryptonote::CORE_SYNC_DATA payload_data; std::vector local_peerlist_new; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(payload_data) - KV_SERIALIZE(local_peerlist_new) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; }; @@ -250,14 +163,13 @@ namespace nodetool */ const static int ID = P2P_COMMANDS_POOL_BASE + 3; -#define PING_OK_RESPONSE_STATUS_TEXT "OK" + static constexpr auto OK_RESPONSE = "OK"sv; struct request { /*actually we don't need to send any real data*/ - BEGIN_KV_SERIALIZE_MAP() - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; struct response @@ -265,10 +177,7 @@ namespace nodetool std::string status; peerid_type peer_id; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - KV_SERIALIZE(peer_id) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; }; @@ -283,17 +192,14 @@ namespace nodetool struct request { - BEGIN_KV_SERIALIZE_MAP() - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; struct response { uint32_t support_flags; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(support_flags) - END_KV_SERIALIZE_MAP() + KV_MAP_SERIALIZABLE }; }; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0e7ddc82f78..eaf7f053d27 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -204,8 +204,7 @@ namespace cryptonote::rpc { const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { "bootstrap-daemon-address" - , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n" - "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching" + , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." , "" }; @@ -1367,7 +1366,6 @@ namespace cryptonote::rpc { {"last_seen", peer.last_seen} }; if (peer.pruning_seed) p["pruning_seed"] = peer.pruning_seed; - if (peer.rpc_port) p["rpc_port"] = peer.rpc_port; return p; } @@ -1858,9 +1856,6 @@ namespace cryptonote::rpc { if (ci.localhost) info["localhost"] = true; if (ci.local_ip) info["local_ip"] = true; if (uint16_t port; tools::parse_int(ci.port, port) && port > 0) info["port"] = port; - // Included for completeness, but undocumented as neither of these are currently actually used - // or support on Beldex: - if (ci.rpc_port > 0) info["rpc_port"] = ci.rpc_port; if (ci.pruning_seed) info["pruning_seed"] = ci.pruning_seed; return info; } From 137576de9b32c58304d426662cd256a18fd5d201 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Mon, 21 Apr 2025 18:35:21 +0530 Subject: [PATCH 065/182] BANNED to new RPC format --- src/daemon/rpc_command_executor.cpp | 12 ++++------ src/rpc/core_rpc_server.cpp | 17 ++++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 ++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 16 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 28 ++++++++++------------ 7 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index f250e5ef034..987ab9da4da 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1223,16 +1223,14 @@ bool rpc_command_executor::unban(const std::string &address) bool rpc_command_executor::banned(const std::string &address) { - BANNED::request req{}; - BANNED::response res{}; + auto maybe_banned = try_running([this, &address] { return invoke(json{{"address", std::move(address)}}); }, "Failed to retrieve ban information"); - req.address = address; - - if (!invoke({address}, res, "Failed to retrieve ban information")) + if (!maybe_banned) return false; + auto& banned_response = *maybe_banned; - if (res.banned) - tools::msg_writer() << address << " is banned for " << res.seconds << " seconds"; + if (banned_response["banned"].get()) + tools::msg_writer() << address << " is banned for " << banned_response["seconds"].get() << " seconds"; else tools::msg_writer() << address << " is not banned"; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index eaf7f053d27..499e9e95b36 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1931,13 +1931,11 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - BANNED::response core_rpc_server::invoke(BANNED::request&& req, rpc_context context) + void core_rpc_server::invoke(BANNED& banned, rpc_context context) { - BANNED::response res{}; - PERF_TIMER(on_banned); - auto na_parsed = net::get_network_address(req.address, 0); + auto na_parsed = net::get_network_address(banned.request.address, 0); if (!na_parsed) throw rpc_error{ERROR_WRONG_PARAM, "Unsupported host type"}; epee::net_utils::network_address na = std::move(*na_parsed); @@ -1945,17 +1943,16 @@ namespace cryptonote::rpc { time_t seconds; if (m_p2p.is_host_blocked(na, &seconds)) { - res.banned = true; - res.seconds = seconds; + banned.response["banned"] = true; + banned.response["seconds"] = seconds; } else { - res.banned = false; - res.seconds = 0; + banned.response["banned"] = false; + banned.response["seconds"] = 0; } - res.status = STATUS_OK; - return res; + banned.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ SETBANS::response core_rpc_server::invoke(SETBANS::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index c72f8216814..489ddc12093 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -228,6 +228,7 @@ namespace cryptonote::rpc { void invoke(SUBMIT_TRANSACTION& tx, rpc_context context); void invoke(GET_BLOCK_HASH& req, rpc_context context); void invoke(GET_PEER_LIST& pl, rpc_context context); + void invoke(BANNED& banned, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -255,7 +256,6 @@ namespace cryptonote::rpc { GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); SETBANS::response invoke(SETBANS::request&& req, rpc_context context); GETBANS::response invoke(GETBANS::request&& req, rpc_context context); - BANNED::response invoke(BANNED::request&& req, rpc_context context); FLUSH_TRANSACTION_POOL::response invoke(FLUSH_TRANSACTION_POOL::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_VERSION::response invoke(GET_VERSION::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 316db46aad2..950bb760d08 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -410,4 +410,8 @@ namespace cryptonote::rpc { void parse_request(GET_PEER_LIST& pl, rpc_input in) { get_values(in, "public_only", pl.request.public_only); } + + void parse_request(BANNED& banned, rpc_input in) { + get_values(in, "address", banned.request.address); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 0ffba231e1d..174f1a030b3 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -29,4 +29,5 @@ namespace cryptonote::rpc { void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); void parse_request(GET_BLOCK_HASH& bh, rpc_input in); void parse_request(GET_PEER_LIST& bh, rpc_input in); + void parse_request(BANNED& banned, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index a34141a9806..e36108d3202 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -593,16 +593,16 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::request) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BANNED::request) - KV_SERIALIZE(address) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BANNED::request) +// KV_SERIALIZE(address) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BANNED::response) - KV_SERIALIZE(status) - KV_SERIALIZE(banned) - KV_SERIALIZE(seconds) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BANNED::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(banned) +// KV_SERIALIZE(seconds) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(FLUSH_TRANSACTION_POOL::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 88602ff818d..6c9415bef17 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1271,27 +1271,23 @@ namespace cryptonote::rpc { struct response : STATUS {}; }; - BELDEX_RPC_DOC_INTROSPECT - // Determine whether a given IP address is banned + /// Determine whether a given IP address is banned + /// + /// Inputs: + /// - \p address The IP address to check. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p banned True if the given address is banned, false otherwise. + /// - \p seconds The number of seconds remaining in the ban. struct BANNED : RPC_COMMAND { static constexpr auto names() { return NAMES("banned"); } - struct request - { + struct request_parameters { std::string address; // The IP address to check - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - bool banned; // True if the given address is banned, false otherwise. - uint32_t seconds; // The number of seconds remaining in the ban. - - KV_MAP_SERIALIZABLE - }; + } request; }; BELDEX_RPC_DOC_INTROSPECT From 589d4a56afd5ee470592c72bd5a0e4c526b3adb0 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Mon, 21 Apr 2025 19:15:31 +0530 Subject: [PATCH 066/182] FLUSH_TRANSACTION_POOL to new rpc format --- src/daemon/rpc_command_executor.cpp | 10 +++++----- src/rpc/core_rpc_server.cpp | 15 ++++++--------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 ++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 6 +++--- src/rpc/core_rpc_server_commands_defs.h | 18 ++++++++++-------- 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 987ab9da4da..03bbec2f611 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1239,14 +1239,14 @@ bool rpc_command_executor::banned(const std::string &address) bool rpc_command_executor::flush_txpool(std::string txid) { - FLUSH_TRANSACTION_POOL::request req{}; - FLUSH_TRANSACTION_POOL::response res{}; - + std::vector txids{}; if (!txid.empty()) - req.txids.push_back(std::move(txid)); + txids.push_back(std::move(txid)); - if (!invoke(std::move(req), res, "Failed to flush tx pool")) + if (!invoke(json{{txids, std::move(txids)}})) { + tools::fail_msg_writer() << "Failed to flush tx pool"; return false; + } tools::success_msg_writer() << "Pool successfully flushed"; return true; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 499e9e95b36..54be4e3da6d 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2001,15 +2001,13 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - FLUSH_TRANSACTION_POOL::response core_rpc_server::invoke(FLUSH_TRANSACTION_POOL::request&& req, rpc_context context) + void core_rpc_server::invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context) { - FLUSH_TRANSACTION_POOL::response res{}; - PERF_TIMER(on_flush_txpool); bool failed = false; std::vector txids; - if (req.txids.empty()) + if (flush_transaction_pool.request.txids.empty()) { std::vector pool_txs; m_core.get_pool().get_transactions(pool_txs); @@ -2020,7 +2018,7 @@ namespace cryptonote::rpc { } else { - for (const auto &txid_hex: req.txids) + for (const auto &txid_hex: flush_transaction_pool.request.txids) { if(!tools::hex_to_type(txid_hex, txids.emplace_back())) { @@ -2031,16 +2029,15 @@ namespace cryptonote::rpc { } if (!m_core.get_blockchain_storage().flush_txes_from_pool(txids)) { - res.status = "Failed to remove one or more tx(es)"; - return res; + flush_transaction_pool.response["status"] = "Failed to remove one or more tx(es)"; + return; } - res.status = failed + flush_transaction_pool.response["status"] = failed ? txids.empty() ? "Failed to parse txid" : "Failed to parse some of the txids" : STATUS_OK; - return res; } //------------------------------------------------------------------------------------------------------------------------------ GET_OUTPUT_HISTOGRAM::response core_rpc_server::invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 489ddc12093..8db8aaecf99 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -229,6 +229,7 @@ namespace cryptonote::rpc { void invoke(GET_BLOCK_HASH& req, rpc_context context); void invoke(GET_PEER_LIST& pl, rpc_context context); void invoke(BANNED& banned, rpc_context context); + void invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -256,7 +257,6 @@ namespace cryptonote::rpc { GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); SETBANS::response invoke(SETBANS::request&& req, rpc_context context); GETBANS::response invoke(GETBANS::request&& req, rpc_context context); - FLUSH_TRANSACTION_POOL::response invoke(FLUSH_TRANSACTION_POOL::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_VERSION::response invoke(GET_VERSION::request&& req, rpc_context context); GET_COINBASE_TX_SUM::response invoke(GET_COINBASE_TX_SUM::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 950bb760d08..7e6463f482a 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -414,4 +414,8 @@ namespace cryptonote::rpc { void parse_request(BANNED& banned, rpc_input in) { get_values(in, "address", banned.request.address); } + + void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in) { + get_values(in, "txids", flush_transaction_pool.request.txids); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 174f1a030b3..7f812e96055 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -30,4 +30,5 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HASH& bh, rpc_input in); void parse_request(GET_PEER_LIST& bh, rpc_input in); void parse_request(BANNED& banned, rpc_input in); + void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index e36108d3202..71d46632e9e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -605,9 +605,9 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(FLUSH_TRANSACTION_POOL::request) - KV_SERIALIZE(txids) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(FLUSH_TRANSACTION_POOL::request) +// KV_SERIALIZE(txids) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 6c9415bef17..783e9376024 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1290,20 +1290,22 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Flush tx ids from transaction pool.. + /// Flush tx ids from transaction pool.. + /// + /// Inputs: + /// - \p txids Optional, list of transactions IDs to flosh from pool (all tx ids flushed if empty) + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. struct FLUSH_TRANSACTION_POOL : RPC_COMMAND { static constexpr auto names() { return NAMES("flush_txpool"); } - struct request + struct request_parameters { std::vector txids; // Optional, list of transactions IDs to flush from pool (all tx ids flushed if empty). - - KV_MAP_SERIALIZABLE - }; - - struct response : STATUS {}; + } request; }; BELDEX_RPC_DOC_INTROSPECT From 2a926594736ef5f01c77565a0f7047bc8ccbadb2 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 21:05:23 +0530 Subject: [PATCH 067/182] set_log_level and set_log_categories to use new RPC --- src/daemon/rpc_command_executor.cpp | 11 ++-- src/rpc/core_rpc_server.cpp | 26 ++++----- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 8 +++ src/rpc/core_rpc_server_command_parser.h | 2 + src/rpc/core_rpc_server_commands_defs.cpp | 20 +++---- src/rpc/core_rpc_server_commands_defs.h | 67 +++++++++++----------- 7 files changed, 71 insertions(+), 67 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 03bbec2f611..2c9b4120ba3 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -781,8 +781,7 @@ bool rpc_command_executor::print_quorum_state(uint64_t start_height, uint64_t en bool rpc_command_executor::set_log_level(int8_t level) { - SET_LOG_LEVEL::response res{}; - if (!invoke({level}, res, "Failed to set log level")) + if (!invoke(json{{"level", level}})) return false; tools::success_msg_writer() << "Log level is now " << std::to_string(level); @@ -791,12 +790,12 @@ bool rpc_command_executor::set_log_level(int8_t level) { } bool rpc_command_executor::set_log_categories(std::string categories) { - SET_LOG_CATEGORIES::response res{}; - - if (!invoke({std::move(categories)}, res, "Failed to set log categories")) + auto maybe_categories = try_running([this, &categories] { return invoke(json{{"categories", std::move(categories)}}); }, "Failed to set log categories"); + if (!maybe_categories) return false; + auto& categories_response = *maybe_categories; - tools::success_msg_writer() << "Log categories are now " << res.categories; + tools::success_msg_writer() << "Log categories are now " << categories_response["categories"].get(); return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 54be4e3da6d..bd7fad7b8f2 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1386,30 +1386,24 @@ namespace cryptonote::rpc { pl.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - SET_LOG_LEVEL::response core_rpc_server::invoke(SET_LOG_LEVEL::request&& req, rpc_context context) + void core_rpc_server::invoke(SET_LOG_LEVEL& set_log_level, rpc_context context) { - SET_LOG_LEVEL::response res{}; - PERF_TIMER(on_set_log_level); - if (req.level < 0 || req.level > 4) + if (set_log_level.request.level < 0 || set_log_level.request.level > 4) { - res.status = "Error: log level not valid"; - return res; + set_log_level.response["status"] = "Error: log level not valid"; + return; } - mlog_set_log_level(req.level); - res.status = STATUS_OK; - return res; + mlog_set_log_level(set_log_level.request.level); + set_log_level.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - SET_LOG_CATEGORIES::response core_rpc_server::invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context) + void core_rpc_server::invoke(SET_LOG_CATEGORIES& set_log_categories, rpc_context context) { - SET_LOG_CATEGORIES::response res{}; - PERF_TIMER(on_set_log_categories); - mlog_set_log(req.categories.c_str()); - res.categories = mlog_get_categories(); - res.status = STATUS_OK; - return res; + mlog_set_log(set_log_categories.request.categories.c_str()); + set_log_categories.response["categories"] = mlog_get_categories(); + set_log_categories.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ GET_TRANSACTION_POOL_HASHES_BIN::response core_rpc_server::invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 8db8aaecf99..b7ce926aa49 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -228,6 +228,8 @@ namespace cryptonote::rpc { void invoke(SUBMIT_TRANSACTION& tx, rpc_context context); void invoke(GET_BLOCK_HASH& req, rpc_context context); void invoke(GET_PEER_LIST& pl, rpc_context context); + void invoke(SET_LOG_LEVEL& set_log_level, rpc_context context); + void invoke(SET_LOG_CATEGORIES& set_log_categories, rpc_context context); void invoke(BANNED& banned, rpc_context context); void invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context); @@ -243,8 +245,6 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); // FIXME: unconverted JSON RPC endpoints: - SET_LOG_LEVEL::response invoke(SET_LOG_LEVEL::request&& req, rpc_context context); - SET_LOG_CATEGORIES::response invoke(SET_LOG_CATEGORIES::request&& req, rpc_context context); SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); OUT_PEERS::response invoke(OUT_PEERS::request&& req, rpc_context context); IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 7e6463f482a..7449d46a495 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -411,6 +411,14 @@ namespace cryptonote::rpc { get_values(in, "public_only", pl.request.public_only); } + void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in) { + get_values(in, "level", set_log_level.request.level); + } + + void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in) { + get_values(in, "categories", set_log_categories.request.categories); + } + void parse_request(BANNED& banned, rpc_input in) { get_values(in, "address", banned.request.address); } diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 7f812e96055..abe0ba9dde1 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -29,6 +29,8 @@ namespace cryptonote::rpc { void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); void parse_request(GET_BLOCK_HASH& bh, rpc_input in); void parse_request(GET_PEER_LIST& bh, rpc_input in); + void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); + void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); void parse_request(BANNED& banned, rpc_input in); void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 71d46632e9e..8c8efa242d5 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -382,20 +382,20 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_LEVEL::request) - KV_SERIALIZE(level) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_LEVEL::request) +// KV_SERIALIZE(level) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_CATEGORIES::request) - KV_SERIALIZE(categories) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_CATEGORIES::request) +// KV_SERIALIZE(categories) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_CATEGORIES::response) - KV_SERIALIZE(status) - KV_SERIALIZE(categories) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_LOG_CATEGORIES::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(categories) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(tx_info) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 783e9376024..8245966e8eb 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -898,55 +898,56 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Set the daemon log level. By default, log level is set to `0`. For more fine-tuned logging - // control set the set_log_categories command instead. + /// Set the daemon log level. By default, log level is set to `0`. For more fine-tuned logging + /// control set the set_log_categories command instead. + /// + /// Inputs: + /// - \p level Daemon log level to set from `0` (less verbose) to `4` (most verbose) + /// + /// Output values (requires a restricted/admin RPC endpoint): + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. struct SET_LOG_LEVEL : LEGACY { static constexpr auto names() { return NAMES("set_log_level"); } - struct request + struct request_parameters { int8_t level; // Daemon log level to set from `0` (less verbose) to `4` (most verbose) + } request; - KV_MAP_SERIALIZABLE - }; - - struct response : STATUS {}; }; - BELDEX_RPC_DOC_INTROSPECT - // Set the daemon log categories. Categories are represented as a comma separated list of `:` (similarly to syslog standard `:`), where: - // Category is one of the following: * (all facilities), default, net, net.http, net.p2p, logging, net.trottle, blockchain.db, blockchain.db.lmdb, bcutil, checkpoints, net.dns, net.dl, - // i18n, perf,stacktrace, updates, account, cn ,difficulty, hardfork, miner, blockchain, txpool, cn.block_queue, net.cn, daemon, debugtools.deserialize, debugtools.objectsizes, device.ledger, - // wallet.gen_multisig, multisig, bulletproofs, ringct, daemon.rpc, wallet.simplewallet, WalletAPI, wallet.ringdb, wallet.wallet2, wallet.rpc, tests.core. - // - // Level is one of the following: FATAL - higher level, ERROR, WARNING, INFO, DEBUG, TRACE. - // Lower level A level automatically includes higher level. By default, categories are set to: - // `*:WARNING,net:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,stacktrace:INFO,logging:INFO,msgwriter:INFO` - // Setting the categories to "" prevent any logs to be outputed. - // - // You can append to the current the log level for updating just one or more categories while - // leaving other log levels unchanged by specifying one or more ":" pairs - // preceded by a "+", for example "+difficulty:DEBUG,net:WARNING". + /// Set the daemon log categories. Categories are represented as a comma separated list of `:` (similarly to syslog standard `:`), where: + /// Category is one of the following: * (all facilities), default, net, net.http, net.p2p, logging, net.trottle, blockchain.db, blockchain.db.lmdb, bcutil, checkpoints, net.dns, net.dl, + /// i18n, perf,stacktrace, updates, account, cn ,difficulty, hardfork, miner, blockchain, txpool, cn.block_queue, net.cn, daemon, debugtools.deserialize, debugtools.objectsizes, device.ledger, + /// wallet.gen_multisig, multisig, bulletproofs, ringct, daemon.rpc, wallet.simplewallet, WalletAPI, wallet.ringdb, wallet.wallet2, wallet.rpc, tests.core. + /// + /// Level is one of the following: FATAL - higher level, ERROR, WARNING, INFO, DEBUG, TRACE. + /// Lower level A level automatically includes higher level. By default, categories are set to: + /// `*:WARNING,net:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,stacktrace:INFO,logging:INFO,msgwriter:INFO` + /// Setting the categories to "" prevent any logs to be outputed. + /// + /// You can append to the current the log level for updating just one or more categories while + /// leaving other log levels unchanged by specifying one or more ":" pairs + /// preceded by a "+", for example "+difficulty:DEBUG,net:WARNING". + /// + /// Inputs: + /// - \p categories Optional, daemon log categores to enable + /// + /// Output values (requires a restricted/admin RPC endpoint): + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p categories Daemon log enabled categories struct SET_LOG_CATEGORIES : LEGACY { static constexpr auto names() { return NAMES("set_log_categories"); } - struct request + struct request_parameters { std::string categories; // Optional, daemon log categories to enable + } request; - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - std::string categories; // Daemon log enabled categories - - KV_MAP_SERIALIZABLE - }; }; From 42c02a0af2a092e5285a2a95546092c569aa71bb Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 21:22:07 +0530 Subject: [PATCH 068/182] make_request template instead of try_running --- .vscode/settings.json | 16 +++++++++++++++- src/daemon/rpc_command_executor.cpp | 10 +++++----- src/daemon/rpc_command_executor.h | 5 +++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e706357dec0..cd968a26133 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -72,6 +72,20 @@ "thread": "cpp", "typeindex": "cpp", "typeinfo": "cpp", - "valarray": "cpp" + "valarray": "cpp", + "__bit_reference": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__node_handle": "cpp", + "__split_buffer": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__verbose_abort": "cpp", + "execution": "cpp", + "ios": "cpp", + "locale": "cpp", + "print": "cpp", + "queue": "cpp", + "stack": "cpp" } } \ No newline at end of file diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2c9b4120ba3..25ee5c78887 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -790,11 +790,11 @@ bool rpc_command_executor::set_log_level(int8_t level) { } bool rpc_command_executor::set_log_categories(std::string categories) { - auto maybe_categories = try_running([this, &categories] { return invoke(json{{"categories", std::move(categories)}}); }, "Failed to set log categories"); - if (!maybe_categories) - return false; - auto& categories_response = *maybe_categories; - + // auto maybe_categories = try_running([this, &categories] { return invoke(json{{"categories", std::move(categories)}}); }, "Failed to set log categories"); + // if (!maybe_categories) + // return false; + // auto& categories_response = *maybe_categories; + auto categories_response = make_request(json{{"categories", std::move(categories)}}); tools::success_msg_writer() << "Log categories are now " << categories_response["categories"].get(); return true; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 268fc2e7d61..8a30b7857a4 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -144,6 +144,11 @@ class rpc_command_executor final { return true; } + template + nlohmann::json make_request(nlohmann::json params) { + return invoke(params)["response"]; + } + bool print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json); bool print_mn_state_changes(uint64_t start_height, uint64_t end_height); From 2ac0d8f8e4e910113cf4c856cf3d66710defdfa0 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 21:42:21 +0530 Subject: [PATCH 069/182] RPC: GET_VERSION and GET_COINBASE_TX_SUM updated --- src/daemon/rpc_command_executor.cpp | 15 ++++--- src/rpc/core_rpc_server.cpp | 27 +++++------ src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 5 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 30 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 52 +++++++++++----------- 7 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 25ee5c78887..0aceabaed7a 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1277,15 +1277,16 @@ bool rpc_command_executor::output_histogram(const std::vector &amounts bool rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t count) { - GET_COINBASE_TX_SUM::response res{}; - if (!invoke({height, count}, res, "Failed to retrieve coinbase info")) - return false; + auto maybe_coinbase = try_running([this, &height, &count] { return invoke(json{{"height", height}, {"count", count}}); }, "Failed to retrieve coinbase info"); + if (!maybe_coinbase) + return false; + auto& coinbase = *maybe_coinbase; tools::msg_writer() << "Sum of coinbase transactions between block heights [" - << height << ", " << (height + count) << ") is " - << cryptonote::print_money(res.emission_amount + res.fee_amount) << " " - << "consisting of " << cryptonote::print_money(res.emission_amount) - << " in emissions, " << cryptonote::print_money(res.fee_amount) << " in fees, and " << cryptonote::print_money(res.burn_amount) << " in burn amount"; + << height << ", " << (height + count) << ") is " + << cryptonote::print_money(coinbase["emission_amount"].get() + coinbase["fee_amount"].get()) << " " + << "consisting of " << cryptonote::print_money(coinbase["emission_amount"]) + << " in emissions, and " << cryptonote::print_money(coinbase["fee_amount"]) << " in fees"; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index bd7fad7b8f2..701bcc23f3c 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2072,17 +2072,15 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_VERSION::response core_rpc_server::invoke(GET_VERSION::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_VERSION& version, rpc_context context) { - GET_VERSION::response res{}; - PERF_TIMER(on_get_version); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + //TODO how replace bootstrap daemon + //if (use_bootstrap_daemon_if_necessary(req, res)) + //return res; - res.version = pack_version(VERSION); - res.status = STATUS_OK; - return res; + version.response["version"] = pack_version(VERSION); + version.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MASTER_NODE_STATUS& mns, rpc_context context) @@ -2115,18 +2113,15 @@ namespace cryptonote::rpc { } } //------------------------------------------------------------------------------------------------------------------------------ - GET_COINBASE_TX_SUM::response core_rpc_server::invoke(GET_COINBASE_TX_SUM::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_context context) { - GET_COINBASE_TX_SUM::response res{}; - PERF_TIMER(on_get_coinbase_tx_sum); - if (auto sums = m_core.get_coinbase_tx_sum(req.height, req.count)) { - std::tie(res.emission_amount, res.fee_amount, res.burn_amount) = *sums; - res.status = STATUS_OK; + if (auto sums = m_core.get_coinbase_tx_sum(get_coinbase_tx_sum.request.height, get_coinbase_tx_sum.request.count)) { + std::tie(get_coinbase_tx_sum.response["emission_amount"], get_coinbase_tx_sum.response["fee_amount"], get_coinbase_tx_sum.response["burn_amount"]) = *sums; + get_coinbase_tx_sum.response["status"] = STATUS_OK; } else { - res.status = STATUS_BUSY; // some other request is already calculating it + get_coinbase_tx_sum.response["status"] = STATUS_BUSY; // some other request is already calculating it } - return res; } //------------------------------------------------------------------------------------------------------------------------------ GET_BASE_FEE_ESTIMATE::response core_rpc_server::invoke(GET_BASE_FEE_ESTIMATE::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b7ce926aa49..44a08ba60a3 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -232,6 +232,8 @@ namespace cryptonote::rpc { void invoke(SET_LOG_CATEGORIES& set_log_categories, rpc_context context); void invoke(BANNED& banned, rpc_context context); void invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context); + void invoke(GET_VERSION& version, rpc_context context); + void invoke(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -258,8 +260,6 @@ namespace cryptonote::rpc { SETBANS::response invoke(SETBANS::request&& req, rpc_context context); GETBANS::response invoke(GETBANS::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); - GET_VERSION::response invoke(GET_VERSION::request&& req, rpc_context context); - GET_COINBASE_TX_SUM::response invoke(GET_COINBASE_TX_SUM::request&& req, rpc_context context); GET_BASE_FEE_ESTIMATE::response invoke(GET_BASE_FEE_ESTIMATE::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 7449d46a495..5fa76298c5a 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -426,4 +426,9 @@ namespace cryptonote::rpc { void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in) { get_values(in, "txids", flush_transaction_pool.request.txids); } + + void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in) { + get_values(in, "height", get_coinbase_tx_sum.request.height); + get_values(in, "count", get_coinbase_tx_sum.request.count); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index abe0ba9dde1..15d7f3884d8 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -33,4 +33,5 @@ namespace cryptonote::rpc { void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); void parse_request(BANNED& banned, rpc_input in); void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); + void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 8c8efa242d5..46398e01648 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -634,25 +634,25 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_VERSION::response) - KV_SERIALIZE(status) - KV_SERIALIZE(version) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_VERSION::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(version) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_COINBASE_TX_SUM::request) - KV_SERIALIZE(height); - KV_SERIALIZE(count); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_COINBASE_TX_SUM::request) +// KV_SERIALIZE(height); +// KV_SERIALIZE(count); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_COINBASE_TX_SUM::response) - KV_SERIALIZE(status) - KV_SERIALIZE(emission_amount) - KV_SERIALIZE(fee_amount) - KV_SERIALIZE(burn_amount) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_COINBASE_TX_SUM::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(emission_amount) +// KV_SERIALIZE(fee_amount) +// KV_SERIALIZE(burn_amount) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 8245966e8eb..78a8a16bea2 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1351,47 +1351,45 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get current RPC protocol version. - struct GET_VERSION : PUBLIC + /// Get current RPC protocol version. + /// + /// Inputs: None + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p version RPC current version. + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced + struct GET_VERSION : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_version"); } struct request : EMPTY {}; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - uint32_t version; // RPC current version. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get the coinbase amount and the fees amount for n last blocks starting at particular height. + /// Get the coinbase amount and the fees amount for n last blocks starting at particular height. + /// + /// Inputs: + /// + /// - \p height Block height from which getting the amounts. + /// - \p count Number of blocks to include in the sum. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p emission_amount Amount of coinbase reward in atomic units. + /// - \p fee_amount Amount of fees in atomic units. + /// - \p burn_amount Amount of burnt beldex. struct GET_COINBASE_TX_SUM : RPC_COMMAND { static constexpr auto names() { return NAMES("get_coinbase_tx_sum"); } - struct request + struct request_parameters { uint64_t height; // Block height from which getting the amounts. uint64_t count; // Number of blocks to include in the sum. + } request; - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - uint64_t emission_amount; // Amount of coinbase reward in atomic units. - int64_t fee_amount; // Amount of fees in atomic units. - int64_t burn_amount; // Amount of burnt beldex. - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT From 12e6622d0cf44135e754fad30bbd2c63fdd01008 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 21 Apr 2025 22:01:59 +0530 Subject: [PATCH 070/182] RPC: GET_BASE_FEE_ESTIMATE updated --- src/daemon/rpc_command_executor.cpp | 10 +++-- src/rpc/core_rpc_server.cpp | 26 ++++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 ++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 26 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 44 +++++++++++----------- 7 files changed, 60 insertions(+), 53 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 0aceabaed7a..70775926e57 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1392,13 +1392,15 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) return false; auto& hfinfo = *maybe_hf; - GET_BASE_FEE_ESTIMATE::response feres{}; - if (!invoke({}, feres, "Failed to retrieve current fee info")) - return false; + auto maybe_fees = try_running([this] { return invoke(json{}); }, "Failed to retrieve current fee info"); + if (!maybe_fees) + return false; + auto& feres = *maybe_fees; auto height = info["height"].get(); tools::msg_writer() << "Height: " << height << ", diff " << info["difficulty"].get() << ", cum. diff " << info["cumulative_difficulty"].get() - << ", target " << info["target"].get() << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee_per_byte) << "/" << (hfinfo["enabled"].get() ? "byte" : "kB") + << ", target " << info["target"].get() << " sec" << ", dyn fee " << cryptonote::print_money(feres["fee_per_byte"]) << "/" << (hfinfo["enabled"].get() ? "byte" : "kB") + << " + " << cryptonote::print_money(feres["fee_per_output"]) << "/out"; if (nblocks > 0) { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 701bcc23f3c..ce0075de3e9 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2124,24 +2124,22 @@ namespace cryptonote::rpc { } } //------------------------------------------------------------------------------------------------------------------------------ - GET_BASE_FEE_ESTIMATE::response core_rpc_server::invoke(GET_BASE_FEE_ESTIMATE::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context) { - GET_BASE_FEE_ESTIMATE::response res{}; - PERF_TIMER(on_get_base_fee_estimate); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + //TODO handle bootstrap daemon in new RPC format + //if (use_bootstrap_daemon_if_necessary(req, res)) + //return res; - auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); - res.fee_per_byte = fees.first; - res.fee_per_output = fees.second; - res.flash_fee_fixed = beldex::FLASH_BURN_FIXED; + auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(get_base_fee_estimate.request.grace_blocks); + get_base_fee_estimate.response["fee_per_byte"] = fees.first; + get_base_fee_estimate.response["fee_per_output"] = fees.second; + get_base_fee_estimate.response["flash_fee_fixed"] = FLASH_BURN_FIXED; constexpr auto flash_percent = beldex::FLASH_MINER_TX_FEE_PERCENT + beldex::FLASH_BURN_TX_FEE_PERCENT_OLD; - res.flash_fee_per_byte = res.fee_per_byte * flash_percent / 100; - res.flash_fee_per_output = res.fee_per_output * flash_percent / 100; - res.quantization_mask = Blockchain::get_fee_quantization_mask(); - res.status = STATUS_OK; - return res; + get_base_fee_estimate.response["flash_fee_per_byte"] = fees.first * flash_percent / 100; + get_base_fee_estimate.response["flash_fee_per_output"] = fees.second * flash_percent / 100; + get_base_fee_estimate.response["quantization_mask"] = Blockchain::get_fee_quantization_mask(); + get_base_fee_estimate.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ GET_ALTERNATE_CHAINS::response core_rpc_server::invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 44a08ba60a3..7bd6f562739 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -234,6 +234,7 @@ namespace cryptonote::rpc { void invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context); void invoke(GET_VERSION& version, rpc_context context); void invoke(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_context context); + void invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -260,7 +261,6 @@ namespace cryptonote::rpc { SETBANS::response invoke(SETBANS::request&& req, rpc_context context); GETBANS::response invoke(GETBANS::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); - GET_BASE_FEE_ESTIMATE::response invoke(GET_BASE_FEE_ESTIMATE::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); PRUNE_BLOCKCHAIN::response invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 5fa76298c5a..42eede6eac1 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -431,4 +431,8 @@ namespace cryptonote::rpc { get_values(in, "height", get_coinbase_tx_sum.request.height); get_values(in, "count", get_coinbase_tx_sum.request.count); } + + void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in) { + get_values(in, "grace_blocks", get_base_fee_estimate.request.grace_blocks); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 15d7f3884d8..97908b40b6a 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -34,4 +34,5 @@ namespace cryptonote::rpc { void parse_request(BANNED& banned, rpc_input in); void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in); + void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 46398e01648..a31b75a710f 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -655,21 +655,21 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::request) - KV_SERIALIZE(grace_blocks) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::request) +// KV_SERIALIZE(grace_blocks) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::response) - KV_SERIALIZE(status) - KV_SERIALIZE(fee_per_byte) - KV_SERIALIZE(fee_per_output) - KV_SERIALIZE(flash_fee_per_byte) - KV_SERIALIZE(flash_fee_per_output) - KV_SERIALIZE(flash_fee_fixed) - KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(fee_per_byte) +// KV_SERIALIZE(fee_per_output) +// KV_SERIALIZE(flash_fee_per_byte) +// KV_SERIALIZE(flash_fee_per_output) +// KV_SERIALIZE(flash_fee_fixed) +// KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALTERNATE_CHAINS::chain_info) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 78a8a16bea2..a7f6bd4a40a 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -524,7 +524,7 @@ namespace cryptonote::rpc { bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). tx_verification_context tvc; bool sanity_check_failed; - blink_result blink_status; // 0 for a non-blink tx. For a blink tx: 1 means rejected, 2 means accepted, 3 means timeout. + flash_result flash_status; // 0 for a non-flash tx. For a flash tx: 1 means rejected, 2 means accepted, 3 means timeout. KV_MAP_SERIALIZABLE };*/ @@ -540,7 +540,7 @@ namespace cryptonote::rpc { /// - \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). /// - \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily intended for testing. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output values available from a public RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. struct START_MINING : LEGACY @@ -1392,32 +1392,34 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Gives an estimation of per-output + per-byte fees + /// Gives an estimation of per-output + per-byte fees + /// + /// Inputs: + /// + /// - \p grace_blocks Optional. + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p emission_amount Amount of coinbase reward in atomic units. + /// - \p fee_amount Amount of fees in atomic units. + /// - \p burn_amount Amount of burnt oxen. + /// - \p fee_per_byte Amount of fees estimated per byte in atomic units + /// - \p fee_per_output Amount of fees per output generated by the tx (adds to the `fee_per_byte` per-byte value) + /// - \p flash_fee_per_byte Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. + /// - \p flash_fee_per_output Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. + /// - \p flash_fee_fixed Fixed flash fee in addition to the per-output and per-byte amounts. The portion of the overall flash fee above the overall base fee is burned. + /// - \p quantization_mask + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). struct GET_BASE_FEE_ESTIMATE : PUBLIC { static constexpr auto names() { return NAMES("get_fee_estimate"); } - struct request + struct request_parameters { uint64_t grace_blocks; // Optional + } request; - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - uint64_t fee_per_byte; // Amount of fees estimated per byte in atomic units - uint64_t fee_per_output; // Amount of fees per output generated by the tx (adds to the `fee_per_byte` per-byte value) - uint64_t flash_fee_per_byte; // `fee_per_byte` value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. - uint64_t flash_fee_per_output; // `fee_per_output` value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. - uint64_t flash_fee_fixed; // Fixed flash fee in addition to the per-output and per-byte amounts. The portion of the overall flash fee above the overall base fee is burned. - uint64_t quantization_mask; - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; }; BELDEX_RPC_DOC_INTROSPECT From 0b1eacda436bf9eb4d3eee145ad7ccb9d2a7bcc6 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 10:58:48 +0530 Subject: [PATCH 071/182] RPC: OUT_PEERS and IN_PEERS updated --- src/daemon/rpc_command_executor.cpp | 20 ++++----- src/rpc/core_rpc_server.cpp | 22 ++++------ src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.h | 2 + src/rpc/core_rpc_server_commands_defs.cpp | 32 +++++++------- src/rpc/core_rpc_server_commands_defs.h | 51 ++++++++++++----------- 6 files changed, 65 insertions(+), 66 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 70775926e57..ff300edd711 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1146,12 +1146,12 @@ bool rpc_command_executor::set_limit(int64_t limit_down, int64_t limit_up) bool rpc_command_executor::out_peers(bool set, uint32_t limit) { - OUT_PEERS::request req{set, limit}; - OUT_PEERS::response res{}; - if (!invoke(std::move(req), res, "Failed to set max out peers")) - return false; + auto maybe_out_peers = try_running([this, set, limit] { return invoke(json{{"set", set}, {"out_peers", limit}}); }, "Failed to set max out peers"); + if (!maybe_out_peers) + return false; + auto& out_peers = *maybe_out_peers; - const std::string s = res.out_peers == (uint32_t)-1 ? "unlimited" : std::to_string(res.out_peers); + const std::string s = out_peers["out_peers"] == (uint32_t)-1 ? "unlimited" : out_peers["out_peers"].get(); tools::msg_writer() << "Max number of out peers set to " << s << std::endl; return true; @@ -1159,12 +1159,12 @@ bool rpc_command_executor::out_peers(bool set, uint32_t limit) bool rpc_command_executor::in_peers(bool set, uint32_t limit) { - IN_PEERS::request req{set, limit}; - IN_PEERS::response res{}; - if (!invoke(std::move(req), res, "Failed to set max in peers")) - return false; + auto maybe_in_peers = try_running([this, set, limit] { return invoke(json{{"set", set}, {"in_peers", limit}}); }, "Failed to set max in peers"); + if (!maybe_in_peers) + return false; + auto& in_peers = *maybe_in_peers; - const std::string s = res.in_peers == (uint32_t)-1 ? "unlimited" : std::to_string(res.in_peers); + const std::string s = in_peers["in_peers"] == (uint32_t)-1 ? "unlimited" : in_peers["in_peers"].get(); tools::msg_writer() << "Max number of in peers set to " << s << std::endl; return true; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ce0075de3e9..1e9011a9ac8 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2205,26 +2205,20 @@ namespace cryptonote::rpc { {"status", STATUS_OK}}; } //------------------------------------------------------------------------------------------------------------------------------ - OUT_PEERS::response core_rpc_server::invoke(OUT_PEERS::request&& req, rpc_context context) + void core_rpc_server::invoke(OUT_PEERS& out_peers, rpc_context context) { - OUT_PEERS::response res{}; - PERF_TIMER(on_out_peers); - if (req.set) - m_p2p.change_max_out_public_peers(req.out_peers); - res.status = STATUS_OK; - return res; + if (out_peers.request.set) + m_p2p.change_max_out_public_peers(out_peers.request.out_peers); + out_peers.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - IN_PEERS::response core_rpc_server::invoke(IN_PEERS::request&& req, rpc_context context) + void core_rpc_server::invoke(IN_PEERS& in_peers, rpc_context context) { - IN_PEERS::response res{}; - PERF_TIMER(on_in_peers); - if (req.set) - m_p2p.change_max_in_public_peers(req.in_peers); - res.status = STATUS_OK; - return res; + if (in_peers.request.set) + m_p2p.change_max_in_public_peers(in_peers.request.in_peers); + in_peers.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ POP_BLOCKS::response core_rpc_server::invoke(POP_BLOCKS::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 7bd6f562739..f5e6d8d7639 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -235,6 +235,8 @@ namespace cryptonote::rpc { void invoke(GET_VERSION& version, rpc_context context); void invoke(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_context context); void invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context); + void invoke(OUT_PEERS& out_peers, rpc_context context); + void invoke(IN_PEERS& in_peers, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -249,8 +251,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - OUT_PEERS::response invoke(OUT_PEERS::request&& req, rpc_context context); - IN_PEERS::response invoke(IN_PEERS::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); POP_BLOCKS::response invoke(POP_BLOCKS::request&& req, rpc_context context); GET_LAST_BLOCK_HEADER::response invoke(GET_LAST_BLOCK_HEADER::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 97908b40b6a..39d7159f0d6 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -35,4 +35,6 @@ namespace cryptonote::rpc { void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in); void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in); + void parse_request(OUT_PEERS& out_peers, rpc_input in); + void parse_request(IN_PEERS& in_peers, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index a31b75a710f..af2505e9ffe 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -526,28 +526,28 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(OUT_PEERS::request) - KV_SERIALIZE_OPT(set, true) - KV_SERIALIZE(out_peers) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(OUT_PEERS::request) +// KV_SERIALIZE_OPT(set, true) +// KV_SERIALIZE(out_peers) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(OUT_PEERS::response) - KV_SERIALIZE(out_peers) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(OUT_PEERS::response) +// KV_SERIALIZE(out_peers) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(IN_PEERS::request) - KV_SERIALIZE_OPT(set, true) - KV_SERIALIZE(in_peers) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(IN_PEERS::request) +// KV_SERIALIZE_OPT(set, true) +// KV_SERIALIZE(in_peers) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(IN_PEERS::response) - KV_SERIALIZE(in_peers) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(IN_PEERS::response) +// KV_SERIALIZE(in_peers) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(HARD_FORK_INFO::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index a7f6bd4a40a..3470488ddd8 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1136,7 +1136,6 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_limit"); } }; - BELDEX_RPC_DOC_INTROSPECT /// Set daemon p2p bandwidth limits. /// /// Output values available from a restricted/admin RPC endpoint: @@ -1154,44 +1153,48 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Limit number of Outgoing peers. + /// Limit number of Outgoing peers. + /// + /// Inputs: + /// + /// - \p set If true, set the number of outgoing peers, otherwise the response returns the current limit of outgoing peers. (Defaults to true) + /// - \p out_peers Max number of outgoing peers + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p out_peers The current limit set for outgoing peers. struct OUT_PEERS : LEGACY { static constexpr auto names() { return NAMES("out_peers"); } - struct request + struct request_parameters { bool set; // If true, set the number of outgoing peers, otherwise the response returns the current limit of outgoing peers. (Defaults to true) - uint32_t out_peers; // Max number of outgoing peers - KV_MAP_SERIALIZABLE - }; - - struct response { - uint32_t out_peers; // The current limit set for outgoing peers - std::string status; // General RPC error code. "OK" means everything looks good. - KV_MAP_SERIALIZABLE - }; + uint32_t out_peers; // Max number of outgoing peers + } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Limit number of Incoming peers. + /// Limit number of Incoming peers. + /// + /// Inputs: + /// + /// - \p set If true, set the number of incoming peers, otherwise the response returns the current limit of incoming peers. (Defaults to true) + /// - \p in_peers Max number of incoming peers + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p in_peers The current limit set for incoming peers. struct IN_PEERS : LEGACY { static constexpr auto names() { return NAMES("in_peers"); } - struct request + struct request_parameters { bool set; // If true, set the number of incoming peers, otherwise the response returns the current limit of incoming peers. (Defaults to true) uint32_t in_peers; // Max number of incoming peers - KV_MAP_SERIALIZABLE - }; - - struct response { - uint32_t in_peers; // The current limit set for outgoing peers - std::string status; // General RPC error code. "OK" means everything looks good. - KV_MAP_SERIALIZABLE - }; + } request; }; /// Output values available from a public RPC endpoint: From 38f4fe033699a88ab2f96b02575dedef0e0a488e Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 11:00:10 +0530 Subject: [PATCH 072/182] RPC: POP_BLOCKS updated --- src/daemon/rpc_command_executor.cpp | 7 ++-- src/rpc/core_rpc_server.cpp | 12 ++---- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 14 +++++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 14 +++---- src/rpc/core_rpc_server_commands_defs.h | 47 +++++++++++----------- 7 files changed, 54 insertions(+), 43 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index ff300edd711..fed06587d39 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1961,11 +1961,12 @@ bool rpc_command_executor::print_sr(uint64_t height) bool rpc_command_executor::pop_blocks(uint64_t num_blocks) { - POP_BLOCKS::response res{}; - if (!invoke({num_blocks}, res, "Popping blocks failed")) + auto maybe_pop_blocks = try_running([this, num_blocks] { return invoke(json{{"nblocks", num_blocks}}); }, "Failed to pop blocks"); + if (!maybe_pop_blocks) return false; + auto& pop_blocks = *maybe_pop_blocks; - tools::success_msg_writer() << "new height: " << res.height; + tools::success_msg_writer() << "new height: " << pop_blocks["height"].get(); return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 1e9011a9ac8..afc4dda9b39 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2221,18 +2221,14 @@ namespace cryptonote::rpc { in_peers.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - POP_BLOCKS::response core_rpc_server::invoke(POP_BLOCKS::request&& req, rpc_context context) + void core_rpc_server::invoke(POP_BLOCKS& pop_blocks, rpc_context context) { - POP_BLOCKS::response res{}; - PERF_TIMER(on_pop_blocks); - m_core.get_blockchain_storage().pop_blocks(req.nblocks); - - res.height = m_core.get_current_blockchain_height(); - res.status = STATUS_OK; + m_core.get_blockchain_storage().pop_blocks(pop_blocks.request.nblocks); - return res; + pop_blocks.response["height"] = m_core.get_current_blockchain_height(); + pop_blocks.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ RELAY_TX::response core_rpc_server::invoke(RELAY_TX::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index f5e6d8d7639..08f00d2576c 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -237,6 +237,7 @@ namespace cryptonote::rpc { void invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context); void invoke(OUT_PEERS& out_peers, rpc_context context); void invoke(IN_PEERS& in_peers, rpc_context context); + void invoke(POP_BLOCKS& pop_blocks, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -252,7 +253,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - POP_BLOCKS::response invoke(POP_BLOCKS::request&& req, rpc_context context); GET_LAST_BLOCK_HEADER::response invoke(GET_LAST_BLOCK_HEADER::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HASH::response invoke(GET_BLOCK_HEADER_BY_HASH::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 42eede6eac1..7cafe6f9b84 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -435,4 +435,18 @@ namespace cryptonote::rpc { void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in) { get_values(in, "grace_blocks", get_base_fee_estimate.request.grace_blocks); } + + void parse_request(OUT_PEERS& out_peers, rpc_input in){ + get_values(in, "set", out_peers.request.set); + get_values(in, "out_peers", out_peers.request.out_peers); + } + + void parse_request(IN_PEERS& in_peers, rpc_input in){ + get_values(in, "set", in_peers.request.set); + get_values(in, "in_peers", in_peers.request.in_peers); + } + + void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ + get_values(in, "nblocks", pop_blocks.request.nblocks); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 39d7159f0d6..dc5fe72f5f8 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -37,4 +37,5 @@ namespace cryptonote::rpc { void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in); void parse_request(OUT_PEERS& out_peers, rpc_input in); void parse_request(IN_PEERS& in_peers, rpc_input in); + void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index af2505e9ffe..b27300715a4 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -797,15 +797,15 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(POP_BLOCKS::request) - KV_SERIALIZE(nblocks); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(POP_BLOCKS::request) +// KV_SERIALIZE(nblocks); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(POP_BLOCKS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(height) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(POP_BLOCKS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(height) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(PRUNE_BLOCKCHAIN::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 3470488ddd8..a5299297178 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1548,25 +1548,24 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT + /// Pop blocks off the main chain + /// + /// Inputs: + /// + /// - \p nblocks Number of blocks in that span. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p height Height of the blockchain after blocks have been popped. struct POP_BLOCKS : LEGACY { static constexpr auto names() { return NAMES("pop_blocks"); } - struct request + struct request_parameters { uint64_t nblocks; // Number of blocks in that span. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - uint64_t height; - - KV_MAP_SERIALIZABLE - }; + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -2446,7 +2445,17 @@ namespace cryptonote::rpc { GET_MASTER_NODE_STATUS, SUBMIT_TRANSACTION, GET_BLOCK_HASH, - GET_PEER_LIST + GET_PEER_LIST, + SET_LOG_LEVEL, + SET_LOG_CATEGORIES, + BANNED, + FLUSH_TRANSACTION_POOL, + GET_VERSION, + GET_COINBASE_TX_SUM, + GET_BASE_FEE_ESTIMATE, + OUT_PEERS, + IN_PEERS, + POP_BLOCKS >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2454,24 +2463,14 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HASH, GET_BLOCK_HEADER_BY_HEIGHT, GET_BLOCK, - SET_LOG_LEVEL, - SET_LOG_CATEGORIES, GET_BLOCK_HEADERS_RANGE, SET_BOOTSTRAP_DAEMON, - OUT_PEERS, - IN_PEERS, GETBANS, SETBANS, - BANNED, - FLUSH_TRANSACTION_POOL, GET_OUTPUT_HISTOGRAM, - GET_VERSION, - GET_COINBASE_TX_SUM, - GET_BASE_FEE_ESTIMATE, GET_ALTERNATE_CHAINS, RELAY_TX, GET_OUTPUT_DISTRIBUTION, - POP_BLOCKS, PRUNE_BLOCKCHAIN, GET_QUORUM_STATE, GET_MASTER_NODE_REGISTRATION_CMD_RAW, From ead968761034769aa8f539f91e54e5c27f961c98 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 11:34:45 +0530 Subject: [PATCH 073/182] RPC: STORAGE_SERVER_PING and BELNET_PING updated --- src/rpc/core_rpc_server.cpp | 42 +++++++++---------- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 13 ++++++ src/rpc/core_rpc_server_command_parser.h | 2 + src/rpc/core_rpc_server_commands_defs.cpp | 20 ++++----- src/rpc/core_rpc_server_commands_defs.h | 49 +++++++++++++++------- 6 files changed, 83 insertions(+), 47 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index afc4dda9b39..f09ffcd81fc 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2982,8 +2982,8 @@ namespace cryptonote::rpc { // after the ping had expired). `Success` is a callback that is invoked with a single boolean // argument: true if this ping should trigger an immediate proof send (i.e. first ping after // startup or after a ping expiry), false for an ordinary ping. - template - auto handle_ping( + template + std::string handle_ping( core& core, std::array cur_version, std::array required, @@ -2994,14 +2994,14 @@ namespace cryptonote::rpc { Success success) { std::string our_pubkey_ed25519 = tools::type_to_hex(core.get_master_keys().pub_ed25519); - typename RPC::response res{}; + std::string status{}; if (cur_version < required) { - res.status = fmt::format("Outdated {}. Current: {}.{}.{}, Required: {}.{}.{}",name, cur_version[0], cur_version[1], cur_version[2], required[0], required[1], required[2]); - MERROR(res.status); + status = fmt::format("Outdated {}. Current: {}.{}.{}, Required: {}.{}.{}",name, cur_version[0], cur_version[1], cur_version[2], required[0], required[1], required[2]); + MERROR(status); } else if (!pubkey_ed25519.empty() && !(pubkey_ed25519.find_first_not_of('0') == std::string_view::npos) // TODO: once belnet & ss are always sending this we can remove this empty bypass && (pubkey_ed25519 != our_pubkey_ed25519)) { - res.status = fmt::format("Invalid {} pubkey: expected {}, received {}", name, our_pubkey_ed25519, pubkey_ed25519); - MERROR(res.status); + status = fmt::format("Invalid {} pubkey: expected {}, received {}", name, our_pubkey_ed25519, pubkey_ed25519); + MERROR(status); } else { auto now = std::time(nullptr); auto old = update.exchange(now); @@ -3011,34 +3011,34 @@ namespace cryptonote::rpc { else MDEBUG(fmt::format("Accepted ping from {} {}.{}.{}", name, cur_version[0], cur_version[1], cur_version[2])); success(significant); - res.status = STATUS_OK; + status = STATUS_OK; } return res; } } //------------------------------------------------------------------------------------------------------------------------------ - STORAGE_SERVER_PING::response core_rpc_server::invoke(STORAGE_SERVER_PING::request&& req, rpc_context context) + void core_rpc_server::invoke(STORAGE_SERVER_PING& storage_server_ping, rpc_context context) { - m_core.ss_version = req.version; - return handle_ping(m_core, - req.version, master_nodes::MIN_STORAGE_SERVER_VERSION, - req.pubkey_ed25519, + m_core.ss_version = storage_server_ping.request.version; + storage_server_ping.response["status"] = handle_ping(m_core, + storage_server_ping.request.version, master_nodes::MIN_STORAGE_SERVER_VERSION, + storage_server_ping.request.pubkey_ed25519, "Storage Server", m_core.m_last_storage_server_ping, m_core.get_net_config().UPTIME_PROOF_FREQUENCY, - [this, &req](bool significant) { - m_core.m_storage_https_port = req.https_port; - m_core.m_storage_omq_port = req.omq_port; + [this, &storage_server_ping](bool significant) { + m_core.m_storage_https_port = storage_server_ping.request.https_port; + m_core.m_storage_omq_port = storage_server_ping.request.omq_port; if (significant) m_core.reset_proof_interval(); }); } //------------------------------------------------------------------------------------------------------------------------------ - BELNET_PING::response core_rpc_server::invoke(BELNET_PING::request&& req, rpc_context context) + void core_rpc_server::invoke(BELNET_PING& belnet_ping, rpc_context context) { - m_core.belnet_version = req.version; - return handle_ping(m_core, - req.version, master_nodes::MIN_BELNET_VERSION, - req.pubkey_ed25519, + m_core.belnet_version = belnet_ping.request.version; + belnet_ping.response["status"] = handle_ping(m_core, + belnet_ping.request.version, master_nodes::MIN_BELNET_VERSION, + belnet_ping.request.pubkey_ed25519, "Belnet", m_core.m_last_belnet_ping, m_core.get_net_config().UPTIME_PROOF_FREQUENCY, [this](bool significant) { if (significant) m_core.reset_proof_interval(); }); } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 08f00d2576c..1728eda2c1c 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -238,6 +238,8 @@ namespace cryptonote::rpc { void invoke(OUT_PEERS& out_peers, rpc_context context); void invoke(IN_PEERS& in_peers, rpc_context context); void invoke(POP_BLOCKS& pop_blocks, rpc_context context); + void invoke(BELNET_PING& lokinet_ping, rpc_context context); + void invoke(STORAGE_SERVER_PING& storage_server_ping, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -271,8 +273,6 @@ namespace cryptonote::rpc { GET_MASTER_KEYS::response invoke(GET_MASTER_KEYS::request&& req, rpc_context context); GET_MASTER_PRIVKEYS::response invoke(GET_MASTER_PRIVKEYS::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); - STORAGE_SERVER_PING::response invoke(STORAGE_SERVER_PING::request&& req, rpc_context context); - BELNET_PING::response invoke(BELNET_PING::request&& req, rpc_context context); GET_CHECKPOINTS::response invoke(GET_CHECKPOINTS::request&& req, rpc_context context); GET_MN_STATE_CHANGES::response invoke(GET_MN_STATE_CHANGES::request&& req, rpc_context context); REPORT_PEER_STATUS::response invoke(REPORT_PEER_STATUS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 7cafe6f9b84..aa77d351525 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -449,4 +449,17 @@ namespace cryptonote::rpc { void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ get_values(in, "nblocks", pop_blocks.request.nblocks); } + + + void parse_request(BELNET_PING& belnet_ping, rpc_input in){ + get_values(in, "version", belnet_ping.request.version); + get_values(in, "pubkey_ed25519", belnet_ping.request.pubkey_ed25519); + } + + void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ + get_values(in, "version", storage_server_ping.request.version); + get_values(in, "https_port", storage_server_ping.request.https_port); + get_values(in, "omq_port", storage_server_ping.request.omq_port); + get_values(in, "pubkey_ed25519", storage_server_ping.request.pubkey_ed25519); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index dc5fe72f5f8..2aea7810ab4 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -38,4 +38,6 @@ namespace cryptonote::rpc { void parse_request(OUT_PEERS& out_peers, rpc_input in); void parse_request(IN_PEERS& in_peers, rpc_input in); void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); + void parse_request(BELNET_PING& belnet_ping, rpc_input in); + void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index b27300715a4..33eea2f9def 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1046,18 +1046,18 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(STORAGE_SERVER_PING::request) - KV_SERIALIZE(version); - KV_SERIALIZE(https_port); - KV_SERIALIZE(omq_port); - KV_SERIALIZE(pubkey_ed25519); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(STORAGE_SERVER_PING::request) +// KV_SERIALIZE(version); +// KV_SERIALIZE(https_port); +// KV_SERIALIZE(omq_port); +// KV_SERIALIZE(pubkey_ed25519); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BELNET_PING::request) - KV_SERIALIZE(version); - KV_SERIALIZE(pubkey_ed25519); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BELNET_PING::request) +// KV_SERIALIZE(version); +// KV_SERIALIZE(pubkey_ed25519); +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_STAKING_REQUIREMENT::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index a5299297178..2fff74f35b1 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1995,36 +1995,57 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_master_node_status"); } }; - BELDEX_RPC_DOC_INTROSPECT + /// Endpoint to receive an uptime ping from the connected storage server. This is used + /// to record whether the storage server is ready before the service node starts + /// sending uptime proofs. This is usually called internally from the storage server + /// and this endpoint is mostly available for testing purposes. + /// + /// Inputs: + /// + /// - \p version Storage server version + /// - \p https_port Storage server https port to include in uptime proofs. + /// - \p omq_port Storage server oxenmq port to include in uptime proofs. + /// - \p pubkey_ed25519 Master node Ed25519 pubkey for verifying that storage server is using the right one + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status generic RPC error code; "OK" means the request was successful. struct STORAGE_SERVER_PING : RPC_COMMAND { static constexpr auto names() { return NAMES("storage_server_ping"); } - struct request + struct request_parameters { std::array version; // Storage server version uint16_t https_port; // Storage server https port to include in uptime proofs uint16_t omq_port; // Storage Server oxenmq port to include in uptime proofs std::string pubkey_ed25519; // Master node Ed25519 pubkey for verifying that storage server is using the right one - KV_MAP_SERIALIZABLE - }; - - struct response : STATUS {}; + } request; }; - BELDEX_RPC_DOC_INTROSPECT + /// Endpoint to receive an uptime ping from the connected belnet server. This is used + /// to record whether belnet is ready before the service node starts sending uptime proofs. + /// This is usually called internally from belnet and this endpoint is mostly + /// available for testing purposes. + /// + /// Inputs: + /// + /// - \p version Belnet version + /// - \p pubkey_ed25519 // Master node Ed25519 pubkey for verifying that belnet is using the right one + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status generic RPC error code; "OK" means the request was successful. struct BELNET_PING : RPC_COMMAND { static constexpr auto names() { return NAMES("belnet_ping"); } - struct request + struct request_parameters { std::array version; // Belnet version std::string pubkey_ed25519; // Master node Ed25519 pubkey for verifying that belnet is using the right one - KV_MAP_SERIALIZABLE - }; + } request; - struct response : STATUS {}; }; BELDEX_RPC_DOC_INTROSPECT @@ -2455,7 +2476,9 @@ namespace cryptonote::rpc { GET_BASE_FEE_ESTIMATE, OUT_PEERS, IN_PEERS, - POP_BLOCKS + POP_BLOCKS, + STORAGE_SERVER_PING, + BELNET_PING >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2477,8 +2500,6 @@ namespace cryptonote::rpc { GET_MASTER_NODE_REGISTRATION_CMD, GET_MASTER_KEYS, GET_MASTER_PRIVKEYS, - STORAGE_SERVER_PING, - BELNET_PING, GET_STAKING_REQUIREMENT, GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, GET_CHECKPOINTS, From 25c0b6526a8d31b8b649c66bc056b0398e585b05 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 11:59:31 +0530 Subject: [PATCH 074/182] RPC: PRUNE_BLOCKCHAIN updated --- src/daemon/rpc_command_executor.cpp | 11 ++++---- src/rpc/core_rpc_server.cpp | 16 +++++------ src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 ++- src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 16 +++++------ src/rpc/core_rpc_server_commands_defs.h | 33 ++++++++++++---------- 7 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index fed06587d39..8208deebb37 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2550,10 +2550,8 @@ bool rpc_command_executor::prepare_registration(bool force_registration) bool rpc_command_executor::prune_blockchain() { #if 0 - PRUNE_BLOCKCHAIN::response res{}; - if (!invoke({false}, res, "Failed to prune blockchain")) + if (!invoke(json{{"check", false}}, "Failed to prune blockchain")) return false; - tools::success_msg_writer() << "Blockchain pruned"; #else tools::fail_msg_writer() << "Blockchain pruning is not supported in Beldex yet"; @@ -2563,11 +2561,12 @@ bool rpc_command_executor::prune_blockchain() bool rpc_command_executor::check_blockchain_pruning() { - PRUNE_BLOCKCHAIN::response res{}; - if (!invoke({true}, res, "Failed to check blockchain pruning status")) + auto maybe_pruning = try_running([this] { return invoke(json{{"check", true}}); }, "Failed to check blockchain pruning status"); + if (!maybe_pruning) return false; + auto& pruning = *maybe_pruning; - tools::success_msg_writer() << "Blockchain is" << (res.pruning_seed ? "" : " not") << " pruned"; + tools::success_msg_writer() << "Blockchain is" << (pruning["pruning_seed"] ? "" : " not") << " pruned"; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f09ffcd81fc..4369ee0e804 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2473,24 +2473,22 @@ namespace cryptonote::rpc { return invoke(std::move(static_cast(req)), context, true); } //------------------------------------------------------------------------------------------------------------------------------ - PRUNE_BLOCKCHAIN::response core_rpc_server::invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context) + void core_rpc_server::invoke(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_context context) { - PRUNE_BLOCKCHAIN::response res{}; - try { - if (!(req.check ? m_core.check_blockchain_pruning() : m_core.prune_blockchain())) - throw rpc_error{ERROR_INTERNAL, req.check ? "Failed to check blockchain pruning" : "Failed to prune blockchain"}; - res.pruning_seed = m_core.get_blockchain_pruning_seed(); - res.pruned = res.pruning_seed != 0; + if (!(prune_blockchain.request.check ? m_core.check_blockchain_pruning() : m_core.prune_blockchain())) + throw rpc_error{ERROR_INTERNAL, prune_blockchain.request.check ? "Failed to check blockchain pruning" : "Failed to prune blockchain"}; + auto pruning_seed = m_core.get_blockchain_pruning_seed(); + prune_blockchain.response["pruning_seed"] = pruning_seed; + prune_blockchain.response["pruned"] = pruning_seed != 0; } catch (const std::exception &e) { throw rpc_error{ERROR_INTERNAL, "Failed to prune blockchain"}; } - res.status = STATUS_OK; - return res; + prune_blockchain.response["status"] = STATUS_OK; } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 1728eda2c1c..97b0461ebba 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -240,6 +240,7 @@ namespace cryptonote::rpc { void invoke(POP_BLOCKS& pop_blocks, rpc_context context); void invoke(BELNET_PING& lokinet_ping, rpc_context context); void invoke(STORAGE_SERVER_PING& storage_server_ping, rpc_context context); + void invoke(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -265,7 +266,6 @@ namespace cryptonote::rpc { GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); - PRUNE_BLOCKCHAIN::response invoke(PRUNE_BLOCKCHAIN::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index aa77d351525..4e790fa21dc 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -450,7 +450,6 @@ namespace cryptonote::rpc { get_values(in, "nblocks", pop_blocks.request.nblocks); } - void parse_request(BELNET_PING& belnet_ping, rpc_input in){ get_values(in, "version", belnet_ping.request.version); get_values(in, "pubkey_ed25519", belnet_ping.request.pubkey_ed25519); @@ -461,5 +460,8 @@ namespace cryptonote::rpc { get_values(in, "https_port", storage_server_ping.request.https_port); get_values(in, "omq_port", storage_server_ping.request.omq_port); get_values(in, "pubkey_ed25519", storage_server_ping.request.pubkey_ed25519); + + void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ + get_values(in, "check", prune_blockchain.request.check); } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 2aea7810ab4..c056e787e94 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -40,4 +40,5 @@ namespace cryptonote::rpc { void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); void parse_request(BELNET_PING& belnet_ping, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); + void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 33eea2f9def..477bf600c04 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -808,16 +808,16 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(PRUNE_BLOCKCHAIN::request) - KV_SERIALIZE_OPT(check, false) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(PRUNE_BLOCKCHAIN::request) +// KV_SERIALIZE_OPT(check, false) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(PRUNE_BLOCKCHAIN::response) - KV_SERIALIZE(status) - KV_SERIALIZE(pruned) - KV_SERIALIZE(pruning_seed) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(PRUNE_BLOCKCHAIN::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(pruned) +// KV_SERIALIZE(pruning_seed) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 2fff74f35b1..e003bbc8248 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1568,26 +1568,29 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT + /// Pruning is the process of removing non-critical blockchain information from local storage. + /// Full nodes keep an entire copy of everything that is stored on the blockchain, including data + /// that is not very useful anymore. Pruned nodes remove much of this less relevant information + /// to have a lighter footprint. Of course, running a full node is always better; however, pruned + /// nodes have most of the important information and can still support the network. + /// + /// Inputs: + /// + /// - \p check Instead of running check if the blockchain has already been pruned. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p pruned Bool returning whether the blockchain was pruned or not. + /// - \p pruning_seed The seed that determined how the blockchain was to be pruned. struct PRUNE_BLOCKCHAIN : RPC_COMMAND { static constexpr auto names() { return NAMES("prune_blockchain"); } - struct request + struct request_parameters { bool check; - - KV_MAP_SERIALIZABLE - }; - - struct response - { - bool pruned; - uint32_t pruning_seed; - std::string status; - - KV_MAP_SERIALIZABLE - }; + } request; }; @@ -2479,6 +2482,7 @@ namespace cryptonote::rpc { POP_BLOCKS, STORAGE_SERVER_PING, BELNET_PING + PRUNE_BLOCKCHAIN >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2494,7 +2498,6 @@ namespace cryptonote::rpc { GET_ALTERNATE_CHAINS, RELAY_TX, GET_OUTPUT_DISTRIBUTION, - PRUNE_BLOCKCHAIN, GET_QUORUM_STATE, GET_MASTER_NODE_REGISTRATION_CMD_RAW, GET_MASTER_NODE_REGISTRATION_CMD, From 9642596eb822e4f0a5547adc3610b67bb2364163 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 12:30:54 +0530 Subject: [PATCH 075/182] RPC: TEST_TRIGGER_UPTIME_PROOF, TEST_TRIGGER_P2P_RESYNC, REPORT_PEER_STATUS are updated --- src/daemon/rpc_command_executor.cpp | 4 +- src/rpc/core_rpc_server.cpp | 32 +++++------- src/rpc/core_rpc_server.h | 6 +-- src/rpc/core_rpc_server_command_parser.cpp | 6 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 10 ++-- src/rpc/core_rpc_server_commands_defs.h | 58 ++++++++++++++-------- 7 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 8208deebb37..b898fe699c1 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2603,9 +2603,7 @@ bool rpc_command_executor::version() bool rpc_command_executor::test_trigger_uptime_proof() { - TEST_TRIGGER_UPTIME_PROOF::request req{}; - TEST_TRIGGER_UPTIME_PROOF::response res{}; - return invoke(std::move(req), res, "Failed to trigger uptime proof"); + return invoke(json{{}}, "Failed to trigger uptime proof"); } }// namespace daemonize diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 4369ee0e804..f3d83fa9602 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3195,47 +3195,39 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - REPORT_PEER_STATUS::response core_rpc_server::invoke(REPORT_PEER_STATUS::request&& req, rpc_context context) + void core_rpc_server::invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context) { - REPORT_PEER_STATUS::response res{}; - crypto::public_key pubkey; - if (!tools::hex_to_type(req.pubkey, pubkey)) { - MERROR("Could not parse public key: " << req.pubkey); + if (!tools::hex_to_type(report_peer_status.request.pubkey, pubkey)) { + MERROR("Could not parse public key: " << report_peer_status.request.pubkey); throw rpc_error{ERROR_WRONG_PARAM, "Could not parse public key"}; } bool success = false; - if (req.type == "belnet") - success = m_core.get_master_node_list().set_belnet_peer_reachable(pubkey, req.passed); - else if (req.type == "storage" || req.type == "reachability" /* TODO: old name, can be removed once SS no longer uses it */) - success = m_core.get_master_node_list().set_storage_server_peer_reachable(pubkey, req.passed); + if (report_peer_status.request.type == "belnet") + success = m_core.get_master_node_list().set_belnet_peer_reachable(pubkey, report_peer_status.request.passed); + else if (report_peer_status.request.type == "storage" || report_peer_status.request.type == "reachability" /* TODO: old name, can be removed once SS no longer uses it */) + success = m_core.get_master_node_list().set_storage_server_peer_reachable(pubkey, report_peer_status.request.passed); else throw rpc_error{ERROR_WRONG_PARAM, "Unknown status type"}; if (!success) throw rpc_error{ERROR_WRONG_PARAM, "Pubkey not found"}; - res.status = STATUS_OK; - return res; + report_peer_status.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - TEST_TRIGGER_P2P_RESYNC::response core_rpc_server::invoke(TEST_TRIGGER_P2P_RESYNC::request&& req, rpc_context context) + void core_rpc_server::invoke(TEST_TRIGGER_P2P_RESYNC& test_trigger_p2p_resync, rpc_context context) { - TEST_TRIGGER_P2P_RESYNC::response res{}; - m_p2p.reset_peer_handshake_timer(); - res.status = STATUS_OK; - return res; + test_trigger_p2p_resync.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - TEST_TRIGGER_UPTIME_PROOF::response core_rpc_server::invoke(TEST_TRIGGER_UPTIME_PROOF::request&& req, rpc_context context) + void core_rpc_server::invoke(TEST_TRIGGER_UPTIME_PROOF& test_trigger_uptime_proof, rpc_context context) { if (m_core.get_nettype() != cryptonote::network_type::MAINNET) m_core.submit_uptime_proof(); - TEST_TRIGGER_UPTIME_PROOF::response res{}; - res.status = STATUS_OK; - return res; + test_trigger_uptime_proof.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ BNS_NAMES_TO_OWNERS::response core_rpc_server::invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 97b0461ebba..8a7e2c529b3 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -241,6 +241,9 @@ namespace cryptonote::rpc { void invoke(BELNET_PING& lokinet_ping, rpc_context context); void invoke(STORAGE_SERVER_PING& storage_server_ping, rpc_context context); void invoke(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_context context); + void invoke(TEST_TRIGGER_P2P_RESYNC& test_trigger_p2p_resync, rpc_context context); + void invoke(TEST_TRIGGER_UPTIME_PROOF& test_trigger_uptime_proof, rpc_context context); + void invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -275,9 +278,6 @@ namespace cryptonote::rpc { GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); GET_CHECKPOINTS::response invoke(GET_CHECKPOINTS::request&& req, rpc_context context); GET_MN_STATE_CHANGES::response invoke(GET_MN_STATE_CHANGES::request&& req, rpc_context context); - REPORT_PEER_STATUS::response invoke(REPORT_PEER_STATUS::request&& req, rpc_context context); - TEST_TRIGGER_P2P_RESYNC::response invoke(TEST_TRIGGER_P2P_RESYNC::request&& req, rpc_context context); - TEST_TRIGGER_UPTIME_PROOF::response invoke(TEST_TRIGGER_UPTIME_PROOF::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 4e790fa21dc..5a1a827de5d 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -464,4 +464,10 @@ namespace cryptonote::rpc { void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ get_values(in, "check", prune_blockchain.request.check); } + + void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in) { + get_values(in, "type", report_peer_status.request.type); + get_values(in, "pubkey", report_peer_status.request.pubkey); + get_values(in, "passed", report_peer_status.request.passed); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index c056e787e94..0c2a28156c7 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -41,4 +41,5 @@ namespace cryptonote::rpc { void parse_request(BELNET_PING& belnet_ping, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); + void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 477bf600c04..f75480c27a8 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1133,11 +1133,11 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MN_STATE_CHANGES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(REPORT_PEER_STATUS::request) - KV_SERIALIZE(type) - KV_SERIALIZE(pubkey) - KV_SERIALIZE(passed) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(REPORT_PEER_STATUS::request) +// KV_SERIALIZE(type) +// KV_SERIALIZE(pubkey) +// KV_SERIALIZE(passed) +// KV_SERIALIZE_MAP_CODE_END() diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e003bbc8248..7597c4aaec5 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2215,41 +2215,55 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Reports master node peer status (success/fail) from belnet and storage server. + /// Reports service node peer status (success/fail) from belnet and storage server. + /// + /// Inputs: + /// + /// - /p type test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. + /// - /p pubkey service node pubkey + /// - /p passed whether node is passing the test + /// + /// Output values available from a public RPC endpoint: + /// + /// - /p status Generic RPC error code. "OK" is the success value. struct REPORT_PEER_STATUS : RPC_COMMAND { // TODO: remove the `report_peer_storage_server_status` once we require a storage server version // that stops using the old name. static constexpr auto names() { return NAMES("report_peer_status", "report_peer_storage_server_status"); } - struct request + struct request_parameters { std::string type; // test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. std::string pubkey; // master node pubkey bool passed; // whether the node is passing the test - - KV_MAP_SERIALIZABLE - }; - - struct response : STATUS {}; + } request; }; - // Deliberately undocumented; this RPC call is really only useful for testing purposes to reset - // the resync idle timer (which normally fires every 60s) for the test suite. - struct TEST_TRIGGER_P2P_RESYNC : RPC_COMMAND + /// Deliberately undocumented; this RPC call is really only useful for testing purposes to reset + /// the resync idle timer (which normally fires every 60s) for the test suite. + /// + /// Inputs: none + /// + /// Output values available from a private/admin RPC endpoint: + /// + /// - /p status Generic RPC error code. "OK" is the success value. + struct TEST_TRIGGER_P2P_RESYNC : NO_ARGS { static constexpr auto names() { return NAMES("test_trigger_p2p_resync"); } - - struct request : EMPTY {}; - struct response : STATUS {}; }; - struct TEST_TRIGGER_UPTIME_PROOF : RPC_COMMAND + /// Deliberately undocumented; this RPC call is really only useful for testing purposes to + /// force send an uptime proof. NOT available on mainnet + /// + /// Inputs: none + /// + /// Output values available from a private/admin RPC endpoint: + /// + /// - /p status Generic RPC error code. "OK" is the success value. + struct TEST_TRIGGER_UPTIME_PROOF : NO_ARGS { static constexpr auto names() { return NAMES("test_trigger_uptime_proof"); } - struct request : EMPTY {}; - struct response : STATUS {}; }; BELDEX_RPC_DOC_INTROSPECT @@ -2481,8 +2495,11 @@ namespace cryptonote::rpc { IN_PEERS, POP_BLOCKS, STORAGE_SERVER_PING, - BELNET_PING - PRUNE_BLOCKCHAIN + BELNET_PING, + PRUNE_BLOCKCHAIN, + TEST_TRIGGER_P2P_RESYNC, + TEST_TRIGGER_UPTIME_PROOF, + REPORT_PEER_STATUS >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2507,9 +2524,6 @@ namespace cryptonote::rpc { GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, GET_CHECKPOINTS, GET_MN_STATE_CHANGES, - REPORT_PEER_STATUS, - TEST_TRIGGER_P2P_RESYNC, - TEST_TRIGGER_UPTIME_PROOF, BNS_NAMES_TO_OWNERS, BNS_LOOKUP, BNS_OWNERS_TO_NAMES, From 8bfe1d30c6bcf429d10778c1ec7e40247d98246a Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 12:47:52 +0530 Subject: [PATCH 076/182] RPC: GET_MN_STATE_CHANGES updated --- src/daemon/rpc_command_executor.cpp | 23 +++++------ src/rpc/core_rpc_server.cpp | 38 ++++++++++--------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 5 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 28 +++++++------- src/rpc/core_rpc_server_commands_defs.h | 44 +++++++++++----------- 7 files changed, 73 insertions(+), 68 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b898fe699c1..f370c5a6835 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -338,23 +338,20 @@ bool rpc_command_executor::print_checkpoints(uint64_t start_height, uint64_t end bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, uint64_t end_height) { - GET_MN_STATE_CHANGES::request req{}; - GET_MN_STATE_CHANGES::response res{}; - - req.start_height = start_height; - req.end_height = end_height; - - if (!invoke(std::move(req), res, "Failed to query master nodes state changes")) + auto maybe_mn_state = try_running([&] { return invoke(json{{"start_height", start_height}, {"end_height", end_height}}); }, "Failed to query master nodes state changes"); + if (!maybe_mn_state) return false; + auto mn_state_changes = *maybe_mn_state; + std::stringstream output; - output << "Master Node State Changes (blocks " << res.start_height << "-" << res.end_height << ")" << std::endl; - output << " Recommissions:\t\t" << res.total_recommission << std::endl; - output << " Unlocks:\t\t" << res.total_unlock << std::endl; - output << " Decommissions:\t\t" << res.total_decommission << std::endl; - output << " Deregistrations:\t" << res.total_deregister << std::endl; - output << " IP change penalties:\t" << res.total_ip_change_penalty << std::endl; + output << "Master Node State Changes (blocks " << mn_state_changes["start_height"].get() << "-" << mn_state_changes["end_height"].get() << ")" << std::endl; + output << " Recommissions:\t\t" << mn_state_changes["total_recommission"].get() << std::endl; + output << " Unlocks:\t\t" << mn_state_changes["total_unlock"].get() << std::endl; + output << " Decommissions:\t\t" << mn_state_changes["total_decommission"].get() << std::endl; + output << " Deregistrations:\t" << mn_state_changes["total_deregister"].get() << std::endl; + output << " IP change penalties:\t" << mn_state_changes["total_ip_change_penalty"].get() << std::endl; tools::success_msg_writer() << output.str(); return true; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f3d83fa9602..1c711bb9d9a 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3106,10 +3106,8 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_MN_STATE_CHANGES::response core_rpc_server::invoke(GET_MN_STATE_CHANGES::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context) { - GET_MN_STATE_CHANGES::response res{}; - using blob_t = cryptonote::blobdata; using block_pair_t = std::pair; std::vector blocks; @@ -3118,23 +3116,25 @@ namespace cryptonote::rpc { const uint64_t current_height = db.get_current_blockchain_height(); uint64_t end_height; - if (req.end_height == GET_MN_STATE_CHANGES::HEIGHT_SENTINEL_VALUE) { + uint64_t start_height = get_mn_state_changes.request.start_height; + if (get_mn_state_changes.request.end_height == GET_MN_STATE_CHANGES::HEIGHT_SENTINEL_VALUE) { // current height is the block being mined, so exclude it from the results end_height = current_height - 1; } else { - end_height = req.end_height; + end_height = get_mn_state_changes.request.end_height; } - if (end_height < req.start_height) + if (end_height < start_height) throw rpc_error{ERROR_WRONG_PARAM, "The provided end_height needs to be higher than start_height"}; - if (!db.get_blocks(req.start_height, end_height - req.start_height + 1, blocks)) - throw rpc_error{ERROR_INTERNAL, "Could not query block at requested height: " + std::to_string(req.start_height)}; + if (!db.get_blocks(start_height, end_height - start_height + 1, blocks)) + throw rpc_error{ERROR_INTERNAL, "Could not query block at requested height: " + std::to_string(start_height)}; - res.start_height = req.start_height; - res.end_height = end_height; + get_mn_state_changes.response["start_height"] = start_height; + get_mn_state_changes.response["end_height"] = end_height; std::vector blobs; + int total_deregister = 0, total_decommission = 0, total_recommission = 0, total_ip_change_penalty = 0, total_unlock = 0; for (const auto& block : blocks) { blobs.clear(); @@ -3163,19 +3163,19 @@ namespace cryptonote::rpc { switch(state_change.state) { case master_nodes::new_state::deregister: - res.total_deregister++; + total_deregister++; break; case master_nodes::new_state::decommission: - res.total_decommission++; + total_decommission++; break; case master_nodes::new_state::recommission: - res.total_recommission++; + total_recommission++; break; case master_nodes::new_state::ip_change_penalty: - res.total_ip_change_penalty++; + total_ip_change_penalty++; break; default: @@ -3186,13 +3186,17 @@ namespace cryptonote::rpc { if (tx.type == cryptonote::txtype::key_image_unlock) { - res.total_unlock++; + total_unlock++; } } } - res.status = STATUS_OK; - return res; + get_mn_state_changes.response["total_deregister"] = total_deregister; + get_mn_state_changes.response["total_decommission"] = total_decommission; + get_mn_state_changes.response["total_recommission"] = total_recommission; + get_mn_state_changes.response["total_ip_change_penalty"] = total_ip_change_penalty; + get_mn_state_changes.response["total_unlock"] = total_unlock; + get_mn_state_changes.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 8a7e2c529b3..f18d8986839 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -244,6 +244,7 @@ namespace cryptonote::rpc { void invoke(TEST_TRIGGER_P2P_RESYNC& test_trigger_p2p_resync, rpc_context context); void invoke(TEST_TRIGGER_UPTIME_PROOF& test_trigger_uptime_proof, rpc_context context); void invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context); + void invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -277,7 +278,6 @@ namespace cryptonote::rpc { GET_MASTER_PRIVKEYS::response invoke(GET_MASTER_PRIVKEYS::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); GET_CHECKPOINTS::response invoke(GET_CHECKPOINTS::request&& req, rpc_context context); - GET_MN_STATE_CHANGES::response invoke(GET_MN_STATE_CHANGES::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 5a1a827de5d..9827f219155 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -470,4 +470,9 @@ namespace cryptonote::rpc { get_values(in, "pubkey", report_peer_status.request.pubkey); get_values(in, "passed", report_peer_status.request.passed); } + + void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in) { + get_values(in, "start_height", get_mn_state_changes.request.start_height); + get_values(in, "end_height", get_mn_state_changes.request.end_height); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 0c2a28156c7..d71c31785b1 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -42,4 +42,5 @@ namespace cryptonote::rpc { void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); + void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index f75480c27a8..9d58bae6227 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1115,22 +1115,22 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MN_STATE_CHANGES::request) - KV_SERIALIZE(start_height) - KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MN_STATE_CHANGES::request) +// KV_SERIALIZE(start_height) +// KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MN_STATE_CHANGES::response) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) - KV_SERIALIZE(total_deregister) - KV_SERIALIZE(total_ip_change_penalty) - KV_SERIALIZE(total_decommission) - KV_SERIALIZE(total_recommission) - KV_SERIALIZE(start_height) - KV_SERIALIZE(end_height) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MN_STATE_CHANGES::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE(total_deregister) +// KV_SERIALIZE(total_ip_change_penalty) +// KV_SERIALIZE(total_decommission) +// KV_SERIALIZE(total_recommission) +// KV_SERIALIZE(start_height) +// KV_SERIALIZE(end_height) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(REPORT_PEER_STATUS::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 7597c4aaec5..125a828c9a9 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2182,36 +2182,34 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + /// Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + /// + /// Inputs: + /// + /// - /p start_height The starting block's height. + /// - /p end_height The ending block's height. + /// + /// Output values available from a public RPC endpoint: + /// + /// - /p status Generic RPC error code. "OK" is the success value. + /// - /p untrusted If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// - /p total_deregister + /// - /p total_ip_change_penalty + /// - /p total_decommission + /// - /p total_recommission + /// - /p total_unlock + /// - /p start_height + /// - /p end_height struct GET_MN_STATE_CHANGES : PUBLIC { static constexpr auto names() { return NAMES("get_master_nodes_state_changes"); } static constexpr uint64_t HEIGHT_SENTINEL_VALUE = std::numeric_limits::max() - 1; - struct request + struct request_parameters { uint64_t start_height; uint64_t end_height; // Optional: If omitted, the tally runs until the current block - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // Generic RPC error code. "OK" is the success value. - bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. - - uint32_t total_deregister; - uint32_t total_ip_change_penalty; - uint32_t total_decommission; - uint32_t total_recommission; - uint32_t total_unlock; - uint64_t start_height; - uint64_t end_height; - - KV_MAP_SERIALIZABLE - }; + } request; }; @@ -2500,6 +2498,7 @@ namespace cryptonote::rpc { TEST_TRIGGER_P2P_RESYNC, TEST_TRIGGER_UPTIME_PROOF, REPORT_PEER_STATUS + GET_MN_STATE_CHANGES >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2523,7 +2522,6 @@ namespace cryptonote::rpc { GET_STAKING_REQUIREMENT, GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, GET_CHECKPOINTS, - GET_MN_STATE_CHANGES, BNS_NAMES_TO_OWNERS, BNS_LOOKUP, BNS_OWNERS_TO_NAMES, From 7b46047bf28da2319454ab96af28d1a7156d3fbf Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 12:50:08 +0530 Subject: [PATCH 077/182] RPC: FLUSH_CACHE updated --- src/daemon/rpc_command_executor.cpp | 6 +---- src/rpc/core_rpc_server.cpp | 10 +++----- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 8 +++--- src/rpc/core_rpc_server_commands_defs.h | 29 ++++++++++++++-------- 7 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index f370c5a6835..66f656588f3 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1932,11 +1932,7 @@ bool rpc_command_executor::print_mn(const std::vector &args, bool s bool rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) { - FLUSH_CACHE::response res{}; - FLUSH_CACHE::request req{}; - req.bad_txs = bad_txs; - req.bad_blocks = bad_blocks; - if (!invoke(std::move(req), res, "Failed to flush TX cache")) + if (!invoke(json{{"bad_txs", bad_txs}, {"bad_blocks", bad_blocks}}, "Failed to flush TX cache")) return false; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 1c711bb9d9a..cdf2a6306df 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2636,15 +2636,13 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - FLUSH_CACHE::response core_rpc_server::invoke(FLUSH_CACHE::request&& req, rpc_context context) + void core_rpc_server::invoke(FLUSH_CACHE& flush_cache, rpc_context context) { - FLUSH_CACHE::response res{}; - if (req.bad_txs) + if (flush_cache.request.bad_txs) m_core.flush_bad_txs_cache(); - if (req.bad_blocks) + if (flush_cache.request.bad_blocks) m_core.flush_invalid_blocks(); - res.status = STATUS_OK; - return res; + flush_cache.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ GET_MASTER_NODE_REGISTRATION_CMD_RAW::response core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index f18d8986839..77c8deba426 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -245,6 +245,7 @@ namespace cryptonote::rpc { void invoke(TEST_TRIGGER_UPTIME_PROOF& test_trigger_uptime_proof, rpc_context context); void invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context); void invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context); + void invoke(FLUSH_CACHE& flush_cache, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -282,7 +283,6 @@ namespace cryptonote::rpc { BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); BNS_VALUE_DECRYPT::response invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context); - FLUSH_CACHE::response invoke(FLUSH_CACHE::request&& req, rpc_context); #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) void on_relay_uptime_and_votes() diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 9827f219155..4ebeceab6c0 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -474,5 +474,9 @@ namespace cryptonote::rpc { void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in) { get_values(in, "start_height", get_mn_state_changes.request.start_height); get_values(in, "end_height", get_mn_state_changes.request.end_height); + + void parse_request(FLUSH_CACHE& flush_cache, rpc_input in) { + get_values(in, "bad_txs", flush_cache.request.bad_txs); + get_values(in, "bad_blocks", flush_cache.request.bad_blocks); } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index d71c31785b1..f19777f0090 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -43,4 +43,5 @@ namespace cryptonote::rpc { void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); + void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 9d58bae6227..ffbb4ff8036 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1234,9 +1234,9 @@ KV_SERIALIZE_MAP_CODE_BEGIN(BNS_VALUE_DECRYPT::response) KV_SERIALIZE(value) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(FLUSH_CACHE::request) - KV_SERIALIZE_OPT(bad_txs, false) - KV_SERIALIZE_OPT(bad_blocks, false) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(FLUSH_CACHE::request) +// KV_SERIALIZE_OPT(bad_txs, false) +// KV_SERIALIZE_OPT(bad_blocks, false) +// KV_SERIALIZE_MAP_CODE_END() } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 125a828c9a9..29dceab83ba 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2243,7 +2243,7 @@ namespace cryptonote::rpc { /// /// Inputs: none /// - /// Output values available from a private/admin RPC endpoint: + /// Output values available from a public RPC endpoint: /// /// - /p status Generic RPC error code. "OK" is the success value. struct TEST_TRIGGER_P2P_RESYNC : NO_ARGS @@ -2256,7 +2256,7 @@ namespace cryptonote::rpc { /// /// Inputs: none /// - /// Output values available from a private/admin RPC endpoint: + /// Output values available from a public RPC endpoint: /// /// - /p status Generic RPC error code. "OK" is the success value. struct TEST_TRIGGER_UPTIME_PROOF : NO_ARGS @@ -2437,17 +2437,24 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Clear TXs from the daemon cache, currently only the cache storing TX hashes that were previously verified bad by the daemon. + /// Clear TXs from the daemon cache, currently only the cache storing TX hashes that were previously verified bad by the daemon. + /// + /// Inputs: + /// + /// - /p bad_txs Clear the cache storing TXs that failed verification. + /// - /p bad_blocks Clear the cache storing blocks that failed verfication. + /// + /// Output values available from a public RPC endpoint: + /// + /// - /p status Generic RPC error code. "OK" is the success value. struct FLUSH_CACHE : RPC_COMMAND { static constexpr auto names() { return NAMES("flush_cache"); } - struct request + struct request_parameter { bool bad_txs; // Clear the cache storing TXs that failed verification. bool bad_blocks; // Clear the cache storing blocks that failed verfication. - KV_MAP_SERIALIZABLE; - }; + } request; struct response : STATUS { }; }; @@ -2497,8 +2504,9 @@ namespace cryptonote::rpc { PRUNE_BLOCKCHAIN, TEST_TRIGGER_P2P_RESYNC, TEST_TRIGGER_UPTIME_PROOF, - REPORT_PEER_STATUS - GET_MN_STATE_CHANGES + REPORT_PEER_STATUS, + GET_MN_STATE_CHANGES, + FLUSH_CACHE >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2525,8 +2533,7 @@ namespace cryptonote::rpc { BNS_NAMES_TO_OWNERS, BNS_LOOKUP, BNS_OWNERS_TO_NAMES, - BNS_VALUE_DECRYPT, - FLUSH_CACHE + BNS_VALUE_DECRYPT >; } // namespace cryptonote::rpc From eb998d6901c4f3b1d33e20e6a804559259092912 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 13:43:30 +0530 Subject: [PATCH 078/182] RPC: GETBANS and SETBANS updated --- src/daemon/rpc_command_executor.cpp | 25 +++---- src/rpc/core_rpc_server.cpp | 82 ++++++++++------------ src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 7 ++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 36 +++++----- src/rpc/core_rpc_server_commands_defs.h | 72 +++++++++---------- 7 files changed, 107 insertions(+), 120 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 66f656588f3..510cc8f9f87 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1169,16 +1169,16 @@ bool rpc_command_executor::in_peers(bool set, uint32_t limit) bool rpc_command_executor::print_bans() { - GETBANS::response res{}; - - if (!invoke({}, res, "Failed to retrieve ban list")) + auto maybe_bans = try_running([this] { return invoke(); }, "Failed to retrieve ban list"); + if (!maybe_bans) return false; + auto bans = *maybe_bans; - if (!res.bans.empty()) + if (!bans.empty()) { - for (auto i = res.bans.begin(); i != res.bans.end(); ++i) + for (auto i = bans.begin(); i != bans.end(); ++i) { - tools::msg_writer() << i->host << " banned for " << i->seconds << " seconds"; + tools::msg_writer() << (*i)["host"] << " banned for " << (*i)["seconds"] << " seconds"; } } else @@ -1189,17 +1189,8 @@ bool rpc_command_executor::print_bans() bool rpc_command_executor::ban(const std::string &address, time_t seconds, bool clear_ban) { - SETBANS::request req{}; - SETBANS::response res{}; - - req.bans.emplace_back(); - auto& ban = req.bans.back(); - ban.host = address; - ban.ip = 0; - ban.ban = !clear_ban; - ban.seconds = seconds; - - if (!invoke(std::move(req), res, clear_ban ? "Failed to clear ban" : "Failed to set ban")) + auto maybe_banned = try_running([this, &address, seconds, clear_ban] { return invoke(json{{"host", std::move(address)}, {"ip", 0}, {"seconds", seconds}, {"ban", !clear_ban}}); }, clear_ban ? "Failed to clear ban" : "Failed to set ban"); + if (!maybe_banned) return false; // TODO(doyle): Work around because integration tests break when using diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index cdf2a6306df..f05e2361787 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1888,10 +1888,8 @@ namespace cryptonote::rpc { hfinfo.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GETBANS::response core_rpc_server::invoke(GETBANS::request&& req, rpc_context context) + void core_rpc_server::invoke(GETBANS& get_bans, rpc_context context) { - GETBANS::response res{}; - PERF_TIMER(on_get_bans); auto now = time(nullptr); @@ -1899,30 +1897,30 @@ namespace cryptonote::rpc { for (std::map::const_iterator i = blocked_hosts.begin(); i != blocked_hosts.end(); ++i) { if (i->second > now) { - GETBANS::ban b; + ban b; b.host = i->first; b.ip = 0; uint32_t ip; if (epee::string_tools::get_ip_int32_from_string(ip, b.host)) b.ip = ip; b.seconds = i->second - now; - res.bans.push_back(b); + get_bans.response["bans"].push_back(b); } } std::map blocked_subnets = m_p2p.get_blocked_subnets(); for (std::map::const_iterator i = blocked_subnets.begin(); i != blocked_subnets.end(); ++i) { if (i->second > now) { - GETBANS::ban b; + ban b; b.host = i->first.host_str(); b.ip = 0; b.seconds = i->second - now; - res.bans.push_back(b); + get_bans.response["bans"].push_back(b); } } - res.status = STATUS_OK; - return res; + get_bans.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(BANNED& banned, rpc_context context) @@ -1949,50 +1947,46 @@ namespace cryptonote::rpc { banned.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - SETBANS::response core_rpc_server::invoke(SETBANS::request&& req, rpc_context context) + void core_rpc_server::invoke(SETBANS& set_bans, rpc_context context) { - SETBANS::response res{}; - PERF_TIMER(on_set_bans); - for (auto i = req.bans.begin(); i != req.bans.end(); ++i) - { - epee::net_utils::network_address na; + epee::net_utils::network_address na; - // try subnet first - if (!i->host.empty()) + // try subnet first + if (!set_bans.request.host.empty()) + { + auto ns_parsed = net::get_ipv4_subnet_address(set_bans.request.host); + if (ns_parsed) { - auto ns_parsed = net::get_ipv4_subnet_address(i->host); - if (ns_parsed) - { - if (i->ban) - m_p2p.block_subnet(*ns_parsed, i->seconds); - else - m_p2p.unblock_subnet(*ns_parsed); - continue; - } + if (set_bans.request.ban) + m_p2p.block_subnet(*ns_parsed, set_bans.request.seconds); + else + m_p2p.unblock_subnet(*ns_parsed); + set_bans.response["status"] = STATUS_OK; + return; } + } - // then host - if (!i->host.empty()) - { - auto na_parsed = net::get_network_address(i->host, 0); - if (!na_parsed) - throw rpc_error{ERROR_WRONG_PARAM, "Unsupported host/subnet type"}; - na = std::move(*na_parsed); - } - else - { - na = epee::net_utils::ipv4_network_address{i->ip, 0}; - } - if (i->ban) - m_p2p.block_host(na, i->seconds); - else - m_p2p.unblock_host(na); + // then host + if (!set_bans.request.host.empty()) + { + auto na_parsed = net::get_network_address(set_bans.request.host, 0); + if (!na_parsed) + throw rpc_error{ERROR_WRONG_PARAM, "Unsupported host/subnet type"}; + na = std::move(*na_parsed); } + else + { + na = epee::net_utils::ipv4_network_address{set_bans.request.ip, 0}; + } + if (set_bans.request.ban) + m_p2p.block_host(na, set_bans.request.seconds); + else + m_p2p.unblock_host(na); - res.status = STATUS_OK; - return res; + set_bans.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 77c8deba426..cf9d5750e8d 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -246,6 +246,8 @@ namespace cryptonote::rpc { void invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context); void invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context); void invoke(FLUSH_CACHE& flush_cache, rpc_context context); + void invoke(GETBANS& get_bans, rpc_context context); + void invoke(SETBANS& set_bans, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -266,8 +268,6 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); - SETBANS::response invoke(SETBANS::request&& req, rpc_context context); - GETBANS::response invoke(GETBANS::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 4ebeceab6c0..27cc5ed876f 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -479,4 +479,11 @@ namespace cryptonote::rpc { get_values(in, "bad_txs", flush_cache.request.bad_txs); get_values(in, "bad_blocks", flush_cache.request.bad_blocks); } + + void parse_request(SETBANS& set_bans, rpc_input in) { + get_values(in, "host", set_bans.request.host); + get_values(in, "ip", set_bans.request.ip); + get_values(in, "seconds", set_bans.request.seconds); + get_values(in, "ban", set_bans.request.ban); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index f19777f0090..c3c486819d3 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -44,4 +44,5 @@ namespace cryptonote::rpc { void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); + void parse_request(SETBANS& set_bans, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index ffbb4ff8036..7ea4d3929a0 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -567,30 +567,30 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::ban) - KV_SERIALIZE(host) - KV_SERIALIZE(ip) - KV_SERIALIZE(seconds) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::ban) +// KV_SERIALIZE(host) +// KV_SERIALIZE(ip) +// KV_SERIALIZE(seconds) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(bans) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(bans) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::ban) - KV_SERIALIZE(host) - KV_SERIALIZE(ip) - KV_SERIALIZE(ban) - KV_SERIALIZE(seconds) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::ban) +// KV_SERIALIZE(host) +// KV_SERIALIZE(ip) +// KV_SERIALIZE(ban) +// KV_SERIALIZE(seconds) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::request) - KV_SERIALIZE(bans) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::request) +// KV_SERIALIZE(bans) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(BANNED::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 29dceab83ba..d65f45865ab 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1223,56 +1223,49 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Get list of banned IPs. + /// Get list of banned IPs. + /// + /// Inputs: None + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p bans List of banned nodes struct GETBANS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_bans"); } + }; - struct request : EMPTY {}; - - struct ban - { - std::string host; // Banned host (IP in A.B.C.D form). - uint32_t ip; // Banned IP address, in Int format. - uint32_t seconds; // Local Unix time that IP is banned until. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector bans; // List of banned nodes: - - KV_MAP_SERIALIZABLE - }; + struct ban + { + std::string host; // Banned host (IP in A.B.C.D form). + uint32_t ip; // Banned IP address, in Int format. + uint32_t seconds; // Local Unix time that IP is banned until }; + inline void to_json(nlohmann::json& j, const ban& b) { j = nlohmann::json{{"host", b.host}, {"ip", b.ip}, {"seconds", b.seconds} }; }; - BELDEX_RPC_DOC_INTROSPECT - // Ban another node by IP. + /// Ban another node by IP. + /// + /// Inputs: + /// - \p host Banned host (IP in A.B.C.D form). + /// - \p ip Banned IP address, in Int format. + /// - \p seconds Local Unix time that IP is banned until, or Number of seconds to ban node + /// - \p ban Set true to ban. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. struct SETBANS : RPC_COMMAND { static constexpr auto names() { return NAMES("set_bans"); } - struct ban + struct request_parameters { - std::string host; // Host to ban (IP in A.B.C.D form - will support I2P address in the future). - uint32_t ip; // IP address to ban, in Int format. + std::string host; // Banned host (IP in A.B.C.D form). + uint32_t ip; // Banned IP address, in Int format. + uint32_t seconds; // Local Unix time that IP is banned until, or Number of seconds to ban node bool ban; // Set true to ban. - uint32_t seconds; // Number of seconds to ban node. - - KV_MAP_SERIALIZABLE - }; - - struct request - { - std::vector bans; // List of nodes to ban. - - KV_MAP_SERIALIZABLE - }; - - struct response : STATUS {}; + } request; }; /// Determine whether a given IP address is banned @@ -1289,7 +1282,8 @@ namespace cryptonote::rpc { { static constexpr auto names() { return NAMES("banned"); } - struct request_parameters { + struct request_parameters + { std::string address; // The IP address to check } request; }; From 9395f74a706941f3f31e4b3ccafb30fb4e51da25 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 14:17:43 +0530 Subject: [PATCH 079/182] various formating fix and rebase fixups --- src/rpc/core_rpc_server_commands_defs.h | 182 +++++++++++++----------- src/wallet/wallet2.cpp | 1 + tests/net_load_tests/clt.cpp | 2 +- tests/net_load_tests/srv.cpp | 2 +- 4 files changed, 100 insertions(+), 87 deletions(-) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d65f45865ab..54fa6d4c37a 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -205,12 +205,12 @@ namespace cryptonote::rpc { /// /// Outputs: /// - /// - /p height -- The current blockchain height according to the queried daemon. - /// - /p status -- Generic RPC error code. "OK" is the success value. - /// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. - /// - /p hash -- Hash of the block at the current height - /// - /p immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 MN checkpoints. Omitted if not available. - /// - /p immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. + /// - \p height -- The current blockchain height according to the queried daemon. + /// - \p status -- Generic RPC error code. "OK" is the success value. + /// - \p untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// - \p hash -- Hash of the block at the current height + /// - \p immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 MN checkpoints. Omitted if not available. + /// - \p immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. struct GET_HEIGHT : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_height", "getheight"); } @@ -220,8 +220,8 @@ namespace cryptonote::rpc { /// /// Outputs: /// - /// - /p status -- Generic RPC error code. "OK" is the success value. - /// - /p untrusted -- If the result is obtained using bootstrap mode then this will be set to + /// - \p status -- Generic RPC error code. "OK" is the success value. + /// - \p untrusted -- If the result is obtained using bootstrap mode then this will be set to /// true, otherwise will be omitted. /// - \p missed_tx -- set of transaction hashes that were not found. If all were found then this /// field is omitted. There is no particular ordering of hashes in this list. @@ -309,7 +309,7 @@ namespace cryptonote::rpc { /// deregistration, decommission, recommission, or ip change reset transaction. This is a /// dict containing: /// - \p old_dereg will be set to true if this is an "old" deregistration transaction - /// (before the Loki 4 hardfork), omitted for more modern state change txes. + /// (before the BDX 4 hardfork), omitted for more modern state change txes. /// - \p type string indicating the state change type: "dereg", "decomm", "recomm", or "ip" /// for a deregistration, decommission, recommission, or ip change penalty transaction. /// - \p height the voting block height for the changing master node and voting master @@ -918,10 +918,14 @@ namespace cryptonote::rpc { }; - /// Set the daemon log categories. Categories are represented as a comma separated list of `:` (similarly to syslog standard `:`), where: - /// Category is one of the following: * (all facilities), default, net, net.http, net.p2p, logging, net.trottle, blockchain.db, blockchain.db.lmdb, bcutil, checkpoints, net.dns, net.dl, - /// i18n, perf,stacktrace, updates, account, cn ,difficulty, hardfork, miner, blockchain, txpool, cn.block_queue, net.cn, daemon, debugtools.deserialize, debugtools.objectsizes, device.ledger, - /// wallet.gen_multisig, multisig, bulletproofs, ringct, daemon.rpc, wallet.simplewallet, WalletAPI, wallet.ringdb, wallet.wallet2, wallet.rpc, tests.core. + /// Set the daemon log categories. Categories are represented as a comma separated list of + /// `:` (similarly to syslog standard `:`), where: + /// Category is one of the following: * (all facilities), default, net, net.http, net.p2p, + /// logging, net.trottle, blockchain.db, blockchain.db.lmdb, bcutil, checkpoints, net.dns, net.dl, + /// i18n, perf,stacktrace, updates, account, cn ,difficulty, hardfork, miner, blockchain, txpool, + /// cn.block_queue, net.cn, daemon, debugtools.deserialize, debugtools.objectsizes, device.ledger, + /// wallet.gen_multisig, multisig, bulletproofs, ringct, daemon.rpc, wallet.simplewallet, + /// WalletAPI, wallet.ringdb, wallet.wallet2, wallet.rpc, tests.core. /// /// Level is one of the following: FATAL - higher level, ERROR, WARNING, INFO, DEBUG, TRACE. /// Lower level A level automatically includes higher level. By default, categories are set to: @@ -960,7 +964,8 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p tx_hashes List of transaction hashes, - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not + /// trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } @@ -1051,7 +1056,8 @@ namespace cryptonote::rpc { /// - \c normal - this is a regular, synchronized peer /// - \p live_ms - number of milliseconds since this connection was initiated /// - \p avg_download - the average download speed from this peer in bytes per second - /// - \p current_download - the current (i.e. average over a very recent period) download speed from this peer in bytes per second. + /// - \p current_download - the current (i.e. average over a very recent period) download speed + /// from this peer in bytes per second. /// - \p avg_upload - the average upload speed to this peer in bytes per second /// - \p current_upload - the current upload speed to this peer in bytes per second /// - \p connection_id - a unique random string identifying this connection @@ -1138,6 +1144,13 @@ namespace cryptonote::rpc { /// Set daemon p2p bandwidth limits. /// + /// Inputs: + /// + /// - \p limit_down Download limit in kBytes per second. -1 means reset to default; 0 (or + /// omitted) means don't change the current limit + /// - \p limit_up Upload limit in kBytes per second. -1 means reset to default; 0 (or omitted) + /// means don't change the current limit + /// /// Output values available from a restricted/admin RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. @@ -1148,8 +1161,8 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("set_limit"); } struct request_parameters { - int64_t limit_down = 0; ///< Download limit in kBytes per second. -1 means reset to default; 0 (or omitted) means don't change the current limit - int64_t limit_up = 0; ///< Upload limit in kBytes per second. -1 means reset to default; 0 (or omitted) means don't change the current limit + int64_t limit_down = 0; + int64_t limit_up = 0; } request; }; @@ -1157,7 +1170,8 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p set If true, set the number of outgoing peers, otherwise the response returns the current limit of outgoing peers. (Defaults to true) + /// - \p set If true, set the number of outgoing peers, otherwise the response returns the current + /// limit of outgoing peers. (Defaults to true) /// - \p out_peers Max number of outgoing peers /// /// Output values available from a restricted/admin RPC endpoint: @@ -1170,8 +1184,8 @@ namespace cryptonote::rpc { struct request_parameters { - bool set; // If true, set the number of outgoing peers, otherwise the response returns the current limit of outgoing peers. (Defaults to true) - uint32_t out_peers; // Max number of outgoing peers + bool set; + uint32_t out_peers; } request; }; @@ -1179,7 +1193,8 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p set If true, set the number of incoming peers, otherwise the response returns the current limit of incoming peers. (Defaults to true) + /// - \p set If true, set the number of incoming peers, otherwise the response returns the current + /// limit of incoming peers. (Defaults to true) /// - \p in_peers Max number of incoming peers /// /// Output values available from a restricted/admin RPC endpoint: @@ -1192,8 +1207,8 @@ namespace cryptonote::rpc { struct request_parameters { - bool set; // If true, set the number of incoming peers, otherwise the response returns the current limit of incoming peers. (Defaults to true) - uint32_t in_peers; // Max number of incoming peers + bool set; + uint32_t in_peers; } request; }; @@ -1302,7 +1317,7 @@ namespace cryptonote::rpc { struct request_parameters { - std::vector txids; // Optional, list of transactions IDs to flush from pool (all tx ids flushed if empty). + std::vector txids; } request; }; @@ -1356,7 +1371,8 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p version RPC current version. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced + /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not + /// trusted (`true`), or when the daemon is fully synced struct GET_VERSION : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_version"); } @@ -1383,8 +1399,8 @@ namespace cryptonote::rpc { struct request_parameters { - uint64_t height; // Block height from which getting the amounts. - uint64_t count; // Number of blocks to include in the sum. + uint64_t height; + uint64_t count; } request; }; @@ -1400,7 +1416,7 @@ namespace cryptonote::rpc { /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p emission_amount Amount of coinbase reward in atomic units. /// - \p fee_amount Amount of fees in atomic units. - /// - \p burn_amount Amount of burnt oxen. + /// - \p burn_amount Amount of burnt beldex. /// - \p fee_per_byte Amount of fees estimated per byte in atomic units /// - \p fee_per_output Amount of fees per output generated by the tx (adds to the `fee_per_byte` per-byte value) /// - \p flash_fee_per_byte Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. @@ -1414,7 +1430,7 @@ namespace cryptonote::rpc { struct request_parameters { - uint64_t grace_blocks; // Optional + uint64_t grace_blocks; } request; }; @@ -1736,32 +1752,28 @@ namespace cryptonote::rpc { }; /// Get information on some, all, or a random subset of Master Nodes. - struct master_node_contribution /// - { /// Output variables available are as follows (you can request which parameters are returned; see - std::string key_image; // The contribution's key image that is locked on the network. /// the request parameters description). Note that BELDEX values are returned in atomic BELDEX units, - std::string key_image_pub_key; // The contribution's key image, public key component /// which are nano-BELDEX (i.e. 1.000000000 BELDEX will be returned as 1000000000). - uint64_t amount; // The amount that is locked in this contribution. /// - + /// + /// Output variables available are as follows (you can request which parameters are returned; see + /// the request parameters description). Note that BDX values are returned in atomic BDX units, + /// which are nano-BDX (i.e. 1.000000000 BDX will be returned as 1000000000). + /// /// - \p height the height of the current top block. (Note that this is one less than the - KV_MAP_SERIALIZABLE /// "blockchain height" as would be returned by the \c get_info endpoint). - }; /// - \p target_height the target height of the blockchain; will be greater than height+1 if this - + /// "blockchain height" as would be returned by the \c get_info endpoint). + /// - \p target_height the target height of the blockchain; will be greater than height+1 if this /// node is still syncing the chain. - BELDEX_RPC_DOC_INTROSPECT /// - \p block_hash the hash of the most recent block - struct master_node_contributor /// - \p hardfork the current hardfork version of the daemon - { /// - \p mnode_revision the current mnode revision for non-hardfork, but mandatory, master node - uint64_t amount; // The total amount of locked Loki in atomic units for this contributor. /// updates. - uint64_t reserved; // The amount of Loki in atomic units reserved by this contributor for this Master Node. /// - \p status generic RPC error code; "OK" means the request was successful. - std::string address; // The wallet address for this contributor rewards are sent to and contributions came from. /// - \p unchanged when using poll_block_hash, this value is set to true and results are omitted if - std::vector locked_contributions; // Array of contributions from this contributor. /// the current block hash has not changed from the requested polling block hash. If block hash - - /// has changed this is set to false (and results included). When not polling this value is - KV_MAP_SERIALIZABLE /// omitted entirely. - }; /// - \p master_node_states list of information about all known master nodes; each element is a - + /// - \p block_hash the hash of the most recent block + /// - \p hardfork the current hardfork version of the daemon + /// - \p mnode_revision the current mnode revision for non-hardfork, but mandatory, master node + /// updates. + /// - \p status generic RPC error code; "OK" means the request was successful. + /// - \p unchanged when using poll_block_hash, this value is set to true and results are omitted + /// if the current block hash has not changed from the requested polling block hash. If block + /// hash has changed this is set to false (and results included). When not polling this value + /// is omitted entirely. + /// - \p master_node_states list of information about all known master nodes; each element is a /// dict containing the following keys (which fields are included/omitted can be controlled via - BELDEX_RPC_DOC_INTROSPECT /// the "fields" input parameter): - // Get information on some, all, or a random subset of Master Nodes. /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). + /// the "fields" input parameter): + /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). /// - \p registration_height The height at which the registration for the Master Node arrived /// on the blockchain. /// - \p registration_hf_version The current hard fork at which the registration for the Master @@ -1833,9 +1845,9 @@ namespace cryptonote::rpc { /// server (as received in the last uptime proof). Omitted if we have never received a proof. /// - \p contributors Array of contributors, contributing to this Master Node. Each element is /// a dict containing: - /// - \p amount The total amount of BELDEX staked by this contributor into + /// - \p amount The total amount of BDX staked by this contributor into /// this Master Node. - /// - \p reserved The amount of BELDEX reserved by this contributor for this Master Node; this + /// - \p reserved The amount of BDX reserved by this contributor for this Master Node; this /// field will be included only if the contributor has unfilled, reserved space in the /// master node. /// - \p address The wallet address of this contributor to which rewards are sent and from @@ -1845,22 +1857,22 @@ namespace cryptonote::rpc { /// Each element contains: /// - \p key_image The contribution's key image which is locked on the network. /// - \p key_image_pub_key The contribution's key image, public key component. - /// - \p amount The amount of BELDEX that is locked in this contribution. + /// - \p amount The amount of BDX that is locked in this contribution. /// - /// - \p total_contributed The total amount of BELDEX contributed to this Master Node. - /// - \p total_reserved The total amount of BELDEX contributed or reserved for this Master Node. + /// - \p total_contributed The total amount of BDX contributed to this Master Node. + /// - \p total_reserved The total amount of BDX contributed or reserved for this Master Node. /// Only included in the response if there are still unfilled reservations (i.e. if it is /// greater than total_contributed). - /// - \p staking_requirement The total BELDEX staking requirement in that is/was required to be + /// - \p staking_requirement The total BDX staking requirement in that is/was required to be /// contributed for this Master Node. /// - \p portions_for_operator The operator fee to take from the master node reward, as a /// fraction of 18446744073709551612 (2^64 - 4) (that is, this number corresponds to 100%). /// Note that some JSON parsers may silently change this value while parsing as typical values /// do not fit into a double without loss of precision. - /// - \p operator_fee The operator fee expressed in millionths (and rounded to the nearest - /// integer value). That is, 1000000 corresponds to a 100% fee, 34567 corresponds to a - /// 3.4567% fee. Note that this number is for human consumption; the actual value that - /// matters for the blockchain is the precise \p portions_for_operator value. + /// - \p operator_fee The operator fee expressed as thousandths of a percent (and rounded to the + /// nearest integer value). That is, 100000 corresponds to a 100% fee, 5456 corresponds to a + /// 5.456% fee. Note that this number is for human consumption; the actual value that matters + /// for the blockchain is the precise \p portions_for_operator value. /// - \p swarm_id The numeric identifier of the Master Node's current swarm. Note that /// returned values can exceed the precision available in a double value, which can result in /// (changed) incorrect values by some JSON parsers. Consider using \p swarm instead if you @@ -1993,7 +2005,7 @@ namespace cryptonote::rpc { }; /// Endpoint to receive an uptime ping from the connected storage server. This is used - /// to record whether the storage server is ready before the service node starts + /// to record whether the storage server is ready before the master node starts /// sending uptime proofs. This is usually called internally from the storage server /// and this endpoint is mostly available for testing purposes. /// @@ -2021,7 +2033,7 @@ namespace cryptonote::rpc { }; /// Endpoint to receive an uptime ping from the connected belnet server. This is used - /// to record whether belnet is ready before the service node starts sending uptime proofs. + /// to record whether belnet is ready before the master node starts sending uptime proofs. /// This is usually called internally from belnet and this endpoint is mostly /// available for testing purposes. /// @@ -2180,20 +2192,20 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - /p start_height The starting block's height. - /// - /p end_height The ending block's height. + /// - \p start_height The starting block's height. + /// - \p end_height The ending block's height. /// /// Output values available from a public RPC endpoint: /// - /// - /p status Generic RPC error code. "OK" is the success value. - /// - /p untrusted If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. - /// - /p total_deregister - /// - /p total_ip_change_penalty - /// - /p total_decommission - /// - /p total_recommission - /// - /p total_unlock - /// - /p start_height - /// - /p end_height + /// - \p status Generic RPC error code. "OK" is the success value. + /// - \p untrusted If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// - \p total_deregister + /// - \p total_ip_change_penalty + /// - \p total_decommission + /// - \p total_recommission + /// - \p total_unlock + /// - \p start_height + /// - \p end_height struct GET_MN_STATE_CHANGES : PUBLIC { static constexpr auto names() { return NAMES("get_master_nodes_state_changes"); } @@ -2207,17 +2219,17 @@ namespace cryptonote::rpc { }; - /// Reports service node peer status (success/fail) from belnet and storage server. + /// Reports master node peer status (success/fail) from belnet and storage server. /// /// Inputs: /// - /// - /p type test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. - /// - /p pubkey service node pubkey - /// - /p passed whether node is passing the test + /// - \p type test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. + /// - \p pubkey master node pubkey + /// - \p passed whether node is passing the test /// /// Output values available from a public RPC endpoint: /// - /// - /p status Generic RPC error code. "OK" is the success value. + /// - \p status Generic RPC error code. "OK" is the success value. struct REPORT_PEER_STATUS : RPC_COMMAND { // TODO: remove the `report_peer_storage_server_status` once we require a storage server version @@ -2239,7 +2251,7 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// - /p status Generic RPC error code. "OK" is the success value. + /// - \p status Generic RPC error code. "OK" is the success value. struct TEST_TRIGGER_P2P_RESYNC : NO_ARGS { static constexpr auto names() { return NAMES("test_trigger_p2p_resync"); } @@ -2252,7 +2264,7 @@ namespace cryptonote::rpc { /// /// Output values available from a public RPC endpoint: /// - /// - /p status Generic RPC error code. "OK" is the success value. + /// - \p status Generic RPC error code. "OK" is the success value. struct TEST_TRIGGER_UPTIME_PROOF : NO_ARGS { static constexpr auto names() { return NAMES("test_trigger_uptime_proof"); } @@ -2435,12 +2447,12 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - /p bad_txs Clear the cache storing TXs that failed verification. - /// - /p bad_blocks Clear the cache storing blocks that failed verfication. + /// - \p bad_txs Clear the cache storing TXs that failed verification. + /// - \p bad_blocks Clear the cache storing blocks that failed verfication. /// /// Output values available from a public RPC endpoint: /// - /// - /p status Generic RPC error code. "OK" is the success value. + /// - \p status Generic RPC error code. "OK" is the success value. struct FLUSH_CACHE : RPC_COMMAND { static constexpr auto names() { return NAMES("flush_cache"); } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 11c04c71640..c27510474ba 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -56,6 +56,7 @@ #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" +#include "epee/profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" #include "serialization/string.h" diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index 6c305755890..629bbcd5e36 100755 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -192,7 +192,7 @@ namespace m_thread_count = (std::max)(min_thread_count, boost::thread::hardware_concurrency() / 2); m_tcp_server.get_config_object().set_handler(&m_commands_handler); - m_tcp_server.get_config_object().m_invoke_timeout = CONNECTION_TIMEOUT; + m_tcp_server.get_config_object().m_invoke_timeout = 1ms * CONNECTION_TIMEOUT; ASSERT_TRUE(m_tcp_server.init_server(clt_port, "127.0.0.1")); ASSERT_TRUE(m_tcp_server.run_server(m_thread_count, false)); diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index 13a55cb2632..a6294e4cdb4 100755 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -229,7 +229,7 @@ int main(int argc, char** argv) srv_levin_commands_handler *commands_handler = new srv_levin_commands_handler(tcp_server); tcp_server.get_config_object().set_handler(commands_handler, [](epee::levin::levin_commands_handler *handler) { delete handler; }); - tcp_server.get_config_object().m_invoke_timeout = 10000; + tcp_server.get_config_object().m_invoke_timeout = 1s; //tcp_server.get_config_object().m_max_packet_size = max_packet_size; if (!tcp_server.run_server(thread_count, true)) From 20d3bfcc2b932b750bf7f346dadf9eb2344f15a8 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 15:02:09 +0530 Subject: [PATCH 080/182] RPC: GET_LAST_BLOCK_HEADER updated --- src/daemon/rpc_command_executor.cpp | 7 +- src/rpc/core_rpc_server.cpp | 24 ++-- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 5 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 122 +++++++++++++++------ src/rpc/core_rpc_server_commands_defs.h | 34 +++--- 7 files changed, 132 insertions(+), 63 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 510cc8f9f87..3bbb6d93626 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2048,14 +2048,13 @@ bool rpc_command_executor::prepare_registration(bool force_registration) // Query the latest block we've synced and check that the timestamp is sensible, issue a warning if not { - GET_LAST_BLOCK_HEADER::response res{}; - - if (!invoke({}, res, "Get latest block failed, unable to check sync status")) + auto const& maybe_header = try_running([this] { return invoke().at("block_header").get();}, "Get latest block failed, unable to check sync status"); + if (!maybe_header) return false; - auto const& header = res.block_header; uint64_t const now = time(nullptr); + auto const& header = *maybe_header; if (now >= header.timestamp) { uint64_t delta = now - header.timestamp; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f05e2361787..2b7f8b2b55a 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1672,23 +1672,29 @@ namespace cryptonote::rpc { return true; } //------------------------------------------------------------------------------------------------------------------------------ - GET_LAST_BLOCK_HEADER::response core_rpc_server::invoke(GET_LAST_BLOCK_HEADER::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context) { - GET_LAST_BLOCK_HEADER::response res{}; - PERF_TIMER(on_get_last_block_header); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - CHECK_CORE_READY(); + if(!check_core_ready()) + { + get_last_block_header.response["status"] = STATUS_BUSY; + return; + } + auto [last_block_height, last_block_hash] = m_core.get_blockchain_top(); block last_block; bool have_last_block = m_core.get_block_by_height(last_block_height, last_block); if (!have_last_block) throw rpc_error{ERROR_INTERNAL, "Internal error: can't get last block."}; - fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header, req.fill_pow_hash && context.admin, req.get_tx_hashes); - res.status = STATUS_OK; - return res; + + block_header_response header{}; + fill_block_header_response(last_block, false, last_block_height, last_block_hash, header, get_last_block_header.request.fill_pow_hash && context.admin, get_last_block_header.request.get_tx_hashes); + + nlohmann::json header_as_json = header; + get_last_block_header.response["block_header"] = header_as_json; + get_last_block_header.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ GET_BLOCK_HEADER_BY_HASH::response core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HASH::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index cf9d5750e8d..99b17ee5bfd 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -246,6 +246,7 @@ namespace cryptonote::rpc { void invoke(REPORT_PEER_STATUS& report_peer_status, rpc_context context); void invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context); void invoke(FLUSH_CACHE& flush_cache, rpc_context context); + void invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context); void invoke(GETBANS& get_bans, rpc_context context); void invoke(SETBANS& set_bans, rpc_context context); @@ -263,7 +264,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - GET_LAST_BLOCK_HEADER::response invoke(GET_LAST_BLOCK_HEADER::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HASH::response invoke(GET_BLOCK_HEADER_BY_HASH::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 27cc5ed876f..584659d1909 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -480,6 +480,11 @@ namespace cryptonote::rpc { get_values(in, "bad_blocks", flush_cache.request.bad_blocks); } + void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in) { + get_values(in, "fill_pow_hash", get_last_block_header.request.fill_pow_hash); + get_values(in, "get_tx_hashes", get_last_block_header.request.get_tx_hashes); + } + void parse_request(SETBANS& set_bans, rpc_input in) { get_values(in, "host", set_bans.request.host); get_values(in, "ip", set_bans.request.ip); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index c3c486819d3..e3fa41cd65a 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -44,5 +44,6 @@ namespace cryptonote::rpc { void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); + void parse_request(GET_LAST_BLOCK_HEADER& get_lash_block_header, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 7ea4d3929a0..1c023efc27e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -10,6 +10,64 @@ void RPC_COMMAND::set_bt() { response_hex.format = json_binary_proxy::fmt::bt; } +void to_json(nlohmann::json& j, const block_header_response& h) +{ + j = nlohmann::json + { + {"major_version", h.major_version}, + {"minor_version", h.minor_version}, + {"timestamp", h.timestamp}, + {"prev_hash", h.prev_hash}, + {"nonce", h.nonce}, + {"orphan_status", h.orphan_status}, + {"height", h.height}, + {"depth", h.depth}, + {"hash", h.hash}, + {"difficulty", h.difficulty}, + {"cumulative_difficulty", h.cumulative_difficulty}, + {"reward", h.reward}, + {"miner_reward", h.miner_reward}, + {"block_size", h.block_size}, + {"block_weight", h.block_weight}, + {"num_txes", h.num_txes}, + {"pow_hash", h.pow_hash ? *h.pow_hash : nullptr}, + {"long_term_weight", h.long_term_weight}, + {"miner_tx_hash", h.miner_tx_hash}, + {"miner_tx_hash", h.miner_tx_hash}, + {"tx_hashes", h.tx_hashes}, + {"master_node_winner", h.master_node_winner}, + }; +}; + +void from_json(const nlohmann::json& j, block_header_response& h) +{ + j.at("major_version").get_to(h.major_version); + j.at("minor_version").get_to(h.minor_version); + j.at("timestamp").get_to(h.timestamp); + j.at("prev_hash").get_to(h.prev_hash); + j.at("nonce").get_to(h.nonce); + j.at("orphan_status").get_to(h.orphan_status); + j.at("height").get_to(h.height); + j.at("depth").get_to(h.depth); + j.at("hash").get_to(h.hash); + j.at("difficulty").get_to(h.difficulty); + j.at("cumulative_difficulty").get_to(h.cumulative_difficulty); + j.at("reward").get_to(h.reward); + j.at("miner_reward").get_to(h.miner_reward); + j.at("block_size").get_to(h.block_size); + j.at("block_weight").get_to(h.block_weight); + j.at("num_txes").get_to(h.num_txes); + if (j.at("pow_hash").is_null()) + h.pow_hash = std::nullopt; + else + h.pow_hash = j["pow_hash"].get(); + j.at("long_term_weight").get_to(h.long_term_weight); + j.at("miner_tx_hash").get_to(h.miner_tx_hash); + j.at("miner_tx_hash").get_to(h.miner_tx_hash); + j.at("tx_hashes").get_to(h.tx_hashes); + j.at("master_node_winner").get_to(h.master_node_winner); +} + KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() @@ -269,42 +327,42 @@ KV_SERIALIZE_MAP_CODE_END() // } -KV_SERIALIZE_MAP_CODE_BEGIN(block_header_response) - KV_SERIALIZE(major_version) - KV_SERIALIZE(minor_version) - KV_SERIALIZE(timestamp) - KV_SERIALIZE(prev_hash) - KV_SERIALIZE(nonce) - KV_SERIALIZE(orphan_status) - KV_SERIALIZE(height) - KV_SERIALIZE(depth) - KV_SERIALIZE(hash) - KV_SERIALIZE(difficulty) - KV_SERIALIZE(cumulative_difficulty) - KV_SERIALIZE(reward) - KV_SERIALIZE(miner_reward) - KV_SERIALIZE(block_size) - KV_SERIALIZE_OPT(block_weight, (uint64_t)0) - KV_SERIALIZE(num_txes) - KV_SERIALIZE(pow_hash) - KV_SERIALIZE_OPT(long_term_weight, (uint64_t)0) - KV_SERIALIZE(miner_tx_hash) - KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(master_node_winner) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(block_header_response) +// KV_SERIALIZE(major_version) +// KV_SERIALIZE(minor_version) +// KV_SERIALIZE(timestamp) +// KV_SERIALIZE(prev_hash) +// KV_SERIALIZE(nonce) +// KV_SERIALIZE(orphan_status) +// KV_SERIALIZE(height) +// KV_SERIALIZE(depth) +// KV_SERIALIZE(hash) +// KV_SERIALIZE(difficulty) +// KV_SERIALIZE(cumulative_difficulty) +// KV_SERIALIZE(reward) +// KV_SERIALIZE(miner_reward) +// KV_SERIALIZE(block_size) +// KV_SERIALIZE_OPT(block_weight, (uint64_t)0) +// KV_SERIALIZE(num_txes) +// KV_SERIALIZE(pow_hash) +// KV_SERIALIZE_OPT(long_term_weight, (uint64_t)0) +// KV_SERIALIZE(miner_tx_hash) +// KV_SERIALIZE(tx_hashes) +// KV_SERIALIZE(master_node_winner) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_LAST_BLOCK_HEADER::request) - KV_SERIALIZE_OPT(fill_pow_hash, false); - KV_SERIALIZE_OPT(get_tx_hashes, false); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_LAST_BLOCK_HEADER::request) +// KV_SERIALIZE_OPT(fill_pow_hash, false); +// KV_SERIALIZE_OPT(get_tx_hashes, false); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_LAST_BLOCK_HEADER::response) - KV_SERIALIZE(block_header) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_LAST_BLOCK_HEADER::response) +// KV_SERIALIZE(block_header) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HASH::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 54fa6d4c37a..b8d90fae886 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -761,28 +761,28 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; - BELDEX_RPC_DOC_INTROSPECT - // Block header information for the most recent block is easily retrieved with this method. No inputs are needed. + void to_json(nlohmann::json& j, const block_header_response& h); + void from_json(const nlohmann::json& j, block_header_response& h); + + /// Block header information for the most recent block is easily retrieved with this method. No inputs are needed. + /// + /// Inputs: + /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. + /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p block_header A structure containing block header information. struct GET_LAST_BLOCK_HEADER : PUBLIC { static constexpr auto names() { return NAMES("get_last_block_header", "getlastblockheader"); } - struct request - { - bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. - bool get_tx_hashes; // If true (default false) then include the hashes of non-coinbase transactions - - KV_MAP_SERIALIZABLE - }; - - struct response + struct request_parameters { - std::string status; // General RPC error code. "OK" means everything looks good. - block_header_response block_header; // A structure containing block header information. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + bool fill_pow_hash; + bool get_tx_hashes; + } request; }; BELDEX_RPC_DOC_INTROSPECT From 23ccd7b5b6ec8d34cd3a05aad3437d9afa467122 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 15:16:49 +0530 Subject: [PATCH 081/182] RPC: GET_CHECKPOINTS updated --- src/blockchain_db/CMakeLists.txt | 1 + src/checkpoints/CMakeLists.txt | 1 + src/checkpoints/checkpoints.cpp | 16 ++++- src/checkpoints/checkpoints.h | 1 + src/cryptonote_basic/CMakeLists.txt | 1 + src/cryptonote_basic/cryptonote_basic.h | 10 +++ src/daemon/rpc_command_executor.cpp | 49 +++++--------- src/device/CMakeLists.txt | 1 + src/multisig/CMakeLists.txt | 1 + src/ringct/CMakeLists.txt | 2 + src/rpc/core_rpc_server.cpp | 33 ++++----- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 46 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 81 ++++------------------- 14 files changed, 102 insertions(+), 143 deletions(-) diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index e833a139bfb..c1e6d6fdbbd 100755 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -39,6 +39,7 @@ target_link_libraries(blockchain_db lmdb filesystem Boost::thread + nlohmann_json::nlohmann_json extra) target_compile_definitions(blockchain_db PRIVATE diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt index 0eb84f9443d..77c293f8c31 100755 --- a/src/checkpoints/CMakeLists.txt +++ b/src/checkpoints/CMakeLists.txt @@ -37,5 +37,6 @@ target_link_libraries(checkpoints cryptonote_basic Boost::program_options Boost::serialization + nlohmann_json::nlohmann_json filesystem extra) diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 59ec50f4576..1f77aaeaaea 100755 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -36,6 +36,7 @@ #include "epee/serialization/keyvalue_serialization.h" #include "cryptonote_core/master_node_rules.h" #include +#include #include "blockchain_db/blockchain_db.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -55,7 +56,20 @@ namespace cryptonote if (result) MINFO ("CHECKPOINT PASSED FOR HEIGHT " << height << " " << block_hash); else MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH " << block_hash << "GIVEN HASH: " << hash); return result; - } + }; + + void to_json(nlohmann::json& j, const checkpoint_t& c) + { + j = nlohmann::json + { + {"version", c.version}, + {"type", c.type}, + {"height", c.height}, + {"block_hash", tools::type_to_hex(c.block_hash)}, + {"signatures", c.signatures}, + {"prev_height", c.prev_height}, + }; + }; height_to_hash const HARDCODED_MAINNET_CHECKPOINTS[] = { diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index 066487d373a..9f8027f0d4f 100755 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -79,6 +79,7 @@ namespace cryptonote FIELD(prev_height) END_SERIALIZE() }; + void to_json(nlohmann::json& j, const checkpoint_t& c); struct height_to_hash { diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index e503d9e5038..a14d1d8d2ff 100755 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -44,4 +44,5 @@ target_link_libraries(cryptonote_basic Boost::program_options Boost::serialization filesystem + nlohmann_json::nlohmann_json extra) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index c5a904288f0..d5eff79cb82 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -45,6 +45,7 @@ #include "ringct/rctTypes.h" #include "device/device.hpp" #include "txtypes.h" +#include namespace master_nodes { @@ -65,6 +66,15 @@ namespace master_nodes FIELD(signature) END_SERIALIZE() }; + + inline void to_json(nlohmann::json& j, const quorum_signature& s) + { + j = nlohmann::json + { + {"voter_index", s.voter_index}, + {"signature", tools::type_to_hex(s.signature)}, + }; + }; }; namespace cryptonote diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 3bbb6d93626..33c86c0decd 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -276,58 +276,45 @@ json rpc_command_executor::invoke( bool rpc_command_executor::print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json) { - GET_CHECKPOINTS::request req{start_height, end_height}; - if (req.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE && - req.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) + uint32_t count; + if (start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE && + end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) { - req.count = GET_CHECKPOINTS::NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT; + count = GET_CHECKPOINTS::NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT; } - else if (req.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE || - req.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) + else if (start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE || + end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) { - req.count = 1; + count = 1; } // Otherwise, neither heights are set to HEIGHT_SENTINEL_VALUE, so get all the checkpoints between start and end - GET_CHECKPOINTS::response res{}; - if (!invoke(std::move(req), res, "Failed to query blockchain checkpoints")) + auto maybe_checkpoints = try_running([&] { return invoke(json{{"start_height", start_height}, {"end_height", end_height}, {"count", count}}); }, "Failed to query blockchain checkpoints"); + if (!maybe_checkpoints) return false; + auto checkpoints = *maybe_checkpoints; + std::string entry; - if (print_json) entry.append("{\n\"checkpoints\": ["); - for (size_t i = 0; i < res.checkpoints.size(); i++) - { - GET_CHECKPOINTS::checkpoint_serialized &checkpoint = res.checkpoints[i]; - if (print_json) - { - entry.append("\n"); - entry.append(epee::serialization::store_t_to_json(checkpoint)); - entry.append(",\n"); - } - else + if (print_json) + entry.append(checkpoints.dump()); + else { + for (size_t i = 0; i < checkpoints.size(); i++) { entry.append("["); entry.append(std::to_string(i)); entry.append("]"); entry.append(" Type: "); - entry.append(checkpoint.type); + entry.append(checkpoints[i]["type"]); entry.append(" Height: "); - entry.append(std::to_string(checkpoint.height)); + entry.append(checkpoints[i]["height"]); entry.append(" Hash: "); - entry.append(checkpoint.block_hash); + entry.append(checkpoints[i]["block_hash"]); entry.append("\n"); } - } - - if (print_json) - { - entry.append("]\n}"); - } - else - { if (entry.empty()) entry.append("No Checkpoints"); } diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index 54d6783342e..97a678fb23d 100755 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -41,6 +41,7 @@ target_link_libraries(device Boost::serialization PRIVATE version + nlohmann_json::nlohmann_json extra) option(HWDEVICE_DEBUG "Enable hardware wallet debugging (requires also using a debug build of the Ledger wallet)" OFF) diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index a529cea2579..8e885773353 100755 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -35,4 +35,5 @@ target_link_libraries(multisig ringct cryptonote_basic common + nlohmann_json::nlohmann_json extra) diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index d27425e0c10..ae4c0ba5a47 100755 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -38,6 +38,7 @@ target_link_libraries(ringct_basic PUBLIC common PRIVATE + nlohmann_json::nlohmann_json extra) add_library(ringct @@ -49,4 +50,5 @@ target_link_libraries(ringct cryptonote_basic device PRIVATE + nlohmann_json::nlohmann_json extra) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 2b7f8b2b55a..53784f22be0 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3063,45 +3063,38 @@ namespace cryptonote::rpc { } } //------------------------------------------------------------------------------------------------------------------------------ - GET_CHECKPOINTS::response core_rpc_server::invoke(GET_CHECKPOINTS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context) { - GET_CHECKPOINTS::response res{}; - - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - if (!context.admin) - check_quantity_limit(req.count, GET_CHECKPOINTS::MAX_COUNT); + check_quantity_limit(get_checkpoints.request.count, GET_CHECKPOINTS::MAX_COUNT); - res.status = STATUS_OK; + get_checkpoints.response["status"] = STATUS_OK; BlockchainDB const &db = m_core.get_blockchain_storage().get_db(); std::vector checkpoints; - if (req.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE && - req.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) + if (get_checkpoints.request.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE && + get_checkpoints.request.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) { checkpoint_t top_checkpoint; if (db.get_top_checkpoint(top_checkpoint)) - checkpoints = db.get_checkpoints_range(top_checkpoint.height, 0, req.count); + checkpoints = db.get_checkpoints_range(top_checkpoint.height, 0, get_checkpoints.request.count); } - else if (req.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) + else if (get_checkpoints.request.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) { - checkpoints = db.get_checkpoints_range(req.end_height, 0, req.count); + checkpoints = db.get_checkpoints_range(get_checkpoints.request.end_height, 0, get_checkpoints.request.count); } - else if (req.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) + else if (get_checkpoints.request.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) { - checkpoints = db.get_checkpoints_range(req.start_height, UINT64_MAX, req.count); + checkpoints = db.get_checkpoints_range(get_checkpoints.request.start_height, UINT64_MAX, get_checkpoints.request.count); } else { - checkpoints = db.get_checkpoints_range(req.start_height, req.end_height); + checkpoints = db.get_checkpoints_range(get_checkpoints.request.start_height, get_checkpoints.request.end_height); } - res.checkpoints.reserve(checkpoints.size()); - for (checkpoint_t const &checkpoint : checkpoints) - res.checkpoints.push_back(checkpoint); + get_checkpoints.response["checkpoints"] = checkpoints; - return res; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 99b17ee5bfd..c053d4e04b0 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -249,6 +249,7 @@ namespace cryptonote::rpc { void invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context); void invoke(GETBANS& get_bans, rpc_context context); void invoke(SETBANS& set_bans, rpc_context context); + void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -278,7 +279,6 @@ namespace cryptonote::rpc { GET_MASTER_KEYS::response invoke(GET_MASTER_KEYS::request&& req, rpc_context context); GET_MASTER_PRIVKEYS::response invoke(GET_MASTER_PRIVKEYS::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); - GET_CHECKPOINTS::response invoke(GET_CHECKPOINTS::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 1c023efc27e..daa47c4837e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -66,7 +66,7 @@ void from_json(const nlohmann::json& j, block_header_response& h) j.at("miner_tx_hash").get_to(h.miner_tx_hash); j.at("tx_hashes").get_to(h.tx_hashes); j.at("master_node_winner").get_to(h.master_node_winner); -} +}; KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) @@ -1143,34 +1143,34 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::request) - KV_SERIALIZE_OPT(start_height, HEIGHT_SENTINEL_VALUE) - KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) - KV_SERIALIZE_OPT(count, NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::request) +// KV_SERIALIZE_OPT(start_height, HEIGHT_SENTINEL_VALUE) +// KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) +// KV_SERIALIZE_OPT(count, NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::quorum_signature_serialized) - KV_SERIALIZE(voter_index); - KV_SERIALIZE(signature); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::quorum_signature_serialized) +// KV_SERIALIZE(voter_index); +// KV_SERIALIZE(signature); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::checkpoint_serialized) - KV_SERIALIZE(version); - KV_SERIALIZE(type); - KV_SERIALIZE(height); - KV_SERIALIZE(block_hash); - KV_SERIALIZE(signatures); - KV_SERIALIZE(prev_height); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::checkpoint_serialized) +// KV_SERIALIZE(version); +// KV_SERIALIZE(type); +// KV_SERIALIZE(height); +// KV_SERIALIZE(block_hash); +// KV_SERIALIZE(signatures); +// KV_SERIALIZE(prev_height); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::response) - KV_SERIALIZE(checkpoints) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::response) +// KV_SERIALIZE(checkpoints) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_MN_STATE_CHANGES::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index b8d90fae886..fa0c1bbbb80 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2108,8 +2108,18 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + /// + /// Inputs: + /// + /// - \p start_height Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. + /// - \p end_height Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. + /// - \p count Optional: Number of checkpoints to query. + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status generic RPC error code; "OK" means the request was successful. + /// - \p checkpoints Array of requested checkpoints struct GET_CHECKPOINTS : PUBLIC { static constexpr auto names() { return NAMES("get_checkpoints"); } @@ -2117,75 +2127,12 @@ namespace cryptonote::rpc { static constexpr size_t MAX_COUNT = 256; static constexpr uint32_t NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT = 60; static constexpr uint64_t HEIGHT_SENTINEL_VALUE = std::numeric_limits::max() - 1; - struct request + struct request_parameters { uint64_t start_height; // Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. uint64_t end_height; // Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. uint32_t count; // Optional: Number of checkpoints to query. - - KV_MAP_SERIALIZABLE - }; - - struct quorum_signature_serialized - { - uint16_t voter_index; // Index of the voter in the relevant quorum - std::string signature; // The signature generated by the voter in the quorum - - quorum_signature_serialized() = default; - quorum_signature_serialized(master_nodes::quorum_signature const &entry) - : voter_index(entry.voter_index) - , signature(tools::type_to_hex(entry.signature)) { } - - KV_MAP_SERIALIZABLE - - BEGIN_SERIALIZE() // NOTE: For store_t_to_json - FIELD(voter_index) - FIELD(signature) - END_SERIALIZE() - }; - - struct checkpoint_serialized - { - uint8_t version; - std::string type; // Either "Hardcoded" or "MasterNode" for checkpoints generated by Master Nodes or declared in the code - uint64_t height; // The height the checkpoint is relevant for - std::string block_hash; // The block hash the checkpoint is specifying - std::vector signatures; // Signatures from Master Nodes who agree on the block hash - uint64_t prev_height; // The previous height the checkpoint is based off - - checkpoint_serialized() = default; - checkpoint_serialized(checkpoint_t const &checkpoint) - : version(checkpoint.version) - , type(checkpoint_t::type_to_string(checkpoint.type)) - , height(checkpoint.height) - , block_hash(tools::type_to_hex(checkpoint.block_hash)) - , prev_height(checkpoint.prev_height) - { - signatures.reserve(checkpoint.signatures.size()); - for (master_nodes::quorum_signature const &entry : checkpoint.signatures) - signatures.push_back(entry); - } - - KV_MAP_SERIALIZABLE - - BEGIN_SERIALIZE() // NOTE: For store_t_to_json - FIELD(version) - FIELD(type) - FIELD(height) - FIELD(block_hash) - FIELD(signatures) - FIELD(prev_height) - END_SERIALIZE() - }; - - struct response - { - std::vector checkpoints; // Array of requested checkpoints - std::string status; // Generic RPC error code. "OK" is the success value. - bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. - - KV_MAP_SERIALIZABLE - }; + } request; }; /// Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. From 96c82252fe1c936adc244904c85b49a9728bc311 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 16:04:38 +0530 Subject: [PATCH 082/182] RPC: GET_BLOCK_HEADER_BY_HASH updated --- src/daemon/rpc_command_executor.cpp | 19 +++++++------ src/rpc/core_rpc_server.cpp | 29 ++++++++++---------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 7 +++++ src/rpc/core_rpc_server_command_parser.h | 3 ++- src/rpc/core_rpc_server_commands_defs.cpp | 24 ++++++++--------- src/rpc/core_rpc_server_commands_defs.h | 31 +++++++++++----------- 7 files changed, 61 insertions(+), 54 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 33c86c0decd..c1af37922cf 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1316,21 +1316,20 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, for (const std::string &block_id: chain.block_hashes) tools::msg_writer() << " " << block_id; tools::msg_writer() << "Chain parent on main chain: " << chain.main_chain_parent_block; - GET_BLOCK_HEADER_BY_HASH::request bhreq{}; - GET_BLOCK_HEADER_BY_HASH::response bhres{}; - bhreq.hashes = chain.block_hashes; - bhreq.hashes.push_back(chain.main_chain_parent_block); - bhreq.fill_pow_hash = false; - if (!invoke(std::move(bhreq), bhres, "Failed to query block header by hash")) + std::vector hashes{chain.block_hashes}; + hashes.push_back(chain.main_chain_parent_block); + auto maybe_headers = try_running([&] { return invoke(json{{"hashes", hashes}, {"fill_pow_hash", false}}); }, "Failed to query block header by hash"); + if (!maybe_headers) return false; - if (bhres.block_headers.size() != chain.length + 1) + auto headers = *maybe_headers; + if (headers["block_headers"].size() != chain.length + 1) { tools::fail_msg_writer() << "Failed to get block header info for alt chain"; return true; } - uint64_t t0 = bhres.block_headers.front().timestamp, t1 = t0; - for (const block_header_response &block_header: bhres.block_headers) + uint64_t t0 = headers["block_headers"].front()["timestamp"], t1 = t0; + for (const block_header_response &block_header: headers["block_headers"]) { t0 = std::min(t0, block_header.timestamp); t1 = std::max(t1, block_header.timestamp); @@ -1341,7 +1340,7 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, if (chain.length > 1) { tools::msg_writer() << "Time span: " << tools::get_human_readable_timespan(std::chrono::seconds(dt)); - cryptonote::difficulty_type start_difficulty = bhres.block_headers.back().difficulty; + cryptonote::difficulty_type start_difficulty = headers["block_headers"].back()["difficulty"]; if (start_difficulty > 0) tools::msg_writer() << "Approximated " << 100.f * tools::to_seconds(cryptonote::old::TARGET_BLOCK_TIME_12) * chain.length / dt << "% of network hash rate"; //old block time else diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 53784f22be0..12493703cab 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1697,15 +1697,11 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - GET_BLOCK_HEADER_BY_HASH::response core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HASH::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_context context) { - GET_BLOCK_HEADER_BY_HASH::response res{}; - PERF_TIMER(on_get_block_header_by_hash); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - auto get = [this, &req, admin=context.admin](const std::string &hash, block_header_response &block_header) { + auto get = [this, &get_block_header_by_hash, admin=context.admin](const std::string &hash, block_header_response &block_header) { crypto::hash block_hash; if (!tools::hex_to_type(hash, block_hash)) throw rpc_error{ERROR_WRONG_PARAM, "Failed to parse hex representation of block hash. Hex = " + hash + '.'}; @@ -1717,18 +1713,23 @@ namespace cryptonote::rpc { if (blk.miner_tx.vin.size() != 1 || !std::holds_alternative(blk.miner_tx.vin.front())) throw rpc_error{ERROR_INTERNAL, "Internal error: coinbase transaction in the block has the wrong type"}; uint64_t block_height = var::get(blk.miner_tx.vin.front()).height; - fill_block_header_response(blk, orphan, block_height, block_hash, block_header, req.fill_pow_hash && admin, req.get_tx_hashes); + fill_block_header_response(blk, orphan, block_height, block_hash, block_header, get_block_header_by_hash.request.fill_pow_hash && admin, get_block_header_by_hash.request.get_tx_hashes); }; - if (!req.hash.empty()) - get(req.hash, res.block_header.emplace()); + if (!get_block_header_by_hash.request.hash.empty()) + { + block_header_response block_header; + get(get_block_header_by_hash.request.hash, block_header); + get_block_header_by_hash.response["block_header"] = block_header; + } - res.block_headers.reserve(req.hashes.size()); - for (const std::string &hash: req.hashes) - get(hash, res.block_headers.emplace_back()); + std::vector block_headers; + for (const std::string &hash: get_block_header_by_hash.request.hashes) + get(hash, block_headers.emplace_back()); - res.status = STATUS_OK; - return res; + get_block_header_by_hash.response["block_headers"] = block_headers; + get_block_header_by_hash.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ GET_BLOCK_HEADERS_RANGE::response core_rpc_server::invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index c053d4e04b0..a6d127a2c84 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -247,6 +247,7 @@ namespace cryptonote::rpc { void invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context); void invoke(FLUSH_CACHE& flush_cache, rpc_context context); void invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context); + void invoke(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_context context); void invoke(GETBANS& get_bans, rpc_context context); void invoke(SETBANS& set_bans, rpc_context context); void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); @@ -265,7 +266,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - GET_BLOCK_HEADER_BY_HASH::response invoke(GET_BLOCK_HEADER_BY_HASH::request&& req, rpc_context context); GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 584659d1909..c30cf8fe92d 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -485,6 +485,13 @@ namespace cryptonote::rpc { get_values(in, "get_tx_hashes", get_last_block_header.request.get_tx_hashes); } + void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in) { + get_values(in, "hash", get_block_header_by_hash.request.hash); + get_values(in, "hashes", get_block_header_by_hash.request.hashes); + get_values(in, "fill_pow_hash", get_block_header_by_hash.request.fill_pow_hash); + get_values(in, "get_tx_hashes", get_block_header_by_hash.request.get_tx_hashes); + } + void parse_request(SETBANS& set_bans, rpc_input in) { get_values(in, "host", set_bans.request.host); get_values(in, "ip", set_bans.request.ip); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index e3fa41cd65a..bab0fe45153 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -44,6 +44,7 @@ namespace cryptonote::rpc { void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); - void parse_request(GET_LAST_BLOCK_HEADER& get_lash_block_header, rpc_input in); + void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); + void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index daa47c4837e..fc0c08eb4c3 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -365,20 +365,20 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HASH::request) - KV_SERIALIZE(hash) - KV_SERIALIZE(hashes) - KV_SERIALIZE_OPT(fill_pow_hash, false); - KV_SERIALIZE_OPT(get_tx_hashes, false); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HASH::request) +// KV_SERIALIZE(hash) +// KV_SERIALIZE(hashes) +// KV_SERIALIZE_OPT(fill_pow_hash, false); +// KV_SERIALIZE_OPT(get_tx_hashes, false); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HASH::response) - KV_SERIALIZE(block_header) - KV_SERIALIZE(block_headers) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HASH::response) +// KV_SERIALIZE(block_header) +// KV_SERIALIZE(block_headers) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HEIGHT::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index fa0c1bbbb80..1a865207203 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -785,31 +785,30 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Block header information can be retrieved using either a block's hash or height. This method includes a block's hash as an input parameter to retrieve basic information about the block. + /// Block header information can be retrieved using either a block's hash or height. This method includes a block's hash as an input parameter to retrieve basic information about the block. + /// + /// Inputs: + /// - \p hash The block's SHA256 hash. + /// - \p hashes Request multiple blocks via an array of hashes + /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. + /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p block_header Block header information for the requested `hash` block + /// - \p block_headers Block header information for the requested `hashes` blocks struct GET_BLOCK_HEADER_BY_HASH : PUBLIC { static constexpr auto names() { return NAMES("get_block_header_by_hash", "getblockheaderbyhash"); } - struct request + struct request_parameters { std::string hash; // The block's SHA256 hash. std::vector hashes; // Request multiple blocks via an array of hashes bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. bool get_tx_hashes; // If true (default false) then include the hashes of non-coinbase transactions - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::optional block_header; // Block header information for the requested `hash` block - std::vector block_headers; // Block header information for the requested `hashes` blocks - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + } request; }; BELDEX_RPC_DOC_INTROSPECT From 8e79d8574987198b799561a1f8d23abec0c702b0 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 16:24:24 +0530 Subject: [PATCH 083/182] RPC: GET_MASTER_PRIVKEYS updated --- src/rpc/core_rpc_server.cpp | 15 +++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 12 ++++----- src/rpc/core_rpc_server_commands_defs.h | 30 ++++++++++------------- 4 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 12493703cab..ebbe3b8ed9b 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2736,19 +2736,16 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_PRIVKEYS::response core_rpc_server::invoke(GET_MASTER_PRIVKEYS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context) { - GET_MASTER_PRIVKEYS::response res{}; - PERF_TIMER(on_get_master_node_key); - const auto& keys = m_core.get_master_keys(); if (keys.key != crypto::null_skey) - res.master_node_privkey = tools::type_to_hex(keys.key.data); - res.master_node_ed25519_privkey = tools::type_to_hex(keys.key_ed25519.data); - res.master_node_x25519_privkey = tools::type_to_hex(keys.key_x25519.data); - res.status = STATUS_OK; - return res; + get_master_privkeys.response["master_node_privkey"] = tools::type_to_hex(keys.key.data); + get_master_privkeys.response["master_node_ed25519_privkey"] = tools::type_to_hex(keys.key_ed25519.data); + get_master_privkeys.response["master_node_x25519_privkey"] = tools::type_to_hex(keys.key_x25519.data); + get_master_privkeys.response["status"] = STATUS_OK; + return; } static time_t reachable_to_time_t( diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index a6d127a2c84..50ccb452031 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -251,6 +251,7 @@ namespace cryptonote::rpc { void invoke(GETBANS& get_bans, rpc_context context); void invoke(SETBANS& set_bans, rpc_context context); void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); + void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -277,7 +278,6 @@ namespace cryptonote::rpc { GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::request&& req, rpc_context context); GET_MASTER_KEYS::response invoke(GET_MASTER_KEYS::request&& req, rpc_context context); - GET_MASTER_PRIVKEYS::response invoke(GET_MASTER_PRIVKEYS::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index fc0c08eb4c3..82fc277d463 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -939,12 +939,12 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_KEYS::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_PRIVKEYS::response) - KV_SERIALIZE(master_node_privkey) - KV_SERIALIZE(master_node_ed25519_privkey) - KV_SERIALIZE(master_node_x25519_privkey) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_PRIVKEYS::response) +// KV_SERIALIZE(master_node_privkey) +// KV_SERIALIZE(master_node_ed25519_privkey) +// KV_SERIALIZE(master_node_x25519_privkey) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(master_node_contribution) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1a865207203..636d89a32d0 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1728,26 +1728,22 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get the master private keys of the queried daemon, encoded in hex. Do not ever share - // these keys: they would allow someone to impersonate your master node. All three keys are used - // when running as a master node; when running as a regular node only the x25519 key is regularly - // used for some RPC and and node-to-MN communication requests. + /// Get the master private keys of the queried daemon, encoded in hex. Do not ever share + /// these keys: they would allow someone to impersonate your master node. All three keys are used + /// when running as a master node; when running as a regular node only the x25519 key is regularly + /// used for some RPC and and node-to-MN communication requests. + /// + /// Inputs: None + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p master_node_privkey The queried daemon's master node private key. Will be empty if not running as a master node. + /// - \p master_node_ed25519_privkey The daemon's ed25519 private key (note that this is in sodium's format, which consists of the private and public keys concatenated together) + /// - \p master_node_x25519_privkey The daemon's x25519 private key. struct GET_MASTER_PRIVKEYS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_privkeys", "get_master_node_privkey"); } - - struct request : EMPTY {}; - - struct response - { - std::string master_node_privkey; // The queried daemon's master node private key. Will be empty if not running as a master node. - std::string master_node_ed25519_privkey; // The daemon's ed25519 private key (note that this is in sodium's format, which consists of the private and public keys concatenated together) - std::string master_node_x25519_privkey; // The daemon's x25519 private key. - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE - }; }; /// Get information on some, all, or a random subset of Master Nodes. From 2dcf09b531064c089f7dfc7c12ee9f3608f01fb1 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Tue, 22 Apr 2025 16:27:37 +0530 Subject: [PATCH 084/182] GET_MASTER_KEYS --- src/daemon/rpc_command_executor.cpp | 24 ++++++++----------- src/rpc/core_rpc_server.cpp | 15 +++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 12 +++++----- src/rpc/core_rpc_server_commands_defs.h | 28 ++++++++++------------- 5 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index c1af37922cf..86bf700175d 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -482,12 +482,11 @@ bool rpc_command_executor::show_status() { bool my_mn_registered = false, my_mn_staked = false, my_mn_active = false; uint16_t my_reason_all = 0, my_reason_any = 0; if (info["master_node"].get()) { - GET_MASTER_KEYS::response res{}; - - if (!invoke({}, res, "Failed to retrieve master node keys")) + auto maybe_master_keys = try_running([this] { return invoke(json{}); }, "Failed to retrieve master node keys"); + if (!maybe_master_keys) return false; - my_mn_key = std::move(res.master_node_pubkey); + my_mn_key = (*maybe_master_keys)["master_node_pubkey"]; auto maybe_mns = try_running([&] { return invoke(json{{"master_node_pubkeys", json::array({my_mn_key})}}); }, "Failed to retrieve master node info"); @@ -1942,15 +1941,16 @@ bool rpc_command_executor::pop_blocks(uint64_t num_blocks) bool rpc_command_executor::print_mn_key() { - GET_MASTER_KEYS::response res{}; - - if (!invoke({}, res, "Failed to retrieve master node keys")) + auto maybe_master_keys = try_running([this] { return invoke(json{}); }, "Failed to retrieve master node keys"); + if (!maybe_master_keys) return false; + auto my_mn_keys = *maybe_master_keys; + tools::success_msg_writer() - << "Master Node Public Key: " << res.master_node_pubkey - << "\n Ed25519 Public Key: " << res.master_node_ed25519_pubkey - << "\n X25519 Public Key: " << res.master_node_x25519_pubkey; + << "Master Node Public Key: " << my_mn_keys["master_node_pubkey"] + << "\n Ed25519 Public Key: " << my_mn_keys["master_node_ed25519_pubkey"] + << "\n X25519 Public Key: " << my_mn_keys["master_node_x25519_pubkey"]; return true; } @@ -1996,10 +1996,6 @@ bool rpc_command_executor::prepare_registration(bool force_registration) return false; auto& hfinfo = *maybe_hf; - GET_MASTER_KEYS::response kres{}; - if (!invoke({}, kres, "Failed to retrieve master node keys")) - return false; - if (!info.value("master_node", false)) { tools::fail_msg_writer() << "Unable to prepare registration: this daemon is not running in --master-node mode"; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ebbe3b8ed9b..b95d0ddfcd0 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2721,19 +2721,16 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_KEYS::response core_rpc_server::invoke(GET_MASTER_KEYS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context) { - GET_MASTER_KEYS::response res{}; - PERF_TIMER(on_get_master_node_key); - const auto& keys = m_core.get_master_keys(); if (keys.pub) - res.master_node_pubkey = tools::type_to_hex(keys.pub); - res.master_node_ed25519_pubkey = tools::type_to_hex(keys.pub_ed25519); - res.master_node_x25519_pubkey = tools::type_to_hex(keys.pub_x25519); - res.status = STATUS_OK; - return res; + get_master_keys.response["master_node_pubkey"] = tools::type_to_hex(keys.pub); + get_master_keys.response["master_node_ed25519_pubkey"] = tools::type_to_hex(keys.pub_ed25519); + get_master_keys.response["master_node_x25519_pubkey"] = tools::type_to_hex(keys.pub_x25519); + get_master_keys.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 50ccb452031..33aacf3ad70 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -252,6 +252,7 @@ namespace cryptonote::rpc { void invoke(SETBANS& set_bans, rpc_context context); void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); + void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -277,7 +278,6 @@ namespace cryptonote::rpc { GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::request&& req, rpc_context context); - GET_MASTER_KEYS::response invoke(GET_MASTER_KEYS::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 82fc277d463..d8d80e648f3 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -931,12 +931,12 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD::request) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_KEYS::response) - KV_SERIALIZE(master_node_pubkey) - KV_SERIALIZE(master_node_ed25519_pubkey) - KV_SERIALIZE(master_node_x25519_pubkey) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_KEYS::response) +// KV_SERIALIZE(master_node_pubkey) +// KV_SERIALIZE(master_node_ed25519_pubkey) +// KV_SERIALIZE(master_node_x25519_pubkey) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_PRIVKEYS::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 636d89a32d0..e81fe545359 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1707,25 +1707,21 @@ namespace cryptonote::rpc { using response = GET_MASTER_NODE_REGISTRATION_CMD_RAW::response; }; - BELDEX_RPC_DOC_INTROSPECT - // Get the master public keys of the queried daemon, encoded in hex. All three keys are used - // when running as a master node; when running as a regular node only the x25519 key is regularly - // used for some RPC and and node-to-MN communication requests. + /// Get the master public keys of the queried daemon, encoded in hex. All three keys are used + /// when running as a master node; when running as a regular node only the x25519 key is regularly + /// used for some RPC and and node-to-MN communication requests. + /// + /// Inputs: None + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p master_node_pubkey The queried daemon's master node public key. Will be empty if not running as a master node. + /// - \p master_node_ed25519_pubkey The daemon's ed25519 auxiliary public key. + /// - \p master_node_x25519_pubkey The daemon's x25519 auxiliary public key. struct GET_MASTER_KEYS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_keys", "get_master_node_key"); } - - struct request : EMPTY {}; - - struct response - { - std::string master_node_pubkey; // The queried daemon's master node public key. Will be empty if not running as a master node. - std::string master_node_ed25519_pubkey; // The daemon's ed25519 auxiliary public key. - std::string master_node_x25519_pubkey; // The daemon's x25519 auxiliary public key. - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE - }; }; /// Get the master private keys of the queried daemon, encoded in hex. Do not ever share From 5f07481e47f5adcdba64e30688514ff51476c1f4 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 16:47:32 +0530 Subject: [PATCH 085/182] RPC: GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES updated --- src/cryptonote_core/master_node_list.h | 2 ++ src/rpc/core_rpc_server.cpp | 18 +++---------- src/rpc/core_rpc_server.h | 4 +-- src/rpc/core_rpc_server_commands_defs.cpp | 18 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 32 ++++++++--------------- 5 files changed, 28 insertions(+), 46 deletions(-) diff --git a/src/cryptonote_core/master_node_list.h b/src/cryptonote_core/master_node_list.h index 26a642acc0a..5a28f89c95e 100755 --- a/src/cryptonote_core/master_node_list.h +++ b/src/cryptonote_core/master_node_list.h @@ -39,6 +39,7 @@ #include "cryptonote_core/master_node_voting.h" #include "cryptonote_core/master_node_quorum_cop.h" #include "common/util.h" +#include namespace cryptonote { @@ -381,6 +382,7 @@ namespace master_nodes END_SERIALIZE() }; + inline void to_json(nlohmann::json& j, const key_image_blacklist_entry& b) { j = nlohmann::json{{"key_image", tools::type_to_hex(b.key_image)}, {"unlock_height", b.unlock_height}, {"amount", b.amount} }; }; struct payout_entry { cryptonote::account_public_address address; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b95d0ddfcd0..22894d620fc 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2701,24 +2701,14 @@ namespace cryptonote::rpc { return invoke(std::move(req_old), context); } //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response core_rpc_server::invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context) { - GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response res{}; - PERF_TIMER(on_get_master_node_blacklisted_key_images); auto &blacklist = m_core.get_master_node_blacklisted_key_images(); - res.status = STATUS_OK; - res.blacklist.reserve(blacklist.size()); - for (const master_nodes::key_image_blacklist_entry &entry : blacklist) - { - res.blacklist.emplace_back(); - auto &new_entry = res.blacklist.back(); - new_entry.key_image = tools::type_to_hex(entry.key_image); - new_entry.unlock_height = entry.unlock_height; - new_entry.amount = entry.amount; - } - return res; + get_master_node_blacklisted_key_images.response["status"] = STATUS_OK; + get_master_node_blacklisted_key_images.response["blacklist"] = blacklist; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 33aacf3ad70..6c27f349fd5 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -251,8 +251,9 @@ namespace cryptonote::rpc { void invoke(GETBANS& get_bans, rpc_context context); void invoke(SETBANS& set_bans, rpc_context context); void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); - void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); + void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); + void invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -277,7 +278,6 @@ namespace cryptonote::rpc { GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); - GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::request&& req, rpc_context context); GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index d8d80e648f3..b9f6c166543 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1130,17 +1130,17 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_STAKING_REQUIREMENT::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::entry) - KV_SERIALIZE(key_image) - KV_SERIALIZE(unlock_height) - KV_SERIALIZE(amount) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::entry) +// KV_SERIALIZE(key_image) +// KV_SERIALIZE(unlock_height) +// KV_SERIALIZE(amount) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response) - KV_SERIALIZE(blacklist) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::response) +// KV_SERIALIZE(blacklist) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e81fe545359..6648e4bdda4 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2073,30 +2073,20 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get information on blacklisted Master Node key images. + /// Get information on blacklisted Master Node key images. + /// + /// Inputs: None + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status generic RPC error code; "OK" means the request was successful. + /// - \p blacklist Array of blacklisted key images, i.e. unspendable transactions. Each entry contains + /// - \p key_image The key image of the transaction that is blacklisted on the network. + /// - \p unlock_height The height at which the key image is removed from the blacklist and becomes spendable. + /// - \p amount The total amount of locked Beldex in atomic units in this blacklisted stake. struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC { static constexpr auto names() { return NAMES("get_master_node_blacklisted_key_images"); } - - struct request : EMPTY {}; - - struct entry - { - std::string key_image; // The key image of the transaction that is blacklisted on the network. - uint64_t unlock_height; // The height at which the key image is removed from the blacklist and becomes spendable. - uint64_t amount; // The total amount of locked Beldex in atomic units in this blacklisted stake. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::vector blacklist; // Array of blacklisted key images, i.e. unspendable transactions - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE - }; }; /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. From 5c17557f735eda04e303b4eca27cfbe1eaf1c50b Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Tue, 22 Apr 2025 17:20:20 +0530 Subject: [PATCH 086/182] GET_BLOCK_HEADER_BY_HEIGHT --- src/rpc/core_rpc_server.cpp | 31 +++++++++++---------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 7 +++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 24 ++++++++-------- src/rpc/core_rpc_server_commands_defs.h | 32 +++++++++++----------- 6 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 22894d620fc..6e087dc119a 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1762,15 +1762,10 @@ namespace cryptonote::rpc { return res; } //------------------------------------------------------------------------------------------------------------------------------ - GET_BLOCK_HEADER_BY_HEIGHT::response core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context) { - GET_BLOCK_HEADER_BY_HEIGHT::response res{}; - PERF_TIMER(on_get_block_header_by_height); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - - auto get = [this, curr_height=m_core.get_current_blockchain_height(), pow=req.fill_pow_hash && context.admin, tx_hashes=req.get_tx_hashes] + auto get = [this, curr_height=m_core.get_current_blockchain_height(), pow=get_block_header_by_height.request.fill_pow_hash && context.admin, tx_hashes=get_block_header_by_height.request.get_tx_hashes] (uint64_t height, block_header_response& bhr) { if (height >= curr_height) throw rpc_error{ERROR_TOO_BIG_HEIGHT, @@ -1782,15 +1777,21 @@ namespace cryptonote::rpc { fill_block_header_response(blk, false, height, get_block_hash(blk), bhr, pow, tx_hashes); }; - if (req.height) - get(*req.height, res.block_header.emplace()); - if (!req.heights.empty()) - res.block_headers.reserve(req.heights.size()); - for (auto height : req.heights) - get(height, res.block_headers.emplace_back()); + block_header_response header; + if (get_block_header_by_height.request.height) + { + get(*get_block_header_by_height.request.height, header); + get_block_header_by_height.response["block_header"] = header; + } + std::vector headers; + if (!get_block_header_by_height.request.heights.empty()) + headers.reserve(get_block_header_by_height.request.heights.size()); + for (auto height : get_block_header_by_height.request.heights) + get(height, headers.emplace_back()); - res.status = STATUS_OK; - return res; + get_block_header_by_height.response["status"] = STATUS_OK; + get_block_header_by_height.response["block_headers"] = headers; + return; } //------------------------------------------------------------------------------------------------------------------------------ GET_BLOCK::response core_rpc_server::invoke(GET_BLOCK::request&& req, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 6c27f349fd5..b9cf6cb3abf 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -254,6 +254,7 @@ namespace cryptonote::rpc { void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); void invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context); + void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -269,7 +270,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index c30cf8fe92d..0aef0acbda2 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -498,4 +498,11 @@ namespace cryptonote::rpc { get_values(in, "seconds", set_bans.request.seconds); get_values(in, "ban", set_bans.request.ban); } + + void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in) { + get_values(in, "height", get_block_header_by_height.request.height); + get_values(in, "heights", get_block_header_by_height.request.heights); + get_values(in, "fill_pow_hash", get_block_header_by_height.request.fill_pow_hash); + get_values(in, "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index bab0fe45153..e8cc50c3314 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -47,4 +47,5 @@ namespace cryptonote::rpc { void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); + void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index b9f6c166543..e2ea112792e 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -381,20 +381,20 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HEIGHT::request) - KV_SERIALIZE(height) - KV_SERIALIZE(heights) - KV_SERIALIZE_OPT(fill_pow_hash, false); - KV_SERIALIZE_OPT(get_tx_hashes, false); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HEIGHT::request) +// KV_SERIALIZE(height) +// KV_SERIALIZE(heights) +// KV_SERIALIZE_OPT(fill_pow_hash, false); +// KV_SERIALIZE_OPT(get_tx_hashes, false); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HEIGHT::response) - KV_SERIALIZE(block_header) - KV_SERIALIZE(block_headers) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADER_BY_HEIGHT::response) +// KV_SERIALIZE(block_header) +// KV_SERIALIZE(block_headers) +// KV_SERIALIZE(status) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 6648e4bdda4..73315487b4f 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -811,31 +811,31 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Similar to get_block_header_by_hash above, this method includes a block's height as an input parameter to retrieve basic information about the block. + /// Similar to get_block_header_by_hash above, this method includes a block's height as an input parameter to retrieve basic information about the block. + /// + /// Inputs: + /// + /// - \p height A block height to look up; returned in `block_header` + /// - \p heights Block heights to retrieve; returned in `block_headers` + /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. + /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p block_header Block header information for the requested `height` block + /// - \p block_headers Block header information for the requested `heights` blocks struct GET_BLOCK_HEADER_BY_HEIGHT : PUBLIC { static constexpr auto names() { return NAMES("get_block_header_by_height", "getblockheaderbyheight"); } - struct request + struct request_parameters { std::optional height; // A block height to look up; returned in `block_header` std::vector heights; // Block heights to retrieve; returned in `block_headers` bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. bool get_tx_hashes; // If true (default false) then include the hashes of non-coinbase transactions - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::optional block_header; // Block header information for the requested `height` block - std::vector block_headers; // Block header information for the requested `heights` blocks - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + } request; }; BELDEX_RPC_DOC_INTROSPECT From 30b115ed3e29fc09acb19e99ee0754f84270696c Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 17:38:54 +0530 Subject: [PATCH 087/182] RPC: GET_BLOCK_HEADERS_RANGE updated --- src/daemon/rpc_command_executor.cpp | 51 +++++++++------------- src/rpc/core_rpc_server.cpp | 23 +++++----- src/rpc/core_rpc_server.h | 5 +++ src/rpc/core_rpc_server_command_parser.cpp | 7 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 22 +++++----- src/rpc/core_rpc_server_commands_defs.h | 34 +++++++-------- 7 files changed, 73 insertions(+), 70 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 86bf700175d..271c6958bbf 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -690,9 +690,6 @@ bool rpc_command_executor::print_net_stats() } bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint64_t end_block_index) { - GET_BLOCK_HEADERS_RANGE::request req{}; - GET_BLOCK_HEADERS_RANGE::response res{}; - // negative: relative to the end if (start_block_index < 0) { @@ -711,15 +708,13 @@ bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint end_block_index += start_block_index - 1; } - req.start_height = start_block_index; - req.end_height = end_block_index; - req.fill_pow_hash = false; - - if (!invoke(std::move(req), res, "Failed to retrieve block headers")) + auto maybe_block_headers = try_running([this, start_block_index, end_block_index] { return invoke(json{{"start_height", start_block_index},{"end_height", end_block_index}, {"fill_pow_hash", false}}); }, "Failed to retrieve block headers"); + if (!maybe_block_headers) return false; + auto& block_headers = *maybe_block_headers; bool first = true; - for (auto & header : res.headers) + for (auto & header : block_headers["headers"]) { if (first) first = false; @@ -727,11 +722,11 @@ bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint tools::msg_writer() << "\n"; tools::msg_writer() - << "height: " << header.height << ", timestamp: " << header.timestamp << " (" << tools::get_human_readable_timestamp(header.timestamp) << ")" - << ", size: " << header.block_size << ", weight: " << header.block_weight << " (long term " << header.long_term_weight << "), transactions: " << header.num_txes - << "\nmajor version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version - << "\nblock id: " << header.hash << ", previous block id: " << header.prev_hash - << "\ndifficulty: " << header.difficulty << ", nonce " << header.nonce << ", reward " << cryptonote::print_money(header.reward) << "\n"; + << "height: " << header["height"] << ", timestamp: " << header["timestamp"] << " (" << tools::get_human_readable_timestamp(header["timestamp"].get()) << ")" + << ", size: " << header["block_size"] << ", weight: " << header["block_weight"] << " (long term " << header["long_term_weight"] << "), transactions: " << header["num_txes"] + << "\nmajor version: " << header["major_version"] << ", minor version: " << header["minor_version"] + << "\nblock id: " << header["hash"] << ", previous block id: " << header["prev_hash"] + << "\ndifficulty: " << header["difficulty"] << ", nonce " << header["nonce"] << ", reward " << cryptonote::print_money(header["reward"].get()) << "\n"; } return true; @@ -1380,14 +1375,10 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) if (nblocks > height) nblocks = height; - GET_BLOCK_HEADERS_RANGE::request bhreq{}; - GET_BLOCK_HEADERS_RANGE::response bhres{}; - - bhreq.start_height = height - nblocks; - bhreq.end_height = height - 1; - bhreq.fill_pow_hash = false; - if (!invoke(std::move(bhreq), bhres, "Failed to retrieve block headers")) + auto maybe_block_headers = try_running([this, height, nblocks] { return invoke(json{{"start_height", height - nblocks},{"end_height", height - 1}, {"fill_pow_hash", false}}); }, "Failed to retrieve block headers"); + if (!maybe_block_headers) return false; + auto& block_headers = *maybe_block_headers; double avgdiff = 0; double avgnumtxes = 0; @@ -1396,16 +1387,16 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) weights.reserve(nblocks); uint64_t earliest = std::numeric_limits::max(), latest = 0; std::map> versions; // version -> {majorcount, minorcount} - for (const auto &bhr: bhres.headers) + for (const auto &bhr: block_headers["headers"]) { - avgdiff += bhr.difficulty; - avgnumtxes += bhr.num_txes; - avgreward += bhr.reward; - weights.push_back(bhr.block_weight); - versions[bhr.major_version].first++; - versions[bhr.minor_version].second++; - earliest = std::min(earliest, bhr.timestamp); - latest = std::max(latest, bhr.timestamp); + avgdiff += bhr["difficulty"].get(); + avgnumtxes += bhr["num_txes"].get(); + avgreward += bhr["reward"].get(); + weights.push_back(bhr["block_weight"].get()); + versions[bhr["major_version"]].first++; + versions[bhr["minor_version"]].second++; + earliest = std::min(earliest, bhr["timestamp"].get()); + latest = std::max(latest, bhr["timestamp"].get()); } avgdiff /= nblocks; avgnumtxes /= nblocks; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 6e087dc119a..ec3b02a961a 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1732,18 +1732,16 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - GET_BLOCK_HEADERS_RANGE::response core_rpc_server::invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context) { - GET_BLOCK_HEADERS_RANGE::response res{}; - PERF_TIMER(on_get_block_headers_range); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - const uint64_t bc_height = m_core.get_current_blockchain_height(); - if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) + uint64_t start_height = get_block_headers_range.request.start_height; + uint64_t end_height = get_block_headers_range.request.end_height; + if (start_height >= bc_height || end_height >= bc_height || start_height > end_height) throw rpc_error{ERROR_TOO_BIG_HEIGHT, "Invalid start/end heights."}; - for (uint64_t h = req.start_height; h <= req.end_height; ++h) + std::vector headers; + for (uint64_t h = start_height; h <= end_height; ++h) { block blk; bool have_block = m_core.get_block_by_height(h, blk); @@ -1755,11 +1753,12 @@ namespace cryptonote::rpc { uint64_t block_height = var::get(blk.miner_tx.vin.front()).height; if (block_height != h) throw rpc_error{ERROR_INTERNAL, "Internal error: coinbase transaction in the block has the wrong height"}; - res.headers.push_back(block_header_response()); - fill_block_header_response(blk, false, block_height, get_block_hash(blk), res.headers.back(), req.fill_pow_hash && context.admin, req.get_tx_hashes); + headers.push_back(block_header_response()); + fill_block_header_response(blk, false, block_height, get_block_hash(blk), headers.back(), get_block_headers_range.request.fill_pow_hash && context.admin, get_block_headers_range.request.get_tx_hashes); } - res.status = STATUS_OK; - return res; + get_block_headers_range.response["headers"] = headers; + get_block_headers_range.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b9cf6cb3abf..1f3f379797d 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -254,6 +254,7 @@ namespace cryptonote::rpc { void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); void invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context); + void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); // Deprecated Monero NIH binary endpoints: @@ -270,7 +271,11 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); +<<<<<<< Updated upstream GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); +======= + GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); +>>>>>>> Stashed changes GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 0aef0acbda2..95b2b3313b6 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -499,6 +499,13 @@ namespace cryptonote::rpc { get_values(in, "ban", set_bans.request.ban); } + void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { + get_values(in, "start_height", get_block_headers_range.request.start_height); + get_values(in, "end_height", get_block_headers_range.request.end_height); + get_values(in, "fill_pow_hash", get_block_headers_range.request.fill_pow_hash); + get_values(in, "get_tx_hashes", get_block_headers_range.request.get_tx_hashes); + } + void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in) { get_values(in, "height", get_block_header_by_height.request.height); get_values(in, "heights", get_block_header_by_height.request.heights); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index e8cc50c3314..df21eb2b75e 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -47,5 +47,6 @@ namespace cryptonote::rpc { void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); + void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index e2ea112792e..2b44b97b9f0 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -543,19 +543,19 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADERS_RANGE::request) - KV_SERIALIZE(start_height) - KV_SERIALIZE(end_height) - KV_SERIALIZE_OPT(fill_pow_hash, false); - KV_SERIALIZE_OPT(get_tx_hashes, false); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADERS_RANGE::request) +// KV_SERIALIZE(start_height) +// KV_SERIALIZE(end_height) +// KV_SERIALIZE_OPT(fill_pow_hash, false); +// KV_SERIALIZE_OPT(get_tx_hashes, false); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADERS_RANGE::response) - KV_SERIALIZE(status) - KV_SERIALIZE(headers) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK_HEADERS_RANGE::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(headers) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(SET_BOOTSTRAP_DAEMON::request) KV_SERIALIZE(address) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 73315487b4f..456dfe2b9f7 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1070,32 +1070,32 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_connections"); } }; - BELDEX_RPC_DOC_INTROSPECT - // Similar to get_block_header_by_height above, but for a range of blocks. - // This method includes a starting block height and an ending block height as - // parameters to retrieve basic information about the range of blocks. + /// Similar to get_block_header_by_height above, but for a range of blocks. + /// This method includes a starting block height and an ending block height as + /// parameters to retrieve basic information about the range of blocks. + /// + /// Inputs: + /// + /// - \p start_height The starting block's height. + /// - \p end_height The ending block's height. + /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. + /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p headers Array of block_header (a structure containing block header information. See get_last_block_header). struct GET_BLOCK_HEADERS_RANGE : PUBLIC { static constexpr auto names() { return NAMES("get_block_headers_range", "getblockheadersrange"); } - struct request + struct request_parameters { uint64_t start_height; // The starting block's height. uint64_t end_height; // The ending block's height. bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. bool get_tx_hashes; // If true (default false) then include the hashes or txes in the block details - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector headers; // Array of block_header (a structure containing block header information. See get_last_block_header). - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + } request; }; BELDEX_RPC_DOC_INTROSPECT From 71077fe532cdfc50f00a94637a4577111c4e8e2d Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 17:45:18 +0530 Subject: [PATCH 088/182] RPC: GET_STAKING_REQUIREMENT updated --- src/daemon/rpc_command_executor.cpp | 11 ++++---- src/rpc/core_rpc_server.cpp | 12 ++++----- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 16 +++++------ src/rpc/core_rpc_server_commands_defs.h | 31 +++++++++++----------- 7 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 271c6958bbf..03798ff7da0 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1911,11 +1911,12 @@ bool rpc_command_executor::print_mn_status(std::vector args) bool rpc_command_executor::print_sr(uint64_t height) { - GET_STAKING_REQUIREMENT::response res{}; - if (!invoke({height}, res, "Failed to retrieve staking requirements")) + auto maybe_staking_requirement = try_running([this, height] { return invoke(json{{"height", height}}); }, "Failed to retrieve staking requirements"); + if (!maybe_staking_requirement) return false; - - tools::success_msg_writer() << "Staking Requirement: " << cryptonote::print_money(res.staking_requirement); + + auto& staking_requirement = *maybe_staking_requirement; + tools::success_msg_writer() << "Staking Requirement: " << cryptonote::print_money(staking_requirement["staking_requirement"]); return true; } @@ -1926,7 +1927,7 @@ bool rpc_command_executor::pop_blocks(uint64_t num_blocks) return false; auto& pop_blocks = *maybe_pop_blocks; - tools::success_msg_writer() << "new height: " << pop_blocks["height"].get(); + tools::success_msg_writer() << "new height: " << pop_blocks["height"]; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ec3b02a961a..f3a64f0c4de 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3024,16 +3024,14 @@ namespace cryptonote::rpc { [this](bool significant) { if (significant) m_core.reset_proof_interval(); }); } //------------------------------------------------------------------------------------------------------------------------------ - GET_STAKING_REQUIREMENT::response core_rpc_server::invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_context context) { - GET_STAKING_REQUIREMENT::response res{}; - PERF_TIMER(on_get_staking_requirement); - res.height = req.height > 0 ? req.height : m_core.get_current_blockchain_height(); + get_staking_requirement.response["height"] = get_staking_requirement.request.height > 0 ? get_staking_requirement.request.height : m_core.get_current_blockchain_height(); - res.staking_requirement = master_nodes::get_staking_requirement(res.height); - res.status = STATUS_OK; - return res; + get_staking_requirement.response["staking_requirement"] = service_nodes::get_staking_requirement(nettype(), get_staking_requirement.request.height); + get_staking_requirement.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ static void check_quantity_limit(size_t count, size_t max, char const *container_name = nullptr) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 1f3f379797d..d97d038fe97 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -251,6 +251,7 @@ namespace cryptonote::rpc { void invoke(GETBANS& get_bans, rpc_context context); void invoke(SETBANS& set_bans, rpc_context context); void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); + void invoke(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_context context); void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); void invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context); @@ -283,7 +284,6 @@ namespace cryptonote::rpc { GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); - GET_STAKING_REQUIREMENT::response invoke(GET_STAKING_REQUIREMENT::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 95b2b3313b6..35c654738c8 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -499,6 +499,10 @@ namespace cryptonote::rpc { get_values(in, "ban", set_bans.request.ban); } + void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in) { + get_values(in, "height", get_staking_requirement.request.height); + } + void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { get_values(in, "start_height", get_block_headers_range.request.start_height); get_values(in, "end_height", get_block_headers_range.request.end_height); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index df21eb2b75e..4ee102bb9a1 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -47,6 +47,7 @@ namespace cryptonote::rpc { void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); + void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 2b44b97b9f0..2e765f56dda 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1118,16 +1118,16 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_STAKING_REQUIREMENT::request) - KV_SERIALIZE(height) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_STAKING_REQUIREMENT::request) +// KV_SERIALIZE(height) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_STAKING_REQUIREMENT::response) - KV_SERIALIZE(staking_requirement) - KV_SERIALIZE(height) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_STAKING_REQUIREMENT::response) +// KV_SERIALIZE(staking_requirement) +// KV_SERIALIZE(height) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::entry) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 456dfe2b9f7..5911a7805b2 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2048,29 +2048,28 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Get the required amount of Beldex to become a Master Node at the queried height. - // For devnet and testnet values, ensure the daemon is started with the - // `--devnet` or `--testnet` flags respectively. + /// Get the required amount of BDX to become a Master Node at the queried height. + /// For devnet and testnet values, ensure the daemon is started with the + /// `--devnet` or `--testnet` flags respectively. + /// + /// Inputs: + /// + /// - \p height The height to query the staking requirement for. 0 (or omitting) means current height. + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status generic RPC error code; "OK" means the request was successful. + /// - \p staking_requirement The staking requirement in Oxen, in atomic units. + /// - \p height The height requested (or current height if 0 was requested) struct GET_STAKING_REQUIREMENT : PUBLIC { static constexpr auto names() { return NAMES("get_staking_requirement"); } - struct request + struct request_parameters { uint64_t height; // The height to query the staking requirement for. 0 (or omitting) means current height. + } request; - KV_MAP_SERIALIZABLE - }; - - struct response - { - uint64_t staking_requirement; // The staking requirement in Beldex, in atomic units. - uint64_t height; // The height requested (or current height if 0 was requested) - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE - }; }; /// Get information on blacklisted Master Node key images. From 37c993ffbeafc7e2ba7d9ea7f92587677bcc586f Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 22 Apr 2025 18:04:20 +0530 Subject: [PATCH 089/182] RPC: RELAY_TX updated --- src/daemon/rpc_command_executor.cpp | 10 ++++----- src/rpc/core_rpc_server.cpp | 26 +++++++++++----------- src/rpc/core_rpc_server.h | 7 +----- src/rpc/core_rpc_server_command_parser.cpp | 7 ++++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 6 ++--- src/rpc/core_rpc_server_commands_defs.h | 19 ++++++++++------ 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 03798ff7da0..17b755f4b68 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1420,12 +1420,12 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) bool rpc_command_executor::relay_tx(const std::string &txid) { - RELAY_TX::response res{}; - if (!invoke({{txid}}, res, "Failed to relay tx")) - return false; + auto maybe_relay = try_running([&] { return invoke(json{{"txid", txid}}); }, "Failed to relay tx"); + if (!maybe_relay) + return false; - tools::success_msg_writer() << "Transaction successfully relayed"; - return true; + tools::success_msg_writer() << "Transaction successfully relayed"; + return true; } bool rpc_command_executor::sync_info() diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f3a64f0c4de..187ac182f95 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2232,20 +2232,18 @@ namespace cryptonote::rpc { pop_blocks.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - RELAY_TX::response core_rpc_server::invoke(RELAY_TX::request&& req, rpc_context context) + void core_rpc_server::invoke(RELAY_TX& relay_tx, rpc_context context) { - RELAY_TX::response res{}; - PERF_TIMER(on_relay_tx); - res.status = ""; - for (const auto &txid_hex: req.txids) + std::string status = ""; + for (const auto &str: relay_tx.request.txids) { crypto::hash txid; - if (!tools::hex_to_type(txid_hex, txid)) + if (!tools::hex_to_type(str, txid)) { - if (!res.status.empty()) res.status += ", "; - res.status += "invalid transaction id: " + txid_hex; + if (!status.empty()) status += ", "; + status += "invalid transaction id: " + str; continue; } cryptonote::blobdata txblob; @@ -2259,15 +2257,17 @@ namespace cryptonote::rpc { } else { - if (!res.status.empty()) res.status += ", "; - res.status += "transaction not found in pool: " + txid_hex; + if (!status.empty()) status += ", "; + status += "transaction not found in pool: " + str; + continue; } } - if (res.status.empty()) - res.status = STATUS_OK; + if (status.empty()) + status = STATUS_OK; - return res; + relay_tx.response["status"] = status; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(SYNC_INFO& sync, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index d97d038fe97..a05339e696a 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -255,6 +255,7 @@ namespace cryptonote::rpc { void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); void invoke(GET_MASTER_PRIVKEYS& get_master_privkeys, rpc_context context); void invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context); + void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); @@ -272,15 +273,9 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); -<<<<<<< Updated upstream - GET_BLOCK_HEADERS_RANGE::response invoke(GET_BLOCK_HEADERS_RANGE::request&& req, rpc_context context); -======= - GET_BLOCK_HEADER_BY_HEIGHT::response invoke(GET_BLOCK_HEADER_BY_HEIGHT::request&& req, rpc_context context); ->>>>>>> Stashed changes GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); - RELAY_TX::response invoke(RELAY_TX::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 35c654738c8..68d92abe5db 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -460,6 +460,7 @@ namespace cryptonote::rpc { get_values(in, "https_port", storage_server_ping.request.https_port); get_values(in, "omq_port", storage_server_ping.request.omq_port); get_values(in, "pubkey_ed25519", storage_server_ping.request.pubkey_ed25519); + } void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ get_values(in, "check", prune_blockchain.request.check); @@ -474,6 +475,7 @@ namespace cryptonote::rpc { void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in) { get_values(in, "start_height", get_mn_state_changes.request.start_height); get_values(in, "end_height", get_mn_state_changes.request.end_height); + } void parse_request(FLUSH_CACHE& flush_cache, rpc_input in) { get_values(in, "bad_txs", flush_cache.request.bad_txs); @@ -503,6 +505,10 @@ namespace cryptonote::rpc { get_values(in, "height", get_staking_requirement.request.height); } + void parse_request(RELAY_TX& relay_tx, rpc_input in){ + get_values(in, "txids", get_block_header_by_hash.request.txids); + } + void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { get_values(in, "start_height", get_block_headers_range.request.start_height); get_values(in, "end_height", get_block_headers_range.request.end_height); @@ -516,4 +522,5 @@ namespace cryptonote::rpc { get_values(in, "fill_pow_hash", get_block_header_by_height.request.fill_pow_hash); get_values(in, "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes); } + } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 4ee102bb9a1..63e52377305 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -48,6 +48,7 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); + void parse_request(RELAY_TX& relay_tx, rpc_input in); void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 2e765f56dda..6f204d2c160 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -746,9 +746,9 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALTERNATE_CHAINS::response) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(RELAY_TX::request) - KV_SERIALIZE(txids) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(RELAY_TX::request) +// KV_SERIALIZE(txids) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(SYNC_INFO::peer) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 5911a7805b2..c0c6f2090d7 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1463,18 +1463,23 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Relay a list of transaction IDs. + /// Relay a list of transaction IDs. + /// + /// Inputs: + /// + /// - \p txids List of transactions IDs to relay from pool. + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. struct RELAY_TX : RPC_COMMAND { static constexpr auto names() { return NAMES("relay_tx"); } - struct request + struct request_parameters { std::vector txids; // List of transactions IDs to relay from pool. - - KV_MAP_SERIALIZABLE - }; + } request; struct response : STATUS {}; }; @@ -1488,7 +1493,7 @@ namespace cryptonote::rpc { /// /// Inputs: none /// - /// Output values available from an admin RPC endpoint: + /// Output values available from a restricted/admin RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p height Current block height From ba86c4a61a4e7262a845b71e57fbb8b0aed58586 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 18:49:02 +0530 Subject: [PATCH 090/182] RPC: GET_BLOCK updated --- src/daemon/rpc_command_executor.cpp | 45 ++++++++++++--------- src/daemon/rpc_command_executor.h | 3 -- src/rpc/core_rpc_server.cpp | 46 +++++++++++----------- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 5 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 26 ++++++------ src/rpc/core_rpc_server_commands_defs.h | 37 +++++++++-------- 8 files changed, 88 insertions(+), 79 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 17b755f4b68..b5d311e0136 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -788,31 +788,40 @@ bool rpc_command_executor::print_height() { return false; } -bool rpc_command_executor::print_block(GET_BLOCK::request&& req, bool include_hex) { - req.fill_pow_hash = true; - GET_BLOCK::response res{}; - - if (!invoke(std::move(req), res, "Block retrieval failed")) - return false; +bool rpc_command_executor::print_block_by_hash(const crypto::hash& block_hash, bool include_hex) { req.fill_pow_hash = true; + auto maybe_block = try_running([this, &block_hash] { + return invoke(json{ + {"hash", tools::type_to_hex(block_hash)}, + {"fill_pow_hash", true}}); + }, "Block retrieval failed"); + if (!maybe_block) + return false; + auto& block = *maybe_block; if (include_hex) - tools::success_msg_writer() << res.blob << std::endl; - print_block_header(res.block_header); - tools::success_msg_writer() << res.json << "\n"; + tools::success_msg_writer() << block["blob"] << std::endl; + print_block_header(block["block_header"]); + tools::success_msg_writer() << block["json"] << "\n"; return true; } -bool rpc_command_executor::print_block_by_hash(const crypto::hash& block_hash, bool include_hex) { - GET_BLOCK::request req{}; - req.hash = tools::type_to_hex(block_hash); - return print_block(std::move(req), include_hex); -} - bool rpc_command_executor::print_block_by_height(uint64_t height, bool include_hex) { - GET_BLOCK::request req{}; - req.height = height; - return print_block(std::move(req), include_hex); + auto maybe_block = try_running([this, height] { + return invoke(json{ + {"height", height}, + {"fill_pow_hash", true}}); + }, "Block retrieval failed"); + if (!maybe_block) + return false; + auto& block = *maybe_block; + + if (include_hex) + tools::success_msg_writer() << block["blob"] << std::endl; + print_block_header(block["block_header"]); + tools::success_msg_writer() << block["json"] << "\n"; + + return true; } bool rpc_command_executor::print_transaction(const crypto::hash& transaction_hash, diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 8a30b7857a4..a47ff187fac 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -175,9 +175,6 @@ class rpc_command_executor final { bool print_height(); -private: - bool print_block(cryptonote::rpc::GET_BLOCK::request&& req, bool include_hdex); - public: bool print_block_by_hash(const crypto::hash& block_hash, bool include_hex); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 187ac182f95..21680a72709 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1793,45 +1793,43 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - GET_BLOCK::response core_rpc_server::invoke(GET_BLOCK::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_BLOCK& get_block, rpc_context context) { - GET_BLOCK::response res{}; - PERF_TIMER(on_get_block); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - block blk; uint64_t block_height; bool orphan = false; crypto::hash block_hash; - if (!req.hash.empty()) + if (!get_block.request.hash.empty()) { - if (!tools::hex_to_type(req.hash, block_hash)) - throw rpc_error{ERROR_WRONG_PARAM, "Failed to parse hex representation of block hash. Hex = " + req.hash + '.'}; + if (!tools::hex_to_type(get_block.request.hash, block_hash)) + throw rpc_error{ERROR_WRONG_PARAM, "Failed to parse hex representation of block hash. Hex = " + get_block.request.hash + '.'}; if (!m_core.get_block_by_hash(block_hash, blk, &orphan)) - throw rpc_error{ERROR_INTERNAL, "Internal error: can't get block by hash. Hash = " + req.hash + '.'}; + throw rpc_error{ERROR_INTERNAL, "Internal error: can't get block by hash. Hash = " + get_block.request.hash + '.'}; if (blk.miner_tx.vin.size() != 1 || !std::holds_alternative(blk.miner_tx.vin.front())) throw rpc_error{ERROR_INTERNAL, "Internal error: coinbase transaction in the block has the wrong type"}; block_height = var::get(blk.miner_tx.vin.front()).height; } else { - if (auto curr_height = m_core.get_current_blockchain_height(); req.height >= curr_height) - throw rpc_error{ERROR_TOO_BIG_HEIGHT, std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(curr_height - 1)}; - if (!m_core.get_block_by_height(req.height, blk)) - throw rpc_error{ERROR_INTERNAL, "Internal error: can't get block by height. Height = " + std::to_string(req.height) + '.'}; + if (auto curr_height = m_core.get_current_blockchain_height(); get_block.request.height >= curr_height) + throw rpc_error{ERROR_TOO_BIG_HEIGHT, std::string("Requested block height: ") + std::to_string(get_block.request.height) + " greater than current top block height: " + std::to_string(curr_height - 1)}; + if (!m_core.get_block_by_height(get_block.request.height, blk)) + throw rpc_error{ERROR_INTERNAL, "Internal error: can't get block by height. Height = " + std::to_string(get_block.request.height) + '.'}; block_hash = get_block_hash(blk); - block_height = req.height; - } - fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash && context.admin, false /*tx hashes*/); - res.tx_hashes.reserve(blk.tx_hashes.size()); - for (const auto& tx_hash : blk.tx_hashes) - res.tx_hashes.push_back(tools::type_to_hex(tx_hash)); - res.blob = oxenc::to_hex(t_serializable_object_to_blob(blk)); - res.json = obj_to_json_str(blk); - res.status = STATUS_OK; - return res; + block_height = get_block.request.height; + } + block_header_response header; + fill_block_header_response(blk, orphan, block_height, block_hash, header, get_block.request.fill_pow_hash && context.admin, false /*tx hashes*/); + get_block.response["block_header"] = header; + std::vector tx_hashes; + tx_hashes.reserve(blk.tx_hashes.size()); + std::transform(blk.tx_hashes.begin(), blk.tx_hashes.end(), tx_hashes.begin(), [](const auto& x) { return tools::type_to_hex(x); }); + get_block.response["tx_hashes"] = tx_hashes; + get_block.response["blob"] = oxenmq::to_hex(t_serializable_object_to_blob(blk)); + get_block.response["json"] = obj_to_json_str(blk); + get_block.response["status"] = STATUS_OK; + return; } static json json_connection_info(const connection_info& ci) { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index a05339e696a..afb97747284 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -258,6 +258,7 @@ namespace cryptonote::rpc { void invoke(RELAY_TX& relay_tx, rpc_context context); void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); + void invoke(GET_BLOCK& get_block, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -269,11 +270,10 @@ namespace cryptonote::rpc { GET_OUTPUTS_BIN::response invoke(GET_OUTPUTS_BIN::request&& req, rpc_context context); GET_TRANSACTION_POOL_HASHES_BIN::response invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context); GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); + GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); // FIXME: unconverted JSON RPC endpoints: SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); - GET_BLOCK::response invoke(GET_BLOCK::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 68d92abe5db..58aef08e3df 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -523,4 +523,9 @@ namespace cryptonote::rpc { get_values(in, "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes); } + void parse_request(GET_BLOCK& get_block, rpc_input in) { + get_values(in, "hash", get_block.request.hash); + get_values(in, "height", get_block.request.height); + get_values(in, "fill_pow_hash", get_block.request.fill_pow_hash); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 63e52377305..108bb684324 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -51,4 +51,5 @@ namespace cryptonote::rpc { void parse_request(RELAY_TX& relay_tx, rpc_input in); void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); + void parse_request(GET_BLOCK& get_block, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 6f204d2c160..995deff5b9b 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -397,21 +397,21 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::request) - KV_SERIALIZE(hash) - KV_SERIALIZE(height) - KV_SERIALIZE_OPT(fill_pow_hash, false); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::request) +// KV_SERIALIZE(hash) +// KV_SERIALIZE(height) +// KV_SERIALIZE_OPT(fill_pow_hash, false); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) - KV_SERIALIZE(block_header) - KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(status) - KV_SERIALIZE(blob) - KV_SERIALIZE(json) - KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) +// KV_SERIALIZE(block_header) +// KV_SERIALIZE(tx_hashes) +// KV_SERIALIZE(status) +// KV_SERIALIZE(blob) +// KV_SERIALIZE(json) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::peer) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index c0c6f2090d7..c4d7dfaa43d 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -838,33 +838,32 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Full block information can be retrieved by either block height or hash, like with the above block header calls. - // For full block information, both lookups use the same method, but with different input parameters. + /// Full block information can be retrieved by either block height or hash, like with the above block header calls. + /// For full block information, both lookups use the same method, but with different input parameters. + /// + /// Inputs: + /// + /// - \p hash The block's hash. + /// - \p height A block height to look up; returned in `block_header` + /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p block_header Block header information for the requested `height` block + /// - \p tx_hashes List of hashes of non-coinbase transactions in the block. If there are no other transactions, this will be an empty list. + /// - \p blob Hexadecimal blob of block information. + /// - \p json JSON formatted block details. struct GET_BLOCK : PUBLIC { static constexpr auto names() { return NAMES("get_block", "getblock"); } - struct request + struct request_parameters { std::string hash; // The block's hash. uint64_t height; // The block's height. bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - block_header_response block_header; // A structure containing block header information. See get_last_block_header. - std::vector tx_hashes; // List of hashes of non-coinbase transactions in the block. If there are no other transactions, this will be an empty list. - std::string blob; // Hexadecimal blob of block information. - std::string json; // JSON formatted block details. - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE - }; + } request; }; /// Get the list of current network peers known to this node. From 8019d4f3f6ba4d9919d0d8fe3496160ada9ad3f7 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Tue, 22 Apr 2025 19:15:38 +0530 Subject: [PATCH 091/182] commented bootstrap daemon code for future use --- src/daemon/command_parser_executor.cpp | 22 +-- src/daemon/command_parser_executor.h | 2 +- src/daemon/command_server.cpp | 12 +- src/daemon/daemon.cpp | 2 +- src/daemon/rpc_command_executor.cpp | 72 +++---- src/daemon/rpc_command_executor.h | 8 +- src/rpc/CMakeLists.txt | 2 +- src/rpc/bootstrap_daemon.cpp | 172 ++++++++--------- src/rpc/bootstrap_daemon.h | 106 +++++------ src/rpc/core_rpc_server.cpp | 217 +++++++++++----------- src/rpc/core_rpc_server.h | 34 ++-- src/rpc/core_rpc_server_commands_defs.cpp | 27 ++- src/rpc/core_rpc_server_commands_defs.h | 36 ++-- 13 files changed, 362 insertions(+), 350 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 39c924fbf7e..57a4b7e5506 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -821,17 +821,17 @@ bool command_parser_executor::check_blockchain_pruning(const std::vector& args) -{ - const size_t args_count = args.size(); - if (args_count < 1 || args_count > 3) - return false; - - return m_executor.set_bootstrap_daemon( - args[0] != "none" ? args[0] : std::string(), - args_count > 1 ? args[1] : std::string(), - args_count > 2 ? args[2] : std::string()); -} +// bool command_parser_executor::set_bootstrap_daemon(const std::vector& args) +// { +// const size_t args_count = args.size(); +// if (args_count < 1 || args_count > 3) +// return false; + +// return m_executor.set_bootstrap_daemon( +// args[0] != "none" ? args[0] : std::string(), +// args_count > 1 ? args[1] : std::string(), +// args_count > 2 ? args[2] : std::string()); +// } bool command_parser_executor::flush_cache(const std::vector& args) { diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index ed22ba2bdb8..23c44f254d3 100755 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -137,7 +137,7 @@ class command_parser_executor final bool print_mn_state_changes(const std::vector &args); - bool set_bootstrap_daemon(const std::vector& args); + // bool set_bootstrap_daemon(const std::vector& args); bool flush_cache(const std::vector& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index ae3b3a5bd3b..57dcdc97823 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -330,12 +330,12 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) , "print_mn_state_changes [end height]" , "Query the state changes between the range, omit the last argument to scan until the current block" ); - m_command_lookup.set_handler( - "set_bootstrap_daemon" - , [this](const auto &x) { return m_parser.set_bootstrap_daemon(x); } - , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])" - , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." - ); + // m_command_lookup.set_handler( + // "set_bootstrap_daemon" + // , [this](const auto &x) { return m_parser.set_bootstrap_daemon(x); } + // , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])" + // , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." + // ); m_command_lookup.set_handler( "flush_cache" , [this](const auto &x) { return m_parser.flush_cache(x); } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index e4ff8f27679..7b07789c214 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -44,7 +44,7 @@ #include "rpc/rpc_args.h" #include "rpc/http_server.h" #include "rpc/lmq_server.h" -#include "rpc/bootstrap_daemon.h" +// #include "rpc/bootstrap_daemon.h" #include "cryptonote_protocol/quorumnet.h" #include "cryptonote_core/uptime_proof.h" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b5d311e0136..437184cb79c 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -507,7 +507,7 @@ bool rpc_command_executor::show_status() { uint64_t height = info["height"].get(); uint64_t net_height = std::max(info["target_height"].get(), height); - std::string bootstrap_msg; + // std::string bootstrap_msg; std::ostringstream str; str << "Height: " << height; @@ -521,16 +521,16 @@ bool rpc_command_executor::show_status() { if (ires.height < net_height) str << ", syncing"; - if (info.value("was_bootstrap_ever_used", false)) - { - str << ", bootstrap " << info["bootstrap_daemon_address"].get(); - if (info.value("untrusted", false)){ - auto hwb = info["height_without_bootstrap"].get(); - str << fmt::format(", local height: {} ({:.1f}%)", hwb, get_sync_percentage(hwb, net_height)); - } - else - str << " was used"; - } + // if (info.value("was_bootstrap_ever_used", false)) + // { + // str << ", bootstrap " << info["bootstrap_daemon_address"].get(); + // if (info.value("untrusted", false)){ + // auto hwb = info["height_without_bootstrap"].get(); + // str << fmt::format(", local height: {} ({:.1f}%)", hwb, get_sync_percentage(hwb, net_height)); + // } + // else + // str << " was used"; + // } auto hf_version = hfinfo["version"].get(); if (hf_version < cryptonote::feature::POS && !has_mining_info) @@ -1243,12 +1243,12 @@ bool rpc_command_executor::output_histogram(const std::vector &amounts if (!invoke(std::move(req), res, "Failed to retrieve output histogram")) return false; - std::sort(res.histogram.begin(), res.histogram.end(), - [](const auto& e1, const auto& e2)->bool { return e1.total_instances < e2.total_instances; }); - for (const auto &e: res.histogram) - { - tools::msg_writer() << e.total_instances << " " << cryptonote::print_money(e.amount); - } + // std::sort(res.histogram.begin(), res.histogram.end(), + // [](const auto& e1, const auto& e2)->bool { return e1.total_instances < e2.total_instances; }); + // for (const auto &e: res.histogram) + // { + // tools::msg_writer() << e.total_instances << " " << cryptonote::print_money(e.amount); + // } return true; } @@ -2536,25 +2536,25 @@ bool rpc_command_executor::check_blockchain_pruning() return true; } -bool rpc_command_executor::set_bootstrap_daemon( - const std::string &address, - const std::string &username, - const std::string &password) -{ - SET_BOOTSTRAP_DAEMON::request req{}; - req.address = address; - req.username = username; - req.password = password; - - SET_BOOTSTRAP_DAEMON::response res{}; - if (!invoke(std::move(req), res, "Failed to set bootstrap daemon to: " + address)) - return false; - - tools::success_msg_writer() - << "Successfully set bootstrap daemon address to " - << (!req.address.empty() ? req.address : "none"); - return true; -} +// bool rpc_command_executor::set_bootstrap_daemon( +// const std::string &address, +// const std::string &username, +// const std::string &password) +// { +// SET_BOOTSTRAP_DAEMON::request req{}; +// req.address = address; +// req.username = username; +// req.password = password; + +// SET_BOOTSTRAP_DAEMON::response res{}; +// if (!invoke(std::move(req), res, "Failed to set bootstrap daemon to: " + address)) +// return false; + +// tools::success_msg_writer() +// << "Successfully set bootstrap daemon address to " +// << (!req.address.empty() ? req.address : "none"); +// return true; +// } bool rpc_command_executor::version() { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index a47ff187fac..1e8e9b0641a 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -244,10 +244,10 @@ class rpc_command_executor final { bool print_net_stats(); - bool set_bootstrap_daemon( - const std::string &address, - const std::string &username, - const std::string &password); + // bool set_bootstrap_daemon( + // const std::string &address, + // const std::string &username, + // const std::string &password); bool flush_cache(bool bad_txs, bool invalid_blocks); diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index fbec5c31bb9..7f4775e7217 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -40,7 +40,7 @@ add_library(rpc_server_base ) add_library(rpc - bootstrap_daemon.cpp + # bootstrap_daemon.cpp core_rpc_server.cpp ) diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index ae03ab39d1c..8ce1f4c3460 100755 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -1,86 +1,86 @@ -#include "bootstrap_daemon.h" - -#include - -#include "common/string_util.h" -#include "crypto/crypto.h" -#include "cryptonote_core/cryptonote_core.h" -#include "epee/misc_log_ex.h" - -#undef BELDEX_DEFAULT_LOG_CATEGORY -#define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon" - -namespace cryptonote -{ - - bootstrap_daemon::bootstrap_daemon(std::function()> get_next_public_node) - : m_get_next_public_node(get_next_public_node) - { - } - - bootstrap_daemon::bootstrap_daemon(const std::string &address, const std::optional> &credentials) - : bootstrap_daemon(nullptr) - { - if (!set_server(address, credentials)) - { - throw std::runtime_error("invalid bootstrap daemon address or credentials"); - } - } - - std::string bootstrap_daemon::address() const noexcept - { - return m_http_client.get_base_url(); - } - - std::optional bootstrap_daemon::get_height() - { - // FIXME - throw std::runtime_error{"FIXME"}; - - /* - // query bootstrap daemon's height - rpc::GET_HEIGHT::response res{}; - if (!invoke({}, res)) - { - return std::nullopt; - } - - if (res.status != cryptonote::rpc::STATUS_OK) - { - return std::nullopt; - } - - return res.height; - */ - } - - bool bootstrap_daemon::set_server(std::string url, const std::optional> &credentials /* = std::nullopt */) - { - if (!tools::starts_with(url, "http://") && !tools::starts_with(url, "https://")) - url.insert(0, "http://"); - m_http_client.set_base_url(std::move(url)); - if (credentials) - m_http_client.set_auth(credentials->first, credentials->second); - else - m_http_client.set_auth(); - - MINFO("Changed bootstrap daemon address to " << url); - return true; - } - - - bool bootstrap_daemon::switch_server_if_needed() - { - if (!m_failed || !m_get_next_public_node) - return true; - - const std::optional address = m_get_next_public_node(); - if (address) { - m_failed = false; - return set_server(*address); - } - - return false; - } - -} +// #include "bootstrap_daemon.h" + +// #include + +// #include "common/string_util.h" +// #include "crypto/crypto.h" +// #include "cryptonote_core/cryptonote_core.h" +// #include "epee/misc_log_ex.h" + +// #undef BELDEX_DEFAULT_LOG_CATEGORY +// #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon" + +// namespace cryptonote +// { + +// bootstrap_daemon::bootstrap_daemon(std::function()> get_next_public_node) +// : m_get_next_public_node(get_next_public_node) +// { +// } + +// bootstrap_daemon::bootstrap_daemon(const std::string &address, const std::optional> &credentials) +// : bootstrap_daemon(nullptr) +// { +// if (!set_server(address, credentials)) +// { +// throw std::runtime_error("invalid bootstrap daemon address or credentials"); +// } +// } + +// std::string bootstrap_daemon::address() const noexcept +// { +// return m_http_client.get_base_url(); +// } + +// std::optional bootstrap_daemon::get_height() +// { +// // FIXME +// throw std::runtime_error{"FIXME"}; + +// /* +// // query bootstrap daemon's height +// rpc::GET_HEIGHT::response res{}; +// if (!invoke({}, res)) +// { +// return std::nullopt; +// } + +// if (res.status != cryptonote::rpc::STATUS_OK) +// { +// return std::nullopt; +// } + +// return res.height; +// */ +// } + +// bool bootstrap_daemon::set_server(std::string url, const std::optional> &credentials /* = std::nullopt */) +// { +// if (!tools::starts_with(url, "http://") && !tools::starts_with(url, "https://")) +// url.insert(0, "http://"); +// m_http_client.set_base_url(std::move(url)); +// if (credentials) +// m_http_client.set_auth(credentials->first, credentials->second); +// else +// m_http_client.set_auth(); + +// MINFO("Changed bootstrap daemon address to " << url); +// return true; +// } + + +// bool bootstrap_daemon::switch_server_if_needed() +// { +// if (!m_failed || !m_get_next_public_node) +// return true; + +// const std::optional address = m_get_next_public_node(); +// if (address) { +// m_failed = false; +// return set_server(*address); +// } + +// return false; +// } + +// } diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 9924e66b53f..96c8226a5fd 100755 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -1,53 +1,53 @@ -#pragma once - -#include -#include - -#include "rpc/http_client.h" -#include "rpc/core_rpc_server_commands_defs.h" -#include "rpc/core_rpc_server_binary_commands.h" -namespace cryptonote -{ - - class bootstrap_daemon - { - public: - bootstrap_daemon(std::function()> get_next_public_node); - bootstrap_daemon(const std::string &address, const std::optional> &credentials); - - std::string address() const noexcept; - std::optional get_height(); - // Called when a request has failed either internally or for some external reason; the next - // request will attempt to use a different bootstrap server (if configured). - void set_failed() { m_failed = true; } - - template , int> = 0> - bool invoke(const typename RPC::request& req, typename RPC::response& res) - { - if (!switch_server_if_needed()) - return false; - - try { - if constexpr (std::is_base_of_v) - res = m_http_client.binary(RPC::names().front(), req); - else - res = m_http_client.json_rpc(RPC::names().front(), req); - } catch (const std::exception& e) { - MWARNING("bootstrap daemon request failed: " << e.what()); - set_failed(); - return false; - } - return true; - } - - private: - bool set_server(std::string address, const std::optional> &credentials = std::nullopt); - bool switch_server_if_needed(); - - private: - rpc::http_client m_http_client; - std::function()> m_get_next_public_node; - bool m_failed = false; - }; - -} +// #pragma once + +// #include +// #include + +// #include "rpc/http_client.h" +// #include "rpc/core_rpc_server_commands_defs.h" +// #include "rpc/core_rpc_server_binary_commands.h" +// namespace cryptonote +// { + +// class bootstrap_daemon +// { +// public: +// bootstrap_daemon(std::function()> get_next_public_node); +// bootstrap_daemon(const std::string &address, const std::optional> &credentials); + +// std::string address() const noexcept; +// std::optional get_height(); +// // Called when a request has failed either internally or for some external reason; the next +// // request will attempt to use a different bootstrap server (if configured). +// void set_failed() { m_failed = true; } + +// template , int> = 0> +// bool invoke(const typename RPC::request& req, typename RPC::response& res) +// { +// if (!switch_server_if_needed()) +// return false; + +// try { +// if constexpr (std::is_base_of_v) +// res = m_http_client.binary(RPC::names().front(), req); +// else +// res = m_http_client.json_rpc(RPC::names().front(), req); +// } catch (const std::exception& e) { +// MWARNING("bootstrap daemon request failed: " << e.what()); +// set_failed(); +// return false; +// } +// return true; +// } + +// private: +// bool set_server(std::string address, const std::optional> &credentials = std::nullopt); +// bool switch_server_if_needed(); + +// private: +// rpc::http_client m_http_client; +// std::function()> m_get_next_public_node; +// bool m_failed = false; +// }; + +// } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 21680a72709..6a50e151bf0 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -40,7 +40,7 @@ #include #include "epee/net/network_throttle.hpp" #include "common/string_util.h" -#include "bootstrap_daemon.h" +// #include "bootstrap_daemon.h" #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/tx_extra.h" @@ -202,17 +202,17 @@ namespace cryptonote::rpc { const std::unordered_map> rpc_commands = register_rpc_commands(rpc::core_rpc_types{}, rpc::core_rpc_binary_types{}); - const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { - "bootstrap-daemon-address" - , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." - , "" - }; + // const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { + // "bootstrap-daemon-address" + // , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." + // , "" + // }; - const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_login = { - "bootstrap-daemon-login" - , "Specify username:password for the bootstrap daemon login" - , "" - }; + // const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_login = { + // "bootstrap-daemon-login" + // , "Specify username:password for the bootstrap daemon login" + // , "" + // }; std::optional rpc_request::body_view() const { if (auto* sv = std::get_if(&body)) return *sv; @@ -223,8 +223,8 @@ namespace cryptonote::rpc { //----------------------------------------------------------------------------------- void core_rpc_server::init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden) { - command_line::add_arg(desc, arg_bootstrap_daemon_address); - command_line::add_arg(desc, arg_bootstrap_daemon_login); + // command_line::add_arg(desc, arg_bootstrap_daemon_address); + // command_line::add_arg(desc, arg_bootstrap_daemon_login); cryptonote::rpc_args::init_options(desc, hidden); } //------------------------------------------------------------------------------------------------------------------------------ @@ -234,47 +234,48 @@ namespace cryptonote::rpc { ) : m_core(cr) , m_p2p(p2p) - , m_should_use_bootstrap_daemon(false) - , m_was_bootstrap_ever_used(false) + // , m_should_use_bootstrap_daemon(false) + // , m_was_bootstrap_ever_used(false) + // {} + // bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username_password) + // { + // std::string_view username, password; + // if (auto loc = username_password.find(':'); loc != std::string::npos) + // { + // username = username_password.substr(0, loc); + // password = username_password.substr(loc + 1); + // } + // return set_bootstrap_daemon(address, username, password); + // } + // //------------------------------------------------------------------------------------------------------------------------------ + // bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password) + // { + // std::optional> credentials; + // if (!username.empty() || !password.empty()) + // credentials.emplace(username, password); + + // std::unique_lock lock{m_bootstrap_daemon_mutex}; + + // if (address.empty()) + // m_bootstrap_daemon.reset(); + // else + // m_bootstrap_daemon = std::make_unique(address, credentials); + + // m_should_use_bootstrap_daemon = (bool) m_bootstrap_daemon; + + // return true; + // } + // //------------------------------------------------------------------------------------------------------------------------------ + // void core_rpc_server::init(const boost::program_options::variables_map& vm) + // { + // if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), + // command_line::get_arg(vm, arg_bootstrap_daemon_login))) + // { + // MERROR("Failed to parse bootstrap daemon address"); + // } + // m_was_bootstrap_ever_used = false; + // } {} - bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username_password) - { - std::string_view username, password; - if (auto loc = username_password.find(':'); loc != std::string::npos) - { - username = username_password.substr(0, loc); - password = username_password.substr(loc + 1); - } - return set_bootstrap_daemon(address, username, password); - } - //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password) - { - std::optional> credentials; - if (!username.empty() || !password.empty()) - credentials.emplace(username, password); - - std::unique_lock lock{m_bootstrap_daemon_mutex}; - - if (address.empty()) - m_bootstrap_daemon.reset(); - else - m_bootstrap_daemon = std::make_unique(address, credentials); - - m_should_use_bootstrap_daemon = (bool) m_bootstrap_daemon; - - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::init(const boost::program_options::variables_map& vm) - { - if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), - command_line::get_arg(vm, arg_bootstrap_daemon_login))) - { - MERROR("Failed to parse bootstrap daemon address"); - } - m_was_bootstrap_ever_used = false; - } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_ready() { @@ -412,11 +413,11 @@ namespace cryptonote::rpc { } info.response["free_space"] = m_core.get_free_space(); - if (std::shared_lock lock{m_bootstrap_daemon_mutex}; m_bootstrap_daemon) { - info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); - info.response["height_without_bootstrap"] = height; - info.response["was_bootstrap_ever_used"] = m_was_bootstrap_ever_used; - } + // if (std::shared_lock lock{m_bootstrap_daemon_mutex}; m_bootstrap_daemon) { + // info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); + // info.response["height_without_bootstrap"] = height; + // info.response["was_bootstrap_ever_used"] = m_was_bootstrap_ever_used; + // } } if (m_core.offline()) @@ -466,8 +467,8 @@ namespace cryptonote::rpc { GET_BLOCKS_BIN::response res{}; PERF_TIMER(on_get_blocks); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; std::vector, std::vector > > > bs; @@ -521,8 +522,8 @@ namespace cryptonote::rpc { GET_ALT_BLOCKS_HASHES_BIN::response res{}; PERF_TIMER(on_get_alt_blocks_hashes); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; std::vector blks; @@ -549,8 +550,8 @@ namespace cryptonote::rpc { GET_BLOCKS_BY_HEIGHT_BIN::response res{}; PERF_TIMER(on_get_blocks_by_height); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; res.status = "Failed"; res.blocks.clear(); @@ -583,8 +584,8 @@ namespace cryptonote::rpc { GET_HASHES_BIN::response res{}; PERF_TIMER(on_get_hashes); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; res.start_height = req.start_height; if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, res.start_height, res.current_height, false)) @@ -602,8 +603,8 @@ namespace cryptonote::rpc { GET_OUTPUTS_BIN::response res{}; PERF_TIMER(on_get_outs_bin); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; if (!context.admin && req.outputs.size() > GET_OUTPUTS_BIN::MAX_COUNT) res.status = "Too many outs requested"; @@ -675,8 +676,8 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response res{}; PERF_TIMER(on_get_indexes); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); if(!r) @@ -1411,8 +1412,8 @@ namespace cryptonote::rpc { GET_TRANSACTION_POOL_HASHES_BIN::response res{}; PERF_TIMER(on_get_transaction_pool_hashes); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; std::vector tx_pool_hashes; m_core.get_pool().get_transaction_hashes(tx_pool_hashes, context.admin, req.flashed_txs_only); @@ -1466,18 +1467,18 @@ namespace cryptonote::rpc { stats.response["status"] = STATUS_OK; } - //------------------------------------------------------------------------------------------------------------------------------ - SET_BOOTSTRAP_DAEMON::response core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context) - { - PERF_TIMER(on_set_bootstrap_daemon); + // //------------------------------------------------------------------------------------------------------------------------------ + // SET_BOOTSTRAP_DAEMON::response core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context) + // { + // PERF_TIMER(on_set_bootstrap_daemon); - if (!set_bootstrap_daemon(req.address, req.username, req.password)) - throw rpc_error{ERROR_WRONG_PARAM, "Failed to set bootstrap daemon to address = " + req.address}; + // if (!set_bootstrap_daemon(req.address, req.username, req.password)) + // throw rpc_error{ERROR_WRONG_PARAM, "Failed to set bootstrap daemon to address = " + req.address}; - SET_BOOTSTRAP_DAEMON::response res{}; - res.status = STATUS_OK; - return res; - } + // SET_BOOTSTRAP_DAEMON::response res{}; + // res.status = STATUS_OK; + // return res; + // } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(STOP_DAEMON& stop_daemon, rpc_context context) { @@ -1496,8 +1497,8 @@ namespace cryptonote::rpc { PERF_TIMER(on_get_output_blacklist_bin); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; try { @@ -1516,14 +1517,14 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_BLOCK_COUNT& getblockcount, rpc_context context) { PERF_TIMER(on_getblockcount); - { - std::shared_lock lock{m_bootstrap_daemon_mutex}; - if (m_should_use_bootstrap_daemon) - { - getblockcount.response["status"] = "This command is unsupported for bootstrap daemon"; - return; - } - } + // { + // std::shared_lock lock{m_bootstrap_daemon_mutex}; + // if (m_should_use_bootstrap_daemon) + // { + // getblockcount.response["status"] = "This command is unsupported for bootstrap daemon"; + // return; + // } + // } getblockcount.response["count"] = m_core.get_current_blockchain_height(); getblockcount.response["status"] = STATUS_OK; } @@ -1531,14 +1532,14 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_BLOCK_HASH& getblockhash, rpc_context context) { PERF_TIMER(on_getblockhash); - { - std::shared_lock lock{m_bootstrap_daemon_mutex}; - if (m_should_use_bootstrap_daemon) - { - getblockhash.response["status"] = "This command is unsupported for bootstrap daemon"; - return; - } - } + // { + // std::shared_lock lock{m_bootstrap_daemon_mutex}; + // if (m_should_use_bootstrap_daemon) + // { + // getblockhash.response["status"] = "This command is unsupported for bootstrap daemon"; + // return; + // } + // } auto curr_height = m_core.get_current_blockchain_height(); for (auto h : getblockhash.request.heights) { @@ -1593,7 +1594,7 @@ namespace cryptonote::rpc { response.tx_hashes.push_back(tools::type_to_hex(tx_hash)); } } - +/* /// All the common (untemplated) code for use_bootstrap_daemon_if_necessary. Returns a held lock /// if we need to bootstrap, an unheld one if we don't. std::unique_lock core_rpc_server::should_bootstrap_lock() @@ -1670,7 +1671,7 @@ namespace cryptonote::rpc { m_was_bootstrap_ever_used = true; res.untrusted = true; return true; - } + }*/ //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context) { @@ -2038,8 +2039,8 @@ namespace cryptonote::rpc { GET_OUTPUT_HISTOGRAM::response res{}; PERF_TIMER(on_get_output_histogram); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; if (!context.admin && req.recent_cutoff > 0 && req.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) { @@ -2419,8 +2420,8 @@ namespace cryptonote::rpc { GET_OUTPUT_DISTRIBUTION::response res{}; PERF_TIMER(on_get_output_distribution); - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; try { @@ -2466,8 +2467,8 @@ namespace cryptonote::rpc { return res; } - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; return invoke(std::move(static_cast(req)), context, true); } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index afb97747284..b3e21e6edf7 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -55,9 +55,9 @@ namespace boost::program_options { class variables_map; } -namespace cryptonote { - class bootstrap_daemon; -} +// namespace cryptonote { +// class bootstrap_daemon; +// } namespace cryptonote::rpc { // FIXME: temporary shim for converting RPC methods @@ -185,8 +185,8 @@ namespace cryptonote::rpc { class core_rpc_server { public: - static const command_line::arg_descriptor arg_bootstrap_daemon_address; - static const command_line::arg_descriptor arg_bootstrap_daemon_login; + // static const command_line::arg_descriptor arg_bootstrap_daemon_address; + // static const command_line::arg_descriptor arg_bootstrap_daemon_login; core_rpc_server( core& cr @@ -194,7 +194,7 @@ namespace cryptonote::rpc { ); static void init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden); - void init(const boost::program_options::variables_map& vm); + // void init(const boost::program_options::variables_map& vm); /// Returns a reference to the owning cryptonote core object core& get_core() { return m_core; } @@ -273,7 +273,7 @@ namespace cryptonote::rpc { GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); // FIXME: unconverted JSON RPC endpoints: - SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); + // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); @@ -334,21 +334,21 @@ namespace cryptonote::rpc { //utils uint64_t get_block_reward(const block& blk); - bool set_bootstrap_daemon(const std::string &address, std::string_view username_password); - bool set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password); + // bool set_bootstrap_daemon(const std::string &address, std::string_view username_password); + // bool set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password); void fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash, bool get_tx_hashes); - std::unique_lock should_bootstrap_lock(); + // std::unique_lock should_bootstrap_lock(); - template - bool use_bootstrap_daemon_if_necessary(const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res); + // template + // bool use_bootstrap_daemon_if_necessary(const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res); core& m_core; nodetool::node_server >& m_p2p; - std::shared_mutex m_bootstrap_daemon_mutex; - std::atomic m_should_use_bootstrap_daemon; - std::unique_ptr m_bootstrap_daemon; - std::chrono::system_clock::time_point m_bootstrap_height_check_time; - bool m_was_bootstrap_ever_used; + // std::shared_mutex m_bootstrap_daemon_mutex; + // std::atomic m_should_use_bootstrap_daemon; + // std::unique_ptr m_bootstrap_daemon; + // std::chrono::system_clock::time_point m_bootstrap_height_check_time; + // bool m_was_bootstrap_ever_used; }; } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 995deff5b9b..58e8b6a4130 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -404,6 +404,7 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() +<<<<<<< Updated upstream // KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) // KV_SERIALIZE(block_header) // KV_SERIALIZE(tx_hashes) @@ -412,6 +413,16 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE(json) // KV_SERIALIZE(untrusted) // KV_SERIALIZE_MAP_CODE_END() +======= +KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) + KV_SERIALIZE(block_header) + KV_SERIALIZE(tx_hashes) + KV_SERIALIZE(status) + KV_SERIALIZE(blob) + KV_SERIALIZE(json) + // KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() +>>>>>>> Stashed changes // KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::peer) @@ -557,11 +568,11 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE(untrusted) // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(SET_BOOTSTRAP_DAEMON::request) - KV_SERIALIZE(address) - KV_SERIALIZE(username) - KV_SERIALIZE(password) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_BOOTSTRAP_DAEMON::request) +// KV_SERIALIZE(address) +// KV_SERIALIZE(username) +// KV_SERIALIZE(password) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_LIMIT::response) // KV_SERIALIZE(status) @@ -688,7 +699,7 @@ KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::response) KV_SERIALIZE(status) KV_SERIALIZE(histogram) - KV_SERIALIZE(untrusted) + // KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() @@ -851,7 +862,7 @@ KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::response) KV_SERIALIZE(status) KV_SERIALIZE(distributions) - KV_SERIALIZE(untrusted) + // KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() @@ -901,7 +912,7 @@ KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::response) KV_SERIALIZE(status) KV_SERIALIZE(quorums) - KV_SERIALIZE(untrusted) + // KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index c4d7dfaa43d..19f72e50f8d 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1097,23 +1097,23 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Set the bootstrap daemon to use for data on the blockchain whilst syncing the chain. - struct SET_BOOTSTRAP_DAEMON : RPC_COMMAND - { - static constexpr auto names() { return NAMES("set_bootstrap_daemon"); } - struct request - { + // BELDEX_RPC_DOC_INTROSPECT + // // Set the bootstrap daemon to use for data on the blockchain whilst syncing the chain. + // struct SET_BOOTSTRAP_DAEMON : RPC_COMMAND + // { + // static constexpr auto names() { return NAMES("set_bootstrap_daemon"); } + // struct request + // { - std::string address; - std::string username; - std::string password; + // std::string address; + // std::string username; + // std::string password; - KV_MAP_SERIALIZABLE - }; + // KV_MAP_SERIALIZABLE + // }; - struct response : STATUS {}; - }; + // struct response : STATUS {}; + // }; //----------------------------------------------- /// Stop the daemon. @@ -1355,7 +1355,7 @@ namespace cryptonote::rpc { { std::string status; // General RPC error code. "OK" means everything looks good. std::vector histogram; // List of histogram entries: - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + // bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). KV_MAP_SERIALIZABLE }; @@ -1555,7 +1555,7 @@ namespace cryptonote::rpc { { std::string status; // General RPC error code. "OK" means everything looks good. std::vector distributions; // - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + // bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). KV_MAP_SERIALIZABLE }; @@ -1657,7 +1657,7 @@ namespace cryptonote::rpc { { std::string status; // Generic RPC error code. "OK" is the success value. std::vector quorums; // An array of quorums associated with the requested height - bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. + // bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. KV_MAP_SERIALIZABLE }; @@ -2452,7 +2452,7 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HEIGHT, GET_BLOCK, GET_BLOCK_HEADERS_RANGE, - SET_BOOTSTRAP_DAEMON, + // SET_BOOTSTRAP_DAEMON, GETBANS, SETBANS, GET_OUTPUT_HISTOGRAM, From 526bbfd4ad1517d1dec440b40375a1f0463f8a9f Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 22 Apr 2025 21:05:52 +0530 Subject: [PATCH 092/182] RPC: GET_QUORUM_STATE updated --- src/daemon/rpc_command_executor.cpp | 21 ++++---- src/rpc/core_rpc_server.cpp | 38 ++++++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 58 +++++++++++------------ src/rpc/core_rpc_server_commands_defs.h | 38 ++++++++++----- 5 files changed, 85 insertions(+), 72 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 437184cb79c..3e31254e1e4 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -734,22 +734,23 @@ bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint bool rpc_command_executor::print_quorum_state(uint64_t start_height, uint64_t end_height) { - GET_QUORUM_STATE::request req{}; - GET_QUORUM_STATE::response res{}; - - req.start_height = start_height; - req.end_height = end_height; - req.quorum_type = GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE; - - if (!invoke(std::move(req), res, "Failed to retrieve quorum state")) + auto maybe_quorums = try_running([this, start_height, end_height] { + return invoke(json{ + {"start_height", start_height}, + {"end_height", end_height}, + {"quorum_type", GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE}}); + }, "Failed to retrieve quorum state"); + + if (!maybe_quorums) return false; + auto& quorums = *maybe_quorums; std::string output; output.append("{\n\"quorums\": ["); - for (GET_QUORUM_STATE::quorum_for_height const &quorum : res.quorums) + for (auto const& quorum : quorums["quorums"]) { output.append("\n"); - output.append(epee::serialization::store_t_to_json(quorum)); + output.append(quorum); output.append(",\n"); } output.append("]\n}"); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 6a50e151bf0..0ab6987a4c1 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2492,25 +2492,25 @@ namespace cryptonote::rpc { } - GET_QUORUM_STATE::response core_rpc_server::invoke(GET_QUORUM_STATE::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context) { - GET_QUORUM_STATE::response res{}; - PERF_TIMER(on_get_quorum_state); - if (req.quorum_type >= tools::enum_count && - req.quorum_type != GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE) + uint8_t quorum_type = get_quorum_state.request.quorum_type; + + if (quorum_type >= tools::enum_count && + quorum_type != GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE) throw rpc_error{ERROR_WRONG_PARAM, - "Quorum type specifies an invalid value: " + std::to_string(req.quorum_type)}; + "Quorum type specifies an invalid value: " + std::to_string(get_quorum_state.request.quorum_type)}; - auto requested_type = [&req](master_nodes::quorum_type type) { - return req.quorum_type == GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE || - req.quorum_type == static_cast(type); + auto requested_type = [quorum_type](master_nodes::quorum_type type) { + return quorum_type == GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE || + quorum_type == static_cast(type); }; bool latest = false; uint64_t latest_ob = 0, latest_cp = 0, latest_bl = 0; - uint64_t start = req.start_height, end = req.end_height; + uint64_t start = get_quorum_state.request.start_height, end = get_quorum_state.request.end_height; uint64_t curr_height = m_core.get_blockchain_storage().get_current_blockchain_height(); if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE && end == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE) @@ -2552,7 +2552,7 @@ namespace cryptonote::rpc { bool add_curr_POS = (latest || end > curr_height) && requested_type(master_nodes::quorum_type::POS); end = std::min(curr_height, end); - uint64_t count = (start > end) ? start - end : end - start; + uint64_t count = (start > end) ? start - end : end - start; if (!context.admin && count > GET_QUORUM_STATE::MAX_COUNT) throw rpc_error{ERROR_WRONG_PARAM, "Number of requested quorums greater than the allowed limit: " @@ -2560,7 +2560,8 @@ namespace cryptonote::rpc { + ", requested: " + std::to_string(count)}; bool at_least_one_succeeded = false; - res.quorums.reserve(std::min((uint64_t)16, count)); + std::vector quorums; + quorums.reserve(std::min((uint64_t)16, count)); auto net = nettype(); for (size_t height = start; height != end;) { @@ -2569,9 +2570,9 @@ namespace cryptonote::rpc { auto start_quorum_iterator = static_cast(0); auto end_quorum_iterator = master_nodes::max_quorum_type_for_hf(hf_version); - if (req.quorum_type != GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE) + if (quorum_type != GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE) { - start_quorum_iterator = static_cast(req.quorum_type); + start_quorum_iterator = static_cast(quorum_type); end_quorum_iterator = start_quorum_iterator; } @@ -2587,7 +2588,7 @@ namespace cryptonote::rpc { } if (std::shared_ptr quorum = m_core.get_quorum(type, height, true /*include_old*/)) { - auto& entry = res.quorums.emplace_back(); + auto& entry = quorums.emplace_back(); entry.height = height; entry.quorum_type = static_cast(quorum_int); entry.quorum.validators = hexify(quorum->validators); @@ -2617,7 +2618,7 @@ namespace cryptonote::rpc { auto quorum = generate_POS_quorum(m_core.get_nettype(), mn_list.get_block_leader().key, hf_version, mn_list.active_master_nodes_infos(), entropy, POS_round); if (verify_POS_quorum_sizes(quorum)) { - auto& entry = res.quorums.emplace_back(); + auto& entry = quorums.emplace_back(); entry.height = curr_height; entry.quorum_type = static_cast(master_nodes::quorum_type::POS); @@ -2632,8 +2633,9 @@ namespace cryptonote::rpc { if (!at_least_one_succeeded) throw rpc_error{ERROR_WRONG_PARAM, "Failed to query any quorums at all"}; - res.status = STATUS_OK; - return res; + get_quorum_state.response["quorums"] = quorums; + get_quorum_state.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(FLUSH_CACHE& flush_cache, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b3e21e6edf7..9d13c257926 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -259,6 +259,7 @@ namespace cryptonote::rpc { void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); void invoke(GET_BLOCK& get_block, rpc_context context); + void invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -276,7 +277,6 @@ namespace cryptonote::rpc { // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); - GET_QUORUM_STATE::response invoke(GET_QUORUM_STATE::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 58e8b6a4130..32496e30e2b 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -68,6 +68,15 @@ void from_json(const nlohmann::json& j, block_header_response& h) j.at("master_node_winner").get_to(h.master_node_winner); }; +void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q) +{ + j = nlohmann::json{{"validators", q.validators}, {"workers", q.workers}}; +}; +void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q) +{ + j = nlohmann::json{{"height", q.height}, {"quorum_type", q.quorum_type}, {"quorum", q.quorum}}; +}; + KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() @@ -404,7 +413,6 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -<<<<<<< Updated upstream // KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) // KV_SERIALIZE(block_header) // KV_SERIALIZE(tx_hashes) @@ -413,16 +421,6 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE(json) // KV_SERIALIZE(untrusted) // KV_SERIALIZE_MAP_CODE_END() -======= -KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCK::response) - KV_SERIALIZE(block_header) - KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(status) - KV_SERIALIZE(blob) - KV_SERIALIZE(json) - // KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() ->>>>>>> Stashed changes // KV_SERIALIZE_MAP_CODE_BEGIN(GET_PEER_LIST::peer) @@ -889,31 +887,31 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::request) - KV_SERIALIZE_OPT(start_height, HEIGHT_SENTINEL_VALUE) - KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) - KV_SERIALIZE_OPT(quorum_type, ALL_QUORUMS_SENTINEL_VALUE) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::request) +// KV_SERIALIZE_OPT(start_height, HEIGHT_SENTINEL_VALUE) +// KV_SERIALIZE_OPT(end_height, HEIGHT_SENTINEL_VALUE) +// KV_SERIALIZE_OPT(quorum_type, ALL_QUORUMS_SENTINEL_VALUE) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::quorum_t) - KV_SERIALIZE(validators) - KV_SERIALIZE(workers) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::quorum_t) +// KV_SERIALIZE(validators) +// KV_SERIALIZE(workers) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::quorum_for_height) - KV_SERIALIZE(height) - KV_SERIALIZE(quorum_type) - KV_SERIALIZE(quorum) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::quorum_for_height) +// KV_SERIALIZE(height) +// KV_SERIALIZE(quorum_type) +// KV_SERIALIZE(quorum) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::response) - KV_SERIALIZE(status) - KV_SERIALIZE(quorums) - // KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_QUORUM_STATE::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(quorums) +// KV_SERIALIZE(untrusted) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 19f72e50f8d..a38827f2aa2 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1607,8 +1607,24 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT - // Accesses the list of public keys of the nodes who are participating or being tested in a quorum. + /// Accesses the list of public keys of the nodes who are participating or being tested in a quorum. + /// + /// Inputs: + /// + /// - \p start_height (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. + /// - \p end_height (Optional): End height, omit both start and end height to request the latest quorum + /// - \p quorum_type (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, 255 = all quorums, default is all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p quorums An array of quorums associated with the requested height. Each element is structured with the following keys: + /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). + /// - \p height The height the quorums are relevant for + /// - \p quorum_type The quorum type + /// - \p quorum Quorum of Master Nodes. Each element is structured with the following keys: + /// - \p validators List of master node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and Flash these are the participating nodes (there are no workers); for POS Flash quorums these are the block signers. This is hex encoded, even for bt-encoded requests. + /// - \p workers Public key of the quorum workers. For obligations quorums these are the nodes being tested; for POS quorums this is the block producer. Checkpoint and Flash quorums do not populate this field. This is hex encoded, even for bt-encoded requests. struct GET_QUORUM_STATE : PUBLIC { static constexpr auto names() { return NAMES("get_quorum_state"); } @@ -1616,26 +1632,17 @@ namespace cryptonote::rpc { static constexpr size_t MAX_COUNT = 256; static constexpr uint64_t HEIGHT_SENTINEL_VALUE = UINT64_MAX; static constexpr uint8_t ALL_QUORUMS_SENTINEL_VALUE = 255; - struct request + struct request_parameters { uint64_t start_height; // (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. uint64_t end_height; // (Optional): End height, omit both start and end height to request the latest quorum uint8_t quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, 255 = all quorums, default is all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. - - KV_MAP_SERIALIZABLE - }; + } request; struct quorum_t { std::vector validators; // List of master node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and flash these are the participating nodes (there are no workers); for POS flash quorums these are the block signers. std::vector workers; // Public key of the quorum workers. For obligations quorums these are the nodes being tested; for POS quorums this is the block producer. Checkpoint and Flash quorums do not populate this field. - - KV_MAP_SERIALIZABLE - - BEGIN_SERIALIZE() // NOTE: For store_t_to_json - FIELD(validators) - FIELD(workers) - END_SERIALIZE() }; struct quorum_for_height @@ -1643,6 +1650,7 @@ namespace cryptonote::rpc { uint64_t height; // The height the quorums are relevant for uint8_t quorum_type; // The quorum type quorum_t quorum; // Quorum of Master Nodes +<<<<<<< Updated upstream KV_MAP_SERIALIZABLE @@ -1660,8 +1668,12 @@ namespace cryptonote::rpc { // bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. KV_MAP_SERIALIZABLE +======= +>>>>>>> Stashed changes }; }; + inline void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q); + inline void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q); BELDEX_RPC_DOC_INTROSPECT struct GET_MASTER_NODE_REGISTRATION_CMD_RAW : RPC_COMMAND From d183f438db576fe3f74a68d4f9624461c340c25d Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Tue, 22 Apr 2025 21:29:04 +0530 Subject: [PATCH 093/182] GET_ALTERNATE_CHAINS --- src/daemon/rpc_command_executor.cpp | 15 ++++--- src/rpc/core_rpc_server.cpp | 30 ++++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 45 ++++++++++++++----- src/rpc/core_rpc_server_commands_defs.h | 53 ++++++++--------------- 5 files changed, 75 insertions(+), 70 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 3e31254e1e4..66ef3776e94 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1277,15 +1277,16 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, if (!height) return false; - GET_ALTERNATE_CHAINS::response res{}; - - if (!invoke({}, res, "Failed to retrieve alt chain data")) + auto maybe_chains = try_running([this] { + return invoke(); + }, "Failed to retrieve node info"); + if (!maybe_chains) return false; + std::vector chains = (*maybe_chains)["chains"]; if (tip.empty()) { - auto chains = res.chains; - std::sort(chains.begin(), chains.end(), [](const GET_ALTERNATE_CHAINS::chain_info &info0, GET_ALTERNATE_CHAINS::chain_info &info1){ return info0.height < info1.height; }); + std::sort(chains.begin(), chains.end(), [](const auto& info0, auto& info1){ return info0.height < info1.height; }); std::vector display; for (size_t i = 0; i < chains.size(); ++i) { @@ -1309,8 +1310,8 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, else { const uint64_t now = time(NULL); - const auto i = std::find_if(res.chains.begin(), res.chains.end(), [&tip](GET_ALTERNATE_CHAINS::chain_info &info){ return info.block_hash == tip; }); - if (i != res.chains.end()) + const auto i = std::find_if(chains.begin(), chains.end(), [&tip](GET_ALTERNATE_CHAINS::chain_info &info){ return info.block_hash == tip; }); + if (i != chains.end()) { const auto &chain = *i; tools::success_msg_writer() << "Found alternate chain with tip " << tip; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0ab6987a4c1..d703426846e 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2142,37 +2142,37 @@ namespace cryptonote::rpc { get_base_fee_estimate.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GET_ALTERNATE_CHAINS::response core_rpc_server::invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context) { - GET_ALTERNATE_CHAINS::response res{}; - PERF_TIMER(on_get_alternate_chains); try { - std::vector>> chains = m_core.get_blockchain_storage().get_alternative_chains(); - for (const auto &i: chains) + std::vector chains; + std::vector>> alt_chains = m_core.get_blockchain_storage().get_alternative_chains(); + for (const auto &i: alt_chains) { - res.chains.push_back(GET_ALTERNATE_CHAINS::chain_info{tools::type_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second.size(), i.first.cumulative_difficulty, {}, std::string()}); - res.chains.back().block_hashes.reserve(i.second.size()); + chains.push_back(GET_ALTERNATE_CHAINS::chain_info{tools::type_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second.size(), i.first.cumulative_difficulty, {}, std::string()}); + chains.back().block_hashes.reserve(i.second.size()); for (const crypto::hash &block_id: i.second) - res.chains.back().block_hashes.push_back(tools::type_to_hex(block_id)); + chains.back().block_hashes.push_back(tools::type_to_hex(block_id)); if (i.first.height < i.second.size()) { - res.status = "Error finding alternate chain attachment point"; - return res; + get_alternate_chains.response["status"] = "Error finding alternate chain attachment point"; + return; } cryptonote::block main_chain_parent_block; try { main_chain_parent_block = m_core.get_blockchain_storage().get_db().get_block_from_height(i.first.height - i.second.size()); } - catch (const std::exception &e) { res.status = "Error finding alternate chain attachment point"; return res; } - res.chains.back().main_chain_parent_block = tools::type_to_hex(get_block_hash(main_chain_parent_block)); + catch (const std::exception &e) { get_alternate_chains.response["status"] = "Error finding alternate chain attachment point"; return; } + chains.back().main_chain_parent_block = tools::type_to_hex(get_block_hash(main_chain_parent_block)); } - res.status = STATUS_OK; + get_alternate_chains.response["chains"] = chains; + get_alternate_chains.response["status"] = STATUS_OK; } catch (...) { - res.status = "Error retrieving alternate chains"; + get_alternate_chains.response["status"] = "Error retrieving alternate chains"; } - return res; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_LIMIT& limit, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 9d13c257926..9d82ad2ad63 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -260,6 +260,7 @@ namespace cryptonote::rpc { void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); void invoke(GET_BLOCK& get_block, rpc_context context); void invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context); + void invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -276,7 +277,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); - GET_ALTERNATE_CHAINS::response invoke(GET_ALTERNATE_CHAINS::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 32496e30e2b..576800fe4e4 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -77,6 +77,27 @@ void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q) j = nlohmann::json{{"height", q.height}, {"quorum_type", q.quorum_type}, {"quorum", q.quorum}}; }; +void to_json(nlohmann::json& j, const GET_ALTERNATE_CHAINS::chain_info& c) +{ + j = nlohmann::json{ + {"block_hash", c.block_hash}, + {"height", c.height}, + {"length", c.length}, + {"difficulty", c.difficulty}, + {"block_hashes", c.block_hashes}, + {"main_chain_parent_block", c.main_chain_parent_block}, + }; +} +void from_json(const nlohmann::json& j, GET_ALTERNATE_CHAINS::chain_info& c) +{ + j.at("block_hash").get_to(c.block_hash); + j.at("height").get_to(c.height); + j.at("length").get_to(c.length); + j.at("difficulty").get_to(c.difficulty); + j.at("block_hashes").get_to(c.block_hashes); + j.at("main_chain_parent_block").get_to(c.main_chain_parent_block); +} + KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() @@ -739,20 +760,20 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALTERNATE_CHAINS::chain_info) - KV_SERIALIZE(block_hash) - KV_SERIALIZE(height) - KV_SERIALIZE(length) - KV_SERIALIZE(difficulty) - KV_SERIALIZE(block_hashes) - KV_SERIALIZE(main_chain_parent_block) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALTERNATE_CHAINS::chain_info) +// KV_SERIALIZE(block_hash) +// KV_SERIALIZE(height) +// KV_SERIALIZE(length) +// KV_SERIALIZE(difficulty) +// KV_SERIALIZE(block_hashes) +// KV_SERIALIZE(main_chain_parent_block) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALTERNATE_CHAINS::response) - KV_SERIALIZE(status) - KV_SERIALIZE(chains) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_ALTERNATE_CHAINS::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(chains) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(RELAY_TX::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index a38827f2aa2..c0e9c6f80f9 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1434,13 +1434,24 @@ namespace cryptonote::rpc { }; BELDEX_RPC_DOC_INTROSPECT - // Display alternative chains seen by the node. + /// Display alternative chains seen by the node. + /// + /// Inputs: None + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p chains Array of Chains. Each element is contains the following keys: + /// - \p block_hash The block hash of the first diverging block of this alternative chain. + /// - \p height The block height of the first diverging block of this alternative chain. + /// - \p length The length in blocks of this alternative chain, after divergence. + /// - \p difficulty The cumulative difficulty of all blocks in the alternative chain. + /// - \p block_hashes List containing hex block hashes + /// - \p main_chain_parent_block struct GET_ALTERNATE_CHAINS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_alternative_chains"); } - struct request : EMPTY {}; - struct chain_info { std::string block_hash; // The block hash of the first diverging block of this alternative chain. @@ -1449,18 +1460,10 @@ namespace cryptonote::rpc { uint64_t difficulty; // The cumulative difficulty of all blocks in the alternative chain. std::vector block_hashes; std::string main_chain_parent_block; - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector chains; // Array of Chains. - - KV_MAP_SERIALIZABLE }; }; + void to_json(nlohmann::json& j, const GET_ALTERNATE_CHAINS::chain_info& c); + void from_json(const nlohmann::json& j, GET_ALTERNATE_CHAINS::chain_info& c); /// Relay a list of transaction IDs. /// @@ -1650,30 +1653,10 @@ namespace cryptonote::rpc { uint64_t height; // The height the quorums are relevant for uint8_t quorum_type; // The quorum type quorum_t quorum; // Quorum of Master Nodes -<<<<<<< Updated upstream - - KV_MAP_SERIALIZABLE - - BEGIN_SERIALIZE() // NOTE: For store_t_to_json - FIELD(height) - FIELD(quorum_type) - FIELD(quorum) - END_SERIALIZE() - }; - - struct response - { - std::string status; // Generic RPC error code. "OK" is the success value. - std::vector quorums; // An array of quorums associated with the requested height - // bool untrusted; // If the result is obtained using bootstrap mode, and therefore not trusted `true`, or otherwise `false`. - - KV_MAP_SERIALIZABLE -======= ->>>>>>> Stashed changes }; }; - inline void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q); - inline void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q); + void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q); + void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q); BELDEX_RPC_DOC_INTROSPECT struct GET_MASTER_NODE_REGISTRATION_CMD_RAW : RPC_COMMAND From 1e52c81fb58484d6cebacc467df4edfb86cbb696 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Tue, 22 Apr 2025 22:59:22 +0530 Subject: [PATCH 094/182] GET_OUTPUT_HISTOGRAM --- src/daemon/rpc_command_executor.cpp | 34 ++++++-------- src/rpc/core_rpc_server.cpp | 41 ++++++++++------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 8 ++++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 51 ++++++++++++++------- src/rpc/core_rpc_server_commands_defs.h | 53 ++++++++++------------ 7 files changed, 106 insertions(+), 84 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 66ef3776e94..f690df6128a 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1232,26 +1232,22 @@ bool rpc_command_executor::flush_txpool(std::string txid) bool rpc_command_executor::output_histogram(const std::vector &amounts, uint64_t min_count, uint64_t max_count) { - GET_OUTPUT_HISTOGRAM::request req{}; - GET_OUTPUT_HISTOGRAM::response res{}; - - req.amounts = amounts; - req.min_count = min_count; - req.max_count = max_count; - req.unlocked = false; - req.recent_cutoff = 0; - - if (!invoke(std::move(req), res, "Failed to retrieve output histogram")) + auto maybe_histogram= try_running([this, &amounts, min_count, max_count] + { return invoke( + json{{"amounts", amounts}, + {"min_count", min_count}, + {"max_count", max_count}, + {"unlocked", false}, + {"recent_cutoff", 0}}); + }, "Failed to retrieve output histogram"); + if (!maybe_histogram) return false; - - // std::sort(res.histogram.begin(), res.histogram.end(), - // [](const auto& e1, const auto& e2)->bool { return e1.total_instances < e2.total_instances; }); - // for (const auto &e: res.histogram) - // { - // tools::msg_writer() << e.total_instances << " " << cryptonote::print_money(e.amount); - // } - - return true; + std::vector histogram = (*maybe_histogram)["histogram"]; + std::sort(histogram.begin(), histogram.end(), + [](const auto& e1, const auto& e2)->bool { return e1.total_instances < e2.total_instances; }); + for (const auto &e: histogram) + tools::msg_writer() << e.total_instances << " " << cryptonote::print_money(e.amount); + return true; } bool rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t count) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index d703426846e..1d4843d2922 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2034,42 +2034,49 @@ namespace cryptonote::rpc { : STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GET_OUTPUT_HISTOGRAM::response core_rpc_server::invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context) { - GET_OUTPUT_HISTOGRAM::response res{}; - PERF_TIMER(on_get_output_histogram); // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - if (!context.admin && req.recent_cutoff > 0 && req.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) + if (!context.admin && get_output_histogram.request.recent_cutoff > 0 && get_output_histogram.request.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) { - res.status = "Recent cutoff is too old"; - return res; + get_output_histogram.response["status"] = "Recent cutoff is too old"; + return; } std::map> histogram; try { auto net = nettype(); - histogram = m_core.get_blockchain_storage().get_output_histogram(req.amounts, req.unlocked, req.recent_cutoff, req.min_count,net); - } + histogram = m_core.get_blockchain_storage().get_output_histogram( + get_output_histogram.request.amounts, + get_output_histogram.request.unlocked, + get_output_histogram.request.recent_cutoff, + get_output_histogram.request.min_count, + net + ); + } catch (const std::exception &e) { - res.status = "Failed to get output histogram"; - return res; + get_output_histogram.response["status"] = "Failed to get output histogram"; + return; } - res.histogram.clear(); - res.histogram.reserve(histogram.size()); - for (const auto &i: histogram) + std::vector response_histogram; + response_histogram.reserve(histogram.size()); + for (const auto &[amount, histogram_tuple]: histogram) { - if (std::get<0>(i.second) >= req.min_count && (std::get<0>(i.second) <= req.max_count || req.max_count == 0)) - res.histogram.push_back(GET_OUTPUT_HISTOGRAM::entry(i.first, std::get<0>(i.second), std::get<1>(i.second), std::get<2>(i.second))); + auto& [total_instances, unlocked_instances, recent_instances] = histogram_tuple; + + if (total_instances >= get_output_histogram.request.min_count && (total_instances <= get_output_histogram.request.max_count || get_output_histogram.request.max_count == 0)) + response_histogram.push_back(GET_OUTPUT_HISTOGRAM::entry{amount, total_instances, unlocked_instances, recent_instances}); } - res.status = STATUS_OK; - return res; + get_output_histogram.response["histogram"] = response_histogram; + get_output_histogram.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_VERSION& version, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 9d82ad2ad63..cc4157b506c 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -261,6 +261,7 @@ namespace cryptonote::rpc { void invoke(GET_BLOCK& get_block, rpc_context context); void invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context); void invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context); + void invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -276,7 +277,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - GET_OUTPUT_HISTOGRAM::response invoke(GET_OUTPUT_HISTOGRAM::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 58aef08e3df..33ca4c6bec8 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -528,4 +528,12 @@ namespace cryptonote::rpc { get_values(in, "height", get_block.request.height); get_values(in, "fill_pow_hash", get_block.request.fill_pow_hash); } + + void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in) { + get_values(in, "amounts", get_output_histogram.request.amounts); + get_values(in, "min_count", get_output_histogram.request.min_count); + get_values(in, "max_count", get_output_histogram.request.max_count); + get_values(in, "unlocked", get_output_histogram.request.unlocked); + get_values(in, "recent_cutoff", get_output_histogram.request.recent_cutoff); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 108bb684324..e61656c67d1 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -52,4 +52,5 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); void parse_request(GET_BLOCK& get_block, rpc_input in); + void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 576800fe4e4..d58c4565133 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -98,6 +98,23 @@ void from_json(const nlohmann::json& j, GET_ALTERNATE_CHAINS::chain_info& c) j.at("main_chain_parent_block").get_to(c.main_chain_parent_block); } +void to_json(nlohmann::json& j, const GET_OUTPUT_HISTOGRAM::entry& e) +{ + j = nlohmann::json{ + {"amount", e.amount}, + {"total_instances", e.total_instances}, + {"unlocked_instances", e.unlocked_instances}, + {"recent_instances", e.recent_instances}, + }; +} +void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& e) +{ + j.at("amount").get_to(e.amount); + j.at("total_instances").get_to(e.total_instances); + j.at("unlocked_instances").get_to(e.unlocked_instances); + j.at("recent_instances").get_to(e.recent_instances); +}; + KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() @@ -698,28 +715,28 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::request) - KV_SERIALIZE(amounts); - KV_SERIALIZE(min_count); - KV_SERIALIZE(max_count); - KV_SERIALIZE(unlocked); - KV_SERIALIZE(recent_cutoff); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::request) +// KV_SERIALIZE(amounts); +// KV_SERIALIZE(min_count); +// KV_SERIALIZE(max_count); +// KV_SERIALIZE(unlocked); +// KV_SERIALIZE(recent_cutoff); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::entry) - KV_SERIALIZE(amount); - KV_SERIALIZE(total_instances); - KV_SERIALIZE(unlocked_instances); - KV_SERIALIZE(recent_instances); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::entry) +// KV_SERIALIZE(amount); +// KV_SERIALIZE(total_instances); +// KV_SERIALIZE(unlocked_instances); +// KV_SERIALIZE(recent_instances); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::response) - KV_SERIALIZE(status) - KV_SERIALIZE(histogram) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_HISTOGRAM::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(histogram) // KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_VERSION::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index c0e9c6f80f9..281a7e7419b 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -402,7 +402,6 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// untrusted ('true'), or when the daemon is fully synced ('false'). /// - \p spent_status array of status codes returned in the same order as the `key_images` input. /// Each value is one of: /// - \p 0 the key image is unspent @@ -437,7 +436,6 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// untrusted ('true'), or when the daemon is fully synced ('false'). /// - \p outs List of outkey information; if `as_tuple` is not set then these are dicts containing /// keys: /// - \p key The public key of the output. @@ -477,7 +475,6 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// untrusted ('true'), or when the daemon is fully synced ('false'). /// - \p reason String containing additional information on why a transaction failed. /// - \p flash_status Set to the result of submitting this transaction to the flash quorum. 1 /// means the quorum rejected the transaction; 2 means the quorum accepted it; 3 means there was @@ -631,7 +628,6 @@ namespace cryptonote::rpc { /// - \p bns_counts BNS registration counts. /// - \p offline Indicates that the node is offline, if true. Omitted for online nodes. /// - \p untrusted Indicates that the result was obtained using a bootstrap mode, and is therefore - /// not trusted (`true`). Omitted for non-bootstrap responses. /// - \p database_size Current size of Blockchain data. Over public RPC this is rounded up to the /// next-largest GB value. /// - \p version Current version of this daemon, as a string. For a public node this will just be @@ -963,7 +959,6 @@ namespace cryptonote::rpc { /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p tx_hashes List of transaction hashes, /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not - /// trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } @@ -1016,7 +1011,6 @@ namespace cryptonote::rpc { /// - \p histo_98pc See `histo` for details. /// - \p histo_max See `histo` for details. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not - /// trusted (`true`), or when the daemon is fully synced (`false`). struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } @@ -1214,7 +1208,6 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// untrusted ('true'), or when the daemon is fully synced ('false'). /// - \p version The major block version for the fork. /// - \p enabled Indicates whether the hard fork is enforced on the blockchain (that is, whether /// the blockchain height is at or above the requested hardfork). @@ -1319,23 +1312,37 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Get a histogram of output amounts. For all amounts (possibly filtered by parameters), - // gives the number of outputs on the chain for that amount. RingCT outputs counts as 0 amount. + /// Get a histogram of output amounts. For all amounts (possibly filtered by parameters), + /// gives the number of outputs on the chain for that amount. RingCT outputs counts as 0 amount. + /// + /// Inputs: + /// + /// - \p amounts list of amounts in Atomic Units. + /// - \p min_count The minimum amounts you are requesting. + /// - \p max_count The maximum amounts you are requesting. + /// - \p unlocked Look for locked only. + /// - \p recent_cutoff + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p histogram List of histogram entries. Each element is structured as follows: + /// - \p uint64_t amount Output amount in atomic units. + /// - \p uint64_t total_instances + /// - \p uint64_t unlocked_instances + /// - \p uint64_t recent_instances struct GET_OUTPUT_HISTOGRAM : PUBLIC { static constexpr auto names() { return NAMES("get_output_histogram"); } - struct request + struct request_parameters { std::vector amounts; // list of amounts in Atomic Units. uint64_t min_count; // The minimum amounts you are requesting. uint64_t max_count; // The maximum amounts you are requesting. bool unlocked; // Look for locked only. uint64_t recent_cutoff; - - KV_MAP_SERIALIZABLE - }; + } request; struct entry { @@ -1343,23 +1350,10 @@ namespace cryptonote::rpc { uint64_t total_instances; uint64_t unlocked_instances; uint64_t recent_instances; - - KV_MAP_SERIALIZABLE - - entry(uint64_t amount, uint64_t total_instances, uint64_t unlocked_instances, uint64_t recent_instances): - amount(amount), total_instances(total_instances), unlocked_instances(unlocked_instances), recent_instances(recent_instances) {} - entry() = default; - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector histogram; // List of histogram entries: - // bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE }; }; + void to_json(nlohmann::json& j, const GET_OUTPUT_HISTOGRAM::entry& c); + void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& c); /// Get current RPC protocol version. /// @@ -1370,7 +1364,6 @@ namespace cryptonote::rpc { /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p version RPC current version. /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not - /// trusted (`true`), or when the daemon is fully synced struct GET_VERSION : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_version"); } From 809c5c63fe9cf5214d76d916ccd46651c67e78d7 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Wed, 23 Apr 2025 11:30:38 +0530 Subject: [PATCH 095/182] move nlohmann all to rpc --- src/blockchain_db/CMakeLists.txt | 1 - src/checkpoints/CMakeLists.txt | 1 - src/checkpoints/checkpoints.cpp | 14 ------------ src/checkpoints/checkpoints.h | 1 - src/cryptonote_basic/CMakeLists.txt | 1 - src/cryptonote_basic/cryptonote_basic.h | 10 -------- src/cryptonote_core/master_node_list.h | 2 -- src/device/CMakeLists.txt | 1 - src/multisig/CMakeLists.txt | 1 - src/ringct/CMakeLists.txt | 2 -- src/rpc/core_rpc_server_commands_defs.cpp | 28 +++++++++++++++++++++++ src/rpc/core_rpc_server_commands_defs.h | 11 ++++++++- 12 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index c1e6d6fdbbd..e833a139bfb 100755 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -39,7 +39,6 @@ target_link_libraries(blockchain_db lmdb filesystem Boost::thread - nlohmann_json::nlohmann_json extra) target_compile_definitions(blockchain_db PRIVATE diff --git a/src/checkpoints/CMakeLists.txt b/src/checkpoints/CMakeLists.txt index 77c293f8c31..0eb84f9443d 100755 --- a/src/checkpoints/CMakeLists.txt +++ b/src/checkpoints/CMakeLists.txt @@ -37,6 +37,5 @@ target_link_libraries(checkpoints cryptonote_basic Boost::program_options Boost::serialization - nlohmann_json::nlohmann_json filesystem extra) diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 1f77aaeaaea..23d0d6a77a9 100755 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -36,7 +36,6 @@ #include "epee/serialization/keyvalue_serialization.h" #include "cryptonote_core/master_node_rules.h" #include -#include #include "blockchain_db/blockchain_db.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -58,19 +57,6 @@ namespace cryptonote return result; }; - void to_json(nlohmann::json& j, const checkpoint_t& c) - { - j = nlohmann::json - { - {"version", c.version}, - {"type", c.type}, - {"height", c.height}, - {"block_hash", tools::type_to_hex(c.block_hash)}, - {"signatures", c.signatures}, - {"prev_height", c.prev_height}, - }; - }; - height_to_hash const HARDCODED_MAINNET_CHECKPOINTS[] = { {0, "6ea477622339f61c5fba036dc75c08b6efcf9ee09c108e5c5591fcc233d17b20"}, diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index 9f8027f0d4f..066487d373a 100755 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -79,7 +79,6 @@ namespace cryptonote FIELD(prev_height) END_SERIALIZE() }; - void to_json(nlohmann::json& j, const checkpoint_t& c); struct height_to_hash { diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index a14d1d8d2ff..e503d9e5038 100755 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -44,5 +44,4 @@ target_link_libraries(cryptonote_basic Boost::program_options Boost::serialization filesystem - nlohmann_json::nlohmann_json extra) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index d5eff79cb82..c5a904288f0 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -45,7 +45,6 @@ #include "ringct/rctTypes.h" #include "device/device.hpp" #include "txtypes.h" -#include namespace master_nodes { @@ -66,15 +65,6 @@ namespace master_nodes FIELD(signature) END_SERIALIZE() }; - - inline void to_json(nlohmann::json& j, const quorum_signature& s) - { - j = nlohmann::json - { - {"voter_index", s.voter_index}, - {"signature", tools::type_to_hex(s.signature)}, - }; - }; }; namespace cryptonote diff --git a/src/cryptonote_core/master_node_list.h b/src/cryptonote_core/master_node_list.h index 5a28f89c95e..26a642acc0a 100755 --- a/src/cryptonote_core/master_node_list.h +++ b/src/cryptonote_core/master_node_list.h @@ -39,7 +39,6 @@ #include "cryptonote_core/master_node_voting.h" #include "cryptonote_core/master_node_quorum_cop.h" #include "common/util.h" -#include namespace cryptonote { @@ -382,7 +381,6 @@ namespace master_nodes END_SERIALIZE() }; - inline void to_json(nlohmann::json& j, const key_image_blacklist_entry& b) { j = nlohmann::json{{"key_image", tools::type_to_hex(b.key_image)}, {"unlock_height", b.unlock_height}, {"amount", b.amount} }; }; struct payout_entry { cryptonote::account_public_address address; diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt index 97a678fb23d..54d6783342e 100755 --- a/src/device/CMakeLists.txt +++ b/src/device/CMakeLists.txt @@ -41,7 +41,6 @@ target_link_libraries(device Boost::serialization PRIVATE version - nlohmann_json::nlohmann_json extra) option(HWDEVICE_DEBUG "Enable hardware wallet debugging (requires also using a debug build of the Ledger wallet)" OFF) diff --git a/src/multisig/CMakeLists.txt b/src/multisig/CMakeLists.txt index 8e885773353..a529cea2579 100755 --- a/src/multisig/CMakeLists.txt +++ b/src/multisig/CMakeLists.txt @@ -35,5 +35,4 @@ target_link_libraries(multisig ringct cryptonote_basic common - nlohmann_json::nlohmann_json extra) diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index ae4c0ba5a47..d27425e0c10 100755 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -38,7 +38,6 @@ target_link_libraries(ringct_basic PUBLIC common PRIVATE - nlohmann_json::nlohmann_json extra) add_library(ringct @@ -50,5 +49,4 @@ target_link_libraries(ringct cryptonote_basic device PRIVATE - nlohmann_json::nlohmann_json extra) diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index d58c4565133..667cb72ee17 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -2,6 +2,34 @@ #include #include +namespace cryptonote { + void to_json(nlohmann::json& j, const checkpoint_t& c) + { + j = nlohmann::json + { + {"version", c.version}, + {"type", c.type}, + {"height", c.height}, + {"block_hash", tools::type_to_hex(c.block_hash)}, + {"signatures", c.signatures}, + {"prev_height", c.prev_height}, + }; + }; +} + +namespace master_nodes { + void to_json(nlohmann::json& j, const key_image_blacklist_entry& b) { j = nlohmann::json{{"key_image", tools::type_to_hex(b.key_image)}, {"unlock_height", b.unlock_height}, {"amount", b.amount} }; }; + + void to_json(nlohmann::json& j, const quorum_signature& s) + { + j = nlohmann::json + { + {"voter_index", s.voter_index}, + {"signature", tools::type_to_hex(s.signature)}, + }; + }; +} + namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 281a7e7419b..bde9dfaca4d 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -55,6 +55,7 @@ #include "crypto/hash.h" #include "cryptonote_config.h" #include "cryptonote_core/master_node_voting.h" +#include "cryptonote_core/master_node_list.h" #include "common/varint.h" #include "common/perf_timer.h" #include "common/meta.h" @@ -71,9 +72,17 @@ #include #include +namespace cryptonote { + void to_json(nlohmann::json& j, const checkpoint_t& c); +} + +namespace master_nodes { + void to_json(nlohmann::json& j, const key_image_blacklist_entry& b); + void to_json(nlohmann::json& j, const quorum_signature& s); +} + /// Namespace for core RPC commands. Every RPC commands gets defined here (including its name(s), /// access, and data type), and added to `core_rpc_types` list at the bottom of the file. - namespace cryptonote::rpc { using version_t = std::pair; From 0c54d036b1555b1463e10ac09ebc102b994104dc Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 23 Apr 2025 11:55:22 +0530 Subject: [PATCH 096/182] RPC: GET_MASTER_NODE_REGISTRATION_CMD_RAW, GET_MASTER_NODE_REGISTRATION_CMD are updated --- src/daemon/rpc_command_executor.cpp | 15 ++---- src/rpc/core_rpc_server.cpp | 59 ++++++++++++++-------- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 7 +++ src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 36 ++++++------- src/rpc/core_rpc_server_commands_defs.h | 58 +++++++++------------ 7 files changed, 97 insertions(+), 83 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index f690df6128a..87de6c1eaf7 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2494,18 +2494,13 @@ bool rpc_command_executor::prepare_registration(bool force_registration) scoped_log_cats.reset(); { - GET_MASTER_NODE_REGISTRATION_CMD_RAW::request req{}; - GET_MASTER_NODE_REGISTRATION_CMD_RAW::response res{}; - - req.args = args; - req.make_friendly = true; - req.staking_requirement = staking_requirement; - - if (!invoke(std::move(req), res, "Failed to validate registration arguments; " - "check the addresses and registration parameters and that the Daemon is running with the '--master-node' flag")) + auto maybe_registration = try_running([this, staking_requirement, &args] { return invoke(json{{"staking_requirement", staking_requirement}, {"args", args}, {"make_friendly", true}}); }, "Failed to validate registration arguments; check the addresses and registration parameters and that the Daemon is running with the '--master-node' flag"); + if (!maybe_registration) return false; - tools::success_msg_writer() << res.registration_cmd; + auto& registration = *maybe_registration; + + tools::success_msg_writer() << registration["registration_cmd"]; } return true; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 1d4843d2922..afa6358e201 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2654,28 +2654,36 @@ namespace cryptonote::rpc { flush_cache.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_NODE_REGISTRATION_CMD_RAW::response core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW& get_master_node_registration_cmd_raw, rpc_context context) { - GET_MASTER_NODE_REGISTRATION_CMD_RAW::response res{}; - PERF_TIMER(on_get_master_node_registration_cmd_raw); if (!m_core.master_node()) throw rpc_error{ERROR_WRONG_PARAM, "Daemon has not been started in master node mode, please relaunch with --master-node flag."}; auto hf_version = get_network_version(nettype(), m_core.get_current_blockchain_height()); - if (!master_nodes::make_registration_cmd(m_core.get_nettype(), hf_version, req.staking_requirement, req.args, m_core.get_master_keys(), res.registration_cmd, req.make_friendly)) + std::string registration_cmd; + if (!master_nodes::make_registration_cmd(m_core.get_nettype(), + hf_version, + get_master_node_registration_cmd_raw.request.staking_requirement, + get_master_node_registration_cmd_raw.request.args, + m_core.get_master_keys(), + registration_cmd, + get_master_node_registration_cmd_raw.request.make_friendly)) throw rpc_error{ERROR_INTERNAL, "Failed to make registration command"}; - res.status = STATUS_OK; - return res; + get_master_node_registration_cmd_raw.response["registration_cmd"] = registration_cmd; + get_master_node_registration_cmd_raw.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ - GET_MASTER_NODE_REGISTRATION_CMD::response core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD& get_master_node_registration_cmd, rpc_context context) { - GET_MASTER_NODE_REGISTRATION_CMD::response res{}; PERF_TIMER(on_get_master_node_registration_cmd); + + if (!m_core.master_node()) + throw rpc_error{ERROR_WRONG_PARAM, "Daemon has not been started in master node mode, please relaunch with --master-node flag."}; std::vector args; @@ -2684,29 +2692,40 @@ namespace cryptonote::rpc { { uint64_t portions_cut; - if (!master_nodes::get_portions_from_percent_str(req.operator_cut, portions_cut)) + if (!master_nodes::get_portions_from_percent_str(get_master_node_registration_cmd.request.operator_cut, portions_cut)) { - res.status = "Invalid value: " + req.operator_cut + ". Should be between [0-100]"; - MERROR(res.status); - return res; + get_master_node_registration_cmd.response["status"] = "Invalid value: " + get_master_node_registration_cmd.request.operator_cut + ". Should be between [0-100]"; + MERROR(get_master_node_registration_cmd.response["status"]); + return; } args.push_back(std::to_string(portions_cut)); } - for (const auto& [address, amount] : req.contributions) + auto& addresses = get_master_node_registration_cmd.request.contributor_addresses; + auto& amounts = get_master_node_registration_cmd.request.contributor_amounts; + + if (addresses.size() != amounts.size()) { + throw std::runtime_error("Mismatch in sizes of addresses and amounts"); + } + + for (size_t i = 0; i < addresses.size(); ++i) { { - uint64_t num_portions = master_nodes::get_portions_to_make_amount(staking_requirement, amount); - args.push_back(address); + uint64_t num_portions = master_nodes::get_portions_to_make_amount(staking_requirement, amounts[i]); + args.push_back(addresses[i]); args.push_back(std::to_string(num_portions)); } - GET_MASTER_NODE_REGISTRATION_CMD_RAW::request req_old{}; + GET_MASTER_NODE_REGISTRATION_CMD_RAW req_old{}; + + req_old.request.staking_requirement = req.staking_requirement; + req_old.request.args = std::move(args); + req_old.request.make_friendly = false; - req_old.staking_requirement = req.staking_requirement; - req_old.args = std::move(args); - req_old.make_friendly = false; - return invoke(std::move(req_old), context); + invoke(req_old, context); + res.status = req_old.response["status"]; + res.registration_cmd = req_old.response["registration_cmd"]; + return res; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index cc4157b506c..b80c315e6a2 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -259,6 +259,8 @@ namespace cryptonote::rpc { void invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context); void invoke(GET_BLOCK& get_block, rpc_context context); + void invoke(GET_MASTER_NODE_REGISTRATION_CMD& get_master_node_registration_cmd, rpc_context context); + void invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW& get_master_node_registration_cmd_raw, rpc_context context); void invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context); void invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context); void invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context); @@ -277,8 +279,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - GET_MASTER_NODE_REGISTRATION_CMD_RAW::response invoke(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request&& req, rpc_context context); - GET_MASTER_NODE_REGISTRATION_CMD::response invoke(GET_MASTER_NODE_REGISTRATION_CMD::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 33ca4c6bec8..13a1b3eb4ad 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -536,4 +536,11 @@ namespace cryptonote::rpc { get_values(in, "unlocked", get_output_histogram.request.unlocked); get_values(in, "recent_cutoff", get_output_histogram.request.recent_cutoff); } + + void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD& cmd, rpc_input in) { + get_values(in, "contributor_addresses", cmd.request.contributor_addresses); + get_values(in, "contributor_amounts", cmd.request.contributor_amounts); + get_values(in, "operator_cut", cmd.request.operator_cut); + get_values(in, "staking_requirement", cmd.request.staking_requirement); + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index e61656c67d1..5390e90ad75 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -42,6 +42,7 @@ namespace cryptonote::rpc { void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 667cb72ee17..70cfeafe9d0 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -980,30 +980,30 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request) - KV_SERIALIZE(args) - KV_SERIALIZE(make_friendly) - KV_SERIALIZE(staking_requirement) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD_RAW::request) +// KV_SERIALIZE(args) +// KV_SERIALIZE(make_friendly) +// KV_SERIALIZE(staking_requirement) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD_RAW::response) - KV_SERIALIZE(status) - KV_SERIALIZE(registration_cmd) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD_RAW::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(registration_cmd) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD::contribution_t) - KV_SERIALIZE(address) - KV_SERIALIZE(amount) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD::contribution_t) +// KV_SERIALIZE(address) +// KV_SERIALIZE(amount) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD::request) - KV_SERIALIZE(operator_cut) - KV_SERIALIZE(contributions) - KV_SERIALIZE(staking_requirement) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_NODE_REGISTRATION_CMD::request) +// KV_SERIALIZE(operator_cut) +// KV_SERIALIZE(contributions) +// KV_SERIALIZE(staking_requirement) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_MASTER_KEYS::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index bde9dfaca4d..77479a34a74 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1660,27 +1660,30 @@ namespace cryptonote::rpc { void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q); void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q); - BELDEX_RPC_DOC_INTROSPECT + /// Returns the command that should be run to prepare a master node, includes correct parameters + /// and master node ids formatted ready for cut and paste into daemon console. + /// + /// Inputs: + /// + /// - \p check Instead of running check if the blockchain has already been pruned. + /// - \p args (Developer) The list of arguments used in raw registration, i.e. portions + /// - \p make_friendly Provide information about how to use the command in the result. + /// - \p staking_requirement The staking requirement to become a Master Node the registration command will be generated upon + /// + /// Output values available from a restricted/admin RPC endpoint: + /// + /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - \p registration_cmd The command to execute in the wallet CLI to register the queried daemon as a Master Node. struct GET_MASTER_NODE_REGISTRATION_CMD_RAW : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_node_registration_cmd_raw"); } - struct request - { - std::vector args; // (Developer) The arguments used in raw registration, i.e. portions - bool make_friendly; // Provide information about how to use the command in the result. - uint64_t staking_requirement; // The staking requirement to become a Master Node the registration command will be generated upon - - KV_MAP_SERIALIZABLE - }; - - struct response + struct request_parameters { - std::string status; // Generic RPC error code. "OK" is the success value. - std::string registration_cmd; // The command to execute in the wallet CLI to register the queried daemon as a Master Node. - - KV_MAP_SERIALIZABLE - }; + std::vector args; + bool make_friendly; + uint64_t staking_requirement; + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -1688,24 +1691,13 @@ namespace cryptonote::rpc { { static constexpr auto names() { return NAMES("get_master_node_registration_cmd"); } - struct contribution_t - { - std::string address; // The wallet address for the contributor - uint64_t amount; // The amount that the contributor will reserve in Beldex atomic units towards the staking requirement - - KV_MAP_SERIALIZABLE - }; - - struct request + struct request_parameters { std::string operator_cut; // The percentage of cut per reward the operator receives expressed as a string, i.e. "1.1%" - std::vector contributions; // Array of contributors for this Master Node + std::vector contributor_addresses; + std::vector contributor_amounts; uint64_t staking_requirement; // The staking requirement to become a Master Node the registration command will be generated upon - - KV_MAP_SERIALIZABLE - }; - - using response = GET_MASTER_NODE_REGISTRATION_CMD_RAW::response; + } request; }; /// Get the master public keys of the queried daemon, encoded in hex. All three keys are used @@ -2423,6 +2415,8 @@ namespace cryptonote::rpc { SUBMIT_TRANSACTION, GET_BLOCK_HASH, GET_PEER_LIST, + GET_MASTER_NODE_REGISTRATION_CMD_RAW, + GET_MASTER_NODE_REGISTRATION_CMD, SET_LOG_LEVEL, SET_LOG_CATEGORIES, BANNED, @@ -2457,8 +2451,6 @@ namespace cryptonote::rpc { RELAY_TX, GET_OUTPUT_DISTRIBUTION, GET_QUORUM_STATE, - GET_MASTER_NODE_REGISTRATION_CMD_RAW, - GET_MASTER_NODE_REGISTRATION_CMD, GET_MASTER_KEYS, GET_MASTER_PRIVKEYS, GET_STAKING_REQUIREMENT, From 9a3cf6296ea1f0e56efbfc3362169128c6c7447c Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Wed, 23 Apr 2025 12:52:11 +0530 Subject: [PATCH 097/182] RPC: BNS_OWNERS_TO_NAMES updated --- src/rpc/core_rpc_server.cpp | 22 ++-- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 124 ++++++++++++++-------- src/rpc/core_rpc_server_commands_defs.h | 45 ++++---- 4 files changed, 121 insertions(+), 72 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index afa6358e201..423f70d60b6 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3332,20 +3332,18 @@ namespace cryptonote::rpc { } //------------------------------------------------------------------------------------------------------------------------------ - BNS_OWNERS_TO_NAMES::response core_rpc_server::invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context) + void core_rpc_server::invoke(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_context context) { - BNS_OWNERS_TO_NAMES::response res{}; - if (!context.admin) - check_quantity_limit(req.entries.size(), BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES); + check_quantity_limit(bns_owners_to_names.request.entries.size(), BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES); std::unordered_map owner_to_request_index; std::vector owners; - owners.reserve(req.entries.size()); - for (size_t request_index = 0; request_index < req.entries.size(); request_index++) + owners.reserve(bns_owners_to_names.request.entries.size()); + for (size_t request_index = 0; request_index < bns_owners_to_names.request.entries.size(); request_index++) { - std::string const &owner = req.entries[request_index]; + std::string const &owner = bns_owners_to_names.request.entries[request_index]; bns::generic_owner bns_owner = {}; std::string errmsg; if (!bns::parse_owner_to_generic_owner(m_core.get_nettype(), owner, bns_owner, &errmsg)) @@ -3361,8 +3359,9 @@ namespace cryptonote::rpc { bns::name_system_db &db = m_core.get_blockchain_storage().name_system_db(); std::optional height; - if (!req.include_expired) height = m_core.get_current_blockchain_height(); + if (!bns_owners_to_names.request.include_expired) height = m_core.get_current_blockchain_height(); + std::vector entries; std::vector records = db.get_mappings_by_owners(owners, height); for (auto &record : records) { @@ -3377,7 +3376,7 @@ namespace cryptonote::rpc { (record.backup_owner ? ("BackupOwner=" + record.backup_owner.to_string(nettype()) + " ") : ""s) + " could not be mapped back a index in the request 'entries' array"}; - auto& entry = res.entries.emplace_back(); + auto& entry = entries.emplace_back(); entry.request_index = it->second; entry.name_hash = std::move(record.name_hash); if (record.owner) entry.owner = record.owner.to_string(nettype()); @@ -3391,8 +3390,9 @@ namespace cryptonote::rpc { entry.txid = tools::type_to_hex(record.txid); } - res.status = STATUS_OK; - return res; + bns_owners_to_names.response["entries"] = entries; + bns_owners_to_names.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index b80c315e6a2..88b967cf2a1 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -264,6 +264,7 @@ namespace cryptonote::rpc { void invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context); void invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context); void invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context); + void invoke(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -281,7 +282,6 @@ namespace cryptonote::rpc { // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); - BNS_OWNERS_TO_NAMES::response invoke(BNS_OWNERS_TO_NAMES::request&& req, rpc_context context); BNS_VALUE_DECRYPT::response invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context); #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 70cfeafe9d0..2022ae28736 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -2,6 +2,28 @@ #include #include +namespace nlohmann { + + template + void to_json(nlohmann::json& j, const std::optional& v) + { + if (v.has_value()) + j = *v; + else + j = nullptr; + } + + template + void from_json(const nlohmann::json& j, std::optional& v) + { + if (j.is_null()) + v = std::nullopt; + else + v = j.get(); + } + +} + namespace cryptonote { void to_json(nlohmann::json& j, const checkpoint_t& c) { @@ -135,6 +157,7 @@ void to_json(nlohmann::json& j, const GET_OUTPUT_HISTOGRAM::entry& e) {"recent_instances", e.recent_instances}, }; } + void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& e) { j.at("amount").get_to(e.amount); @@ -143,6 +166,23 @@ void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& e) j.at("recent_instances").get_to(e.recent_instances); }; +void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) +{ + j = nlohmann::json{ + {"request_index", r.request_index}, + {"name_hash", r.name_hash}, + {"owner", r.owner}, + {"backup_owner", r.backup_owner}, + {"encrypted_bchat_value", r.encrypted_bchat_value}, + {"encrypted_wallet_value", r.encrypted_wallet_value}, + {"encrypted_belnet_value", r.encrypted_belnet_value}, + {"encrypted_eth_addr_value", r.encrypted_eth_addr_value}, + {"update_height", r.update_height}, + {"expiration_height", r.expiration_height}, + {"txid", r.txid}, + }; +} + KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() @@ -1275,31 +1315,31 @@ KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_NAMES_TO_OWNERS::request) - KV_SERIALIZE(entries) - KV_SERIALIZE(include_expired) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_NAMES_TO_OWNERS::request) +// KV_SERIALIZE(entries) +// KV_SERIALIZE(include_expired) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_NAMES_TO_OWNERS::response_entry) - KV_SERIALIZE(entry_index) - KV_SERIALIZE(name_hash) - KV_SERIALIZE(owner) - KV_SERIALIZE(backup_owner) - KV_SERIALIZE(encrypted_bchat_value) - KV_SERIALIZE(encrypted_wallet_value) - KV_SERIALIZE(encrypted_belnet_value) - KV_SERIALIZE(encrypted_eth_addr_value) - KV_SERIALIZE(update_height) - KV_SERIALIZE(expiration_height) - KV_SERIALIZE(txid) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_NAMES_TO_OWNERS::response_entry) +// KV_SERIALIZE(entry_index) +// KV_SERIALIZE(name_hash) +// KV_SERIALIZE(owner) +// KV_SERIALIZE(backup_owner) +// KV_SERIALIZE(encrypted_bchat_value) +// KV_SERIALIZE(encrypted_wallet_value) +// KV_SERIALIZE(encrypted_belnet_value) +// KV_SERIALIZE(encrypted_eth_addr_value) +// KV_SERIALIZE(update_height) +// KV_SERIALIZE(expiration_height) +// KV_SERIALIZE(txid) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_NAMES_TO_OWNERS::response) - KV_SERIALIZE(entries) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_NAMES_TO_OWNERS::response) +// KV_SERIALIZE(entries) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(BNS_LOOKUP::request) KV_SERIALIZE(name) @@ -1319,31 +1359,31 @@ KV_SERIALIZE_MAP_CODE_BEGIN(BNS_LOOKUP::response) KV_SERIALIZE(status) KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::request) - KV_SERIALIZE(entries) - KV_SERIALIZE(include_expired) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::request) +// KV_SERIALIZE(entries) +// KV_SERIALIZE(include_expired) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::response_entry) - KV_SERIALIZE(request_index) - KV_SERIALIZE(name_hash) - KV_SERIALIZE(owner) - KV_SERIALIZE(backup_owner) - KV_SERIALIZE(encrypted_bchat_value) - KV_SERIALIZE(encrypted_wallet_value) - KV_SERIALIZE(encrypted_belnet_value) - KV_SERIALIZE(update_height) - KV_SERIALIZE(expiration_height) - KV_SERIALIZE(txid) - KV_SERIALIZE(encrypted_eth_addr_value) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::response_entry) +// KV_SERIALIZE(request_index) +// KV_SERIALIZE(name_hash) +// KV_SERIALIZE(owner) +// KV_SERIALIZE(backup_owner) +// KV_SERIALIZE(encrypted_bchat_value) +// KV_SERIALIZE(encrypted_wallet_value) +// KV_SERIALIZE(encrypted_belnet_value) +// KV_SERIALIZE(update_height) +// KV_SERIALIZE(expiration_height) +// KV_SERIALIZE(txid) +// KV_SERIALIZE(encrypted_eth_addr_value) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::response) - KV_SERIALIZE(entries) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::response) +// KV_SERIALIZE(entries) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(BNS_RESOLVE::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 77479a34a74..66bab558fa7 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -231,7 +231,6 @@ namespace cryptonote::rpc { /// /// - \p status -- Generic RPC error code. "OK" is the success value. /// - \p untrusted -- If the result is obtained using bootstrap mode then this will be set to - /// true, otherwise will be omitted. /// - \p missed_tx -- set of transaction hashes that were not found. If all were found then this /// field is omitted. There is no particular ordering of hashes in this list. /// - \p txs -- list of transaction details; each element is a dict containing: @@ -1435,7 +1434,6 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT /// Display alternative chains seen by the node. /// /// Inputs: None @@ -2264,21 +2262,40 @@ namespace cryptonote::rpc { }; }; - BELDEX_RPC_DOC_INTROSPECT - // Get all the name mappings for the queried owner. The owner can be either a ed25519 public key or Monero style - // public key; by default purchases are owned by the spend public key of the purchasing wallet. + /// Get all the name mappings for the queried owner. The owner can be either a ed25519 public key or Monero style + /// public key; by default purchases are owned by the spend public key of the purchasing wallet. + /// + /// Inputs: + /// + /// - \p entries List of owner's public keys to find all Oxen Name Service entries for. + /// - \p include_expired Optional: if provided and true, include entries in the results even if they are expired + /// + /// Output values available from a public RPC endpoint: + /// + /// - \p status Generic RPC error code. "OK" is the success value. + /// - \p entries List of BNS names. Each element is structured as follows: + /// - \p request_index (Deprecated) The index in request's `entries` array that was resolved via Beldex Name Service. + /// - \p type The category the Beldex Name Service entry belongs to; currently 0 for Bchat, 1 for Wallet, 2 for Belnet and 3 Eth Address . + /// - \p name_hash The hash of the name that the owner purchased via Beldex Name Service in base64 + /// - \p owner The backup public key specified by the owner that purchased the Beldex Name Service entry. + /// - \p backup_owner The backup public key specified by the owner that purchased the Beldex Name Service entry. Omitted if no backup owner. + /// - \p encrypted_bchat_value The encrypted Bchat value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - \p encrypted_wallet_value The encrypted Wallet value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - \p encrypted_belnet_value The encrypted Belnet value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - \p encrypted_eth_addr_value The encrypted Eth Address value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - \p update_height The last height that this Beldex Name Service entry was updated on the Blockchain. + /// - \p expiration_height For records that expire, this will be set to the expiration block height. + /// - \p txid The txid of the mapping's most recent update or purchase. struct BNS_OWNERS_TO_NAMES : PUBLIC { static constexpr auto names() { return NAMES("bns_owners_to_names", "lns_owners_to_names"); } static constexpr size_t MAX_REQUEST_ENTRIES = 256; - struct request + struct request_parameters { std::vector entries; // The owner's public key to find all Beldex Name Service entries for. bool include_expired; // Optional: if provided and true, include entries in the results even if they are expired - - KV_MAP_SERIALIZABLE - }; + } request; struct response_entry { @@ -2293,17 +2310,9 @@ namespace cryptonote::rpc { uint64_t update_height; // The last height that this Beldex Name Service entry was updated on the Blockchain. std::optional expiration_height;// For records that expire, this will be set to the expiration block height. std::string txid; // The txid of the mapping's most recent update or purchase. - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::vector entries; - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE }; }; + void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r); /// Performs a simple BNS lookup of a BLAKE2b-hashed name. This RPC method is meant for simple, /// single-value resolutions that do not care about registration details, etc.; if you need more From eba6690ebb1a0cf2e3d1f59f8c1a060b37134ab0 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Thu, 24 Apr 2025 18:11:36 +0530 Subject: [PATCH 098/182] Rewrite logic to avoid warning about binding a temporary The for loop here is converting them binding to that temporary, producing a compiler warning. Clean it up. --- src/cryptonote_core/cryptonote_tx_utils.h | 6 +++--- src/daemon/rpc_command_executor.cpp | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 6a24c2e8698..2eb10a4b127 100755 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -165,9 +165,9 @@ namespace cryptonote struct tx_destination_entry { - std::string original; - uint64_t amount; //money - account_public_address addr; //destination address + std::string original; // Cached string of the address. Access using address() + uint64_t amount; // Money + account_public_address addr; // Destination Address bool is_subaddress; bool is_integrated; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 87de6c1eaf7..b7718f0b635 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1329,11 +1329,13 @@ bool rpc_command_executor::alt_chain_info(const std::string &tip, size_t above, tools::fail_msg_writer() << "Failed to get block header info for alt chain"; return true; } - uint64_t t0 = headers["block_headers"].front()["timestamp"], t1 = t0; - for (const block_header_response &block_header: headers["block_headers"]) + uint64_t t0 = std::numeric_limits::max(), + t1 = std::numeric_limits::min(); + for (const auto& block_header: headers["block_headers"]) { - t0 = std::min(t0, block_header.timestamp); - t1 = std::max(t1, block_header.timestamp); + const uint64_t ts = block_header.get(); + t0 = std::min(t0, ts); + t1 = std::max(t1, ts); } const uint64_t dt = t1 - t0; const uint64_t age = std::max(dt, t0 < now ? now - t0 : 0); From a60fcf81863e20d143d424421bb4b9ab58d281aa Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 28 Apr 2025 11:53:28 +0530 Subject: [PATCH 099/182] tx scanning for basic transactions working and moved parser to seperate hpp --- .gitmodules | 3 + CMakeLists.txt | 1 + contrib/format.sh | 50 +++++ external/CMakeLists.txt | 4 +- external/Catch2 | 1 + src/cryptonote_core/blockchain.cpp | 2 +- src/cryptonote_core/master_node_rules.h | 2 +- src/p2p/net_node.inl | 12 +- src/p2p/p2p_protocol_defs.cpp | 1 - src/rpc/CMakeLists.txt | 1 + src/rpc/core_rpc_server.cpp | 11 +- src/rpc/core_rpc_server_binary_commands.h | 18 +- src/rpc/core_rpc_server_command_parser.cpp | 212 +----------------- src/rpc/core_rpc_server_command_parser.h | 2 +- src/rpc/core_rpc_server_commands_defs.h | 14 +- src/rpc/lmq_server.cpp | 242 +++++++++++++++++---- src/rpc/lmq_server.h | 7 + src/rpc/param_parser.hpp | 225 +++++++++++++++++++ src/wallet/api/wallet2_api.h | 2 +- 19 files changed, 519 insertions(+), 291 deletions(-) create mode 100644 contrib/format.sh create mode 160000 external/Catch2 create mode 100644 src/rpc/param_parser.hpp diff --git a/.gitmodules b/.gitmodules index f5ffd522011..088e5dba285 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "external/date"] path = external/date url = https://github.com/HowardHinnant/date.git +[submodule "external/Catch2"] + path = external/Catch2 + url = https://github.com/catchorg/Catch2 diff --git a/CMakeLists.txt b/CMakeLists.txt index f7b665c8c2f..bd48ca8ccad 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,6 +290,7 @@ if(NOT MANUAL_SUBMODULES) check_submodule(external/loki-mq cppzmq) if(BUILD_TESTS) check_submodule(external/googletest) + check_submodule(external/Catch2) endif() check_submodule(external/uWebSockets uSockets) check_submodule(external/ghc-filesystem) diff --git a/contrib/format.sh b/contrib/format.sh new file mode 100644 index 00000000000..7d5513a8853 --- /dev/null +++ b/contrib/format.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +CLANG_FORMAT_DESIRED_VERSION=11 + +TARGET_DIRS=(src/wallet3 src/sqlitedb) + +binary=$(which clang-format-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) +if [ $? -ne 0 ]; then + binary=$(which clang-format-mp-$CLANG_FORMAT_DESIRED_VERSION 2>/dev/null) +fi +if [ $? -ne 0 ]; then + binary=$(which clang-format 2>/dev/null) + if [ $? -ne 0 ]; then + echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." + exit 1 + fi + version=$(clang-format --version) + if [[ ! $version == *"clang-format version $CLANG_FORMAT_DESIRED_VERSION"* ]]; then + echo "Please install clang-format version $CLANG_FORMAT_DESIRED_VERSION and re-run this script." + exit 1 + fi +fi + +cd "$(dirname $0)/../" +if [ "$1" = "verify" ] ; then + for d in ${TARGET_DIRS[@]}; do + if [ $($binary --output-replacements-xml $(find $d | grep -E '\.([hc](pp)?|mm?)$' | grep -v '\#') | grep '' | wc -l) -ne 0 ] ; then + exit 1 + fi + done +else + for d in ${TARGET_DIRS[@]}; do + echo "Formatting $d" + $binary -i $(find $d | grep -E '\.([hc](pp)?|mm)$' | grep -v '\#') &> /dev/null + done +fi + +swift_format=$(which swiftformat 2>/dev/null) +if [ $? -eq 0 ]; then + if [ "$1" = "verify" ] ; then + for f in $(find daemon | grep -E '\.swift$' | grep -v '\#') ; do + if [ $($swift_format --quiet --dryrun < "$f" | diff "$f" - | wc -l) -ne 0 ] ; then + exit 1 + fi + done + else + $swift_format --quiet $(find daemon | grep -E '\.swift$' | grep -v '\#') + fi + +fi \ No newline at end of file diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index e9205cf8f21..83c90e843ab 100755 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -144,4 +144,6 @@ if(cpr_cmake_head MATCHES "project\\(cpr VERSION ([0-9]+)\.([0-9]+)\.([0-9]+) LA target_include_directories(cpr PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/cpr_generated_includes") else() message(FATAL_ERROR "Could not identify cpr submodule version!") -endif() \ No newline at end of file +endif() + +add_subdirectory(Catch2) \ No newline at end of file diff --git a/external/Catch2 b/external/Catch2 new file mode 160000 index 00000000000..4ff57aba429 --- /dev/null +++ b/external/Catch2 @@ -0,0 +1 @@ +Subproject commit 4ff57aba42938478f8b626c0862409f6eb7548f0 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 65d8b32977c..9fab0c1eafa 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -4484,7 +4484,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& try { m_master_node_list.block_add(bl, only_txs, checkpoint); } catch (const std::exception& e) { - MGINFO_RED("Failed to add block to Service Node List: " << e.what()); + MGINFO_RED("Failed to add block to Master Node List: " << e.what()); bvc.m_verifivation_failed = true; return false; } diff --git a/src/cryptonote_core/master_node_rules.h b/src/cryptonote_core/master_node_rules.h index efde8e2ca74..261efa915aa 100755 --- a/src/cryptonote_core/master_node_rules.h +++ b/src/cryptonote_core/master_node_rules.h @@ -279,7 +279,7 @@ namespace master_nodes { //If a nodes timestamp varies by this amount of seconds they will be considered out of sync inline constexpr uint8_t THRESHOLD_SECONDS_OUT_OF_SYNC = 30; - //If the below percentage of service nodes are out of sync we will consider our clock out of sync + //If the below percentage of master nodes are out of sync we will consider our clock out of sync inline constexpr uint8_t MAXIMUM_EXTERNAL_OUT_OF_SYNC = 80; static_assert(cryptonote::old::STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution"); diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index f6254badf67..5f88879c55e 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1223,6 +1223,13 @@ namespace nodetool auto ago = std::chrono::system_clock::now() - std::chrono::system_clock::from_time_t(it->second); return ago <= cryptonote::p2p::FAILED_ADDR_FORGET; } + + //----------------------------------------------------------------------------------- + static std::string peerid_to_string(peerid_type peer_id) + { + return fmt::format("{:016x}", peer_id); + } + //----------------------------------------------------------------------------------- template bool node_server::make_new_connection_from_anchor_peerlist(const std::vector& anchor_peerlist) @@ -1258,11 +1265,6 @@ namespace nodetool return false; } - static std::string peerid_to_string(peerid_type peer_id) - { - return fmt::format("{:016x}", peer_id); - } - //----------------------------------------------------------------------------------- template bool node_server::make_new_connection_from_peerlist(network_zone& zone, bool use_white_list) diff --git a/src/p2p/p2p_protocol_defs.cpp b/src/p2p/p2p_protocol_defs.cpp index 22d0355fe49..a5e0ba6de77 100644 --- a/src/p2p/p2p_protocol_defs.cpp +++ b/src/p2p/p2p_protocol_defs.cpp @@ -1,5 +1,4 @@ #include "p2p_protocol_defs.h" -#include "epee/misc_language.h" #include "epee/string_tools.h" #include "epee/time_helper.h" #include "net/tor_address.h" // needed for serialization diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 7f4775e7217..e9f75d35014 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -84,6 +84,7 @@ target_link_libraries(rpc target_link_libraries(daemon_rpc_server PRIVATE + oxenc rpc_server_base rpc Boost::thread diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 423f70d60b6..a1938bcc3c6 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -80,7 +80,6 @@ namespace cryptonote::rpc { namespace { oxenc::bt_value json_to_bt(json&& j) { - using namespace oxenmq; if (j.is_object()) { oxenc::bt_dict res; for (auto& [k, v] : j.items()) { @@ -125,7 +124,7 @@ namespace cryptonote::rpc { if (auto body = request.body_view()) { if (body->front() == 'd') { // Looks like a bt dict rpc.set_bt(); - parse_request(rpc, oxenmq::bt_dict_consumer{*body}); + parse_request(rpc, oxenc::bt_dict_consumer{*body}); } else parse_request(rpc, json::parse(*body)); @@ -1827,7 +1826,7 @@ namespace cryptonote::rpc { tx_hashes.reserve(blk.tx_hashes.size()); std::transform(blk.tx_hashes.begin(), blk.tx_hashes.end(), tx_hashes.begin(), [](const auto& x) { return tools::type_to_hex(x); }); get_block.response["tx_hashes"] = tx_hashes; - get_block.response["blob"] = oxenmq::to_hex(t_serializable_object_to_blob(blk)); + get_block.response["blob"] = oxenc::to_hex(t_serializable_object_to_blob(blk)); get_block.response["json"] = obj_to_json_str(blk); get_block.response["status"] = STATUS_OK; return; @@ -1881,7 +1880,7 @@ namespace cryptonote::rpc { */ const auto& blockchain = m_core.get_blockchain_storage(); auto version = - hfinfo.request.version > 0 ? hfinfo.request.version : + hfinfo.request.version > 0 ? static_cast(hfinfo.request.version) : hfinfo.request.height > 0 ? blockchain.get_network_version(hfinfo.request.height) : blockchain.get_network_version(); hfinfo.response["version"] = version; @@ -2141,7 +2140,7 @@ namespace cryptonote::rpc { auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(get_base_fee_estimate.request.grace_blocks); get_base_fee_estimate.response["fee_per_byte"] = fees.first; get_base_fee_estimate.response["fee_per_output"] = fees.second; - get_base_fee_estimate.response["flash_fee_fixed"] = FLASH_BURN_FIXED; + get_base_fee_estimate.response["flash_fee_fixed"] = beldex::FLASH_BURN_FIXED; constexpr auto flash_percent = beldex::FLASH_MINER_TX_FEE_PERCENT + beldex::FLASH_BURN_TX_FEE_PERCENT_OLD; get_base_fee_estimate.response["flash_fee_per_byte"] = fees.first * flash_percent / 100; get_base_fee_estimate.response["flash_fee_per_output"] = fees.second * flash_percent / 100; @@ -3056,7 +3055,7 @@ namespace cryptonote::rpc { PERF_TIMER(on_get_staking_requirement); get_staking_requirement.response["height"] = get_staking_requirement.request.height > 0 ? get_staking_requirement.request.height : m_core.get_current_blockchain_height(); - get_staking_requirement.response["staking_requirement"] = service_nodes::get_staking_requirement(nettype(), get_staking_requirement.request.height); + get_staking_requirement.response["staking_requirement"] = master_nodes::get_staking_requirement(nettype(), get_staking_requirement.request.height); get_staking_requirement.response["status"] = STATUS_OK; return; } diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index 97cc103d18a..89b219fab69 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -87,7 +87,7 @@ namespace cryptonote::rpc { }; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get blocks by height. Binary request. struct GET_BLOCKS_BY_HEIGHT_BIN : PUBLIC, BINARY { @@ -111,7 +111,7 @@ namespace cryptonote::rpc { }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get the known blocks hashes which are not on the main chain. struct GET_ALT_BLOCKS_HASHES_BIN : PUBLIC, BINARY { @@ -128,7 +128,7 @@ namespace cryptonote::rpc { }; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get hashes. Binary request. struct GET_HASHES_BIN : PUBLIC, BINARY { @@ -154,7 +154,7 @@ namespace cryptonote::rpc { }; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get global outputs of transactions. Binary request. struct GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN : PUBLIC, BINARY { @@ -178,7 +178,7 @@ namespace cryptonote::rpc { }; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT struct get_outputs_out { uint64_t amount; // Amount of Loki in TXID. @@ -187,7 +187,7 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get outputs. Binary request. struct GET_OUTPUTS_BIN : PUBLIC, BINARY { @@ -225,7 +225,7 @@ namespace cryptonote::rpc { }; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get hashes from transaction pool. Binary request. struct GET_TRANSACTION_POOL_HASHES_BIN : PUBLIC, BINARY { @@ -251,7 +251,7 @@ namespace cryptonote::rpc { }; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Exactly like GET_OUTPUT_DISTRIBUTION, but does a binary RPC transfer instead of JSON struct GET_OUTPUT_DISTRIBUTION_BIN : PUBLIC, BINARY { @@ -261,7 +261,7 @@ namespace cryptonote::rpc { using response = GET_OUTPUT_DISTRIBUTION::response; }; - OXEN_RPC_DOC_INTROSPECT + BELDEX_RPC_DOC_INTROSPECT // Get information on output blacklist. struct GET_OUTPUT_BLACKLIST_BIN : PUBLIC, BINARY { diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 13a1b3eb4ad..142624fd36b 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -1,214 +1,9 @@ #include "core_rpc_server_command_parser.h" -#include "oxenc/bt_serialize.h" +#include "param_parser.hpp" -#include -#include -#include -#include -#include namespace cryptonote::rpc { using nlohmann::json; - namespace { - - // Checks that key names are given in ascending order - template - void check_ascending_names(std::string_view name1, std::string_view name2, const Ignore&...) { - if (!(name2 > name1)) - throw std::runtime_error{"Internal error: request values must be retrieved in ascending order"}; - } - - // Wrapper around a reference for get_values that is used to indicate that the value is - // required, in which case an exception will be raised if the value is not found. Usage: - // - // int a_optional = 0, b_required; - // get_values(input, - // "a", a_optional, - // "b", required{b_required}, - // // ... - // ); - template - struct required { - T& value; - required(T& ref) : value{ref} {} - }; - template - constexpr bool is_required_wrapper = false; - template - constexpr bool is_required_wrapper> = true; - - template - constexpr bool is_std_optional = false; - template - constexpr bool is_std_optional> = true; - - using oxenc::bt_dict_consumer; - - using json_range = std::pair; - - // Advances the dict consumer to the first element >= the given name. Returns true if found, - // false if it advanced beyond the requested name. This is exactly the same as - // `d.skip_until(name)`, but is here so we can also overload an equivalent function for json - // iteration. - bool skip_until(oxenc::bt_dict_consumer& d, std::string_view name) { - return d.skip_until(name); - } - // Equivalent to the above but for a json object iterator. - bool skip_until(json_range& it_range, std::string_view name) { - auto& [it, end] = it_range; - while (it != end && it.key() < name) - ++it; - return it != end && it.key() == name; - } - - // List types that are expandable; for these we emplace_back for each element of the input - template constexpr bool is_expandable_list = false; - template constexpr bool is_expandable_list> = true; - // Don't currently need these, but they will work fine if uncommented: - // template constexpr bool is_expandable_list> = true; - // template constexpr bool is_expandable_list> = true; - // template constexpr bool is_expandable_list> = true; - - // Fixed size elements: tuples, pairs, and std::array's; we accept list input as long as the - // list length matches exactly. - template constexpr bool is_tuple_like = false; - template constexpr bool is_tuple_like> = true; - template constexpr bool is_tuple_like> = true; - template constexpr bool is_tuple_like> = true; - - template - void load_tuple_values(oxenc::bt_list_consumer&, TupleLike&, std::index_sequence); - - // Consumes the next value from the dict consumer into `val` - template - || std::is_same_v, - int> = 0> - void load_value(BTConsumer& c, T& val) { - if constexpr (std::is_integral_v) - val = c.template consume_integer(); - else if constexpr (std::is_same_v || std::is_same_v) - val = c.consume_string_view(); - else if constexpr (is_binary_parameter) - load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); - else if constexpr (is_expandable_list) { - auto lc = c.consume_list_consumer(); - val.clear(); - while (!lc.is_finished()) - load_value(lc, val.emplace_back()); - } - else if constexpr (is_tuple_like) { - auto lc = c.consume_list_consumer(); - load_tuple_values(lc, val, std::make_index_sequence>{}); - } - else - static_assert(std::is_same_v, "Unsupported load_value type"); - } - - // Copies the next value from the json range into `val`, and advances the iterator. Throws - // on unconvertible values. - template - void load_value(json_range& r, T& val) { - auto& key = r.first.key(); - auto& e = *r.first; - if constexpr (std::is_same_v) { - if (e.is_boolean()) - val = e.get(); - else if (e.is_number_unsigned()) { - // Also accept 0 or 1 for bools (mainly to be compatible with bt-encoding which doesn't - // have a distinct bool type). - auto b = e.get(); - if (b <= 1) - val = b; - else - throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; - } else { - throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; - } - } else if constexpr (std::is_unsigned_v) { - if (!e.is_number_unsigned()) - throw std::domain_error{"Invalid value for '" + key + "': non-negative value required"}; - auto i = e.get(); - if (sizeof(T) < sizeof(uint64_t) && i > std::numeric_limits::max()) - throw std::domain_error{"Invalid value for '" + key + "': value too large"}; - val = i; - } else if constexpr (std::is_integral_v) { - if (!e.is_number_integer()) - throw std::domain_error{"Invalid value for '" + key + "': value is not an integer"}; - auto i = e.get(); - if (sizeof(T) < sizeof(int64_t)) { - if (i < std::numeric_limits::lowest()) - throw std::domain_error{"Invalid value for '" + key + "': negative value magnitude is too large"}; - else if (i > std::numeric_limits::max()) - throw std::domain_error{"Invalid value for '" + key + "': value is too large"}; - } - val = i; - } else if constexpr (std::is_same_v || std::is_same_v) { - val = e.get(); - } else if constexpr (is_binary_parameter || is_expandable_list || is_tuple_like) { - try { e.get_to(val); } - catch (const std::exception& e) { throw std::domain_error{"Invalid values in '" + key + "'"};} - } else { - static_assert(std::is_same_v, "Unsupported load type"); - } - ++r.first; - } - - template - void load_tuple_values(oxenc::bt_list_consumer& c, TupleLike& val, std::index_sequence) { - (load_value(c, std::get(val)), ...); - } - - // Gets the next value from a json object iterator or bt_dict_consumer. Leaves the iterator at - // the next value, i.e. found + 1 if found, or the next greater value if not found. (NB: - // nlohmann::json objects are backed by an *ordered* map and so both nlohmann iterators and - // bt_dict_consumer behave analogously here). - template - void get_next_value(In& in, std::string_view name, T& val) { - if constexpr (std::is_same_v) - ; - else if (skip_until(in, name)) { - if constexpr (is_required_wrapper) - return load_value(in, val.value); - else if constexpr (is_std_optional) - return load_value(in, val.emplace()); - else - return load_value(in, val); - } - if constexpr (is_required_wrapper) - throw std::runtime_error{"Required key '" + std::string{name} + "' not found"}; - } - - /// Accessor for simple, flat value retrieval from a json or bt_dict_consumer. In the later - /// case note that the given bt_dict_consumer will be advanced, so you *must* take care to - /// process keys in order, both for the keys passed in here *and* for use before and after this - /// call. - template - void get_values(Input& in, std::string_view name, T&& val, More&&... more) { - if constexpr (std::is_same_v) { - if (auto* json_in = std::get_if(&in)) { - json_range r{json_in->cbegin(), json_in->cend()}; - get_values(r, name, val, std::forward(more)...); - } else if (auto* dict = std::get_if(&in)) { - get_values(*dict, name, val, std::forward(more)...); - } else { - // A monostate indicates that no parameters field was provided at all - get_values(var::get(in), name, val, std::forward(more)...); - } - } else { - static_assert( - std::is_same_v || - std::is_same_v || - std::is_same_v); - get_next_value(in, name, val); - if constexpr (sizeof...(More) > 0) { - check_ascending_names(name, more...); - get_values(in, std::forward(more)...); - } - } - } - } void parse_request(BNS_RESOLVE& bns, rpc_input in) { get_values(in, @@ -319,7 +114,6 @@ namespace cryptonote::rpc { "memory_pool", get.request.memory_pool, "prune", get.request.prune, "split", get.request.split, - "stake_info", get.request.stake_info, "tx_extra", get.request.tx_extra, "tx_hashes", get.request.tx_hashes); @@ -506,7 +300,7 @@ namespace cryptonote::rpc { } void parse_request(RELAY_TX& relay_tx, rpc_input in){ - get_values(in, "txids", get_block_header_by_hash.request.txids); + get_values(in, "txids", relay_tx.request.txids); } void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { @@ -537,7 +331,7 @@ namespace cryptonote::rpc { get_values(in, "recent_cutoff", get_output_histogram.request.recent_cutoff); } - void parse_request(GET_SERVICE_NODE_REGISTRATION_CMD& cmd, rpc_input in) { + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in) { get_values(in, "contributor_addresses", cmd.request.contributor_addresses); get_values(in, "contributor_amounts", cmd.request.contributor_amounts); get_values(in, "operator_cut", cmd.request.operator_cut); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 5390e90ad75..d58d6bfb2bd 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -22,7 +22,7 @@ namespace cryptonote::rpc { void parse_request(SAVE_BC& save_bc, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); - void parse_request(GET_TRANSACTIONS& hfinfo, rpc_input in); + void parse_request(GET_TRANSACTIONS& get, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); void parse_request(SET_LIMIT& limit, rpc_input in); void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 66bab558fa7..e98212b13bd 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -521,18 +521,6 @@ namespace cryptonote::rpc { bool flash = false; } request; - /*struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. Any other value means that something went wrong. - std::string reason; // Additional information. Currently empty, "Not relayed" if transaction was accepted but not relayed, or some descriptive message of why the tx failed. - bool not_relayed; // Transaction was not relayed (true) or relayed (false). - bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - tx_verification_context tvc; - bool sanity_check_failed; - flash_result flash_status; // 0 for a non-flash tx. For a flash tx: 1 means rejected, 2 means accepted, 3 means timeout. - - KV_MAP_SERIALIZABLE - };*/ }; //----------------------------------------------- @@ -2079,7 +2067,7 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_master_node_blacklisted_key_images"); } }; - /// Query hardcoded/service node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + /// Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. /// /// Inputs: /// diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index 2eebd83bde8..18b381f2372 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -1,7 +1,9 @@ #include "lmq_server.h" +#include "param_parser.hpp" #include "cryptonote_config.h" #include "oxenmq/oxenmq.h" +#include "oxenc/bt.h" #include // FIXME: Rename this to omq_server.{h,cpp} @@ -245,6 +247,10 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog }); } + omq.add_request_command("rpc", "get_blocks", [this](oxenmq::Message& m) { + OnGetBlocks(m); + }); + // Subscription commands // The "subscribe" category is for public subscriptions; i.e. anyone on a public RPC node, or @@ -269,38 +275,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // txblob are binary: in particular, txhash is *not* hex-encoded. // omq.add_request_command("sub", "mempool", [this](oxenmq::Message& m) { - - if (m.data.size() != 1) { - m.send_reply("Invalid subscription request: no subscription type given"); - return; - } - - mempool_sub_type sub_type; - if (m.data[0] == "flash"sv) - sub_type = mempool_sub_type::flash; - else if (m.data[0] == "all"sv) - sub_type = mempool_sub_type::all; - else { - m.send_reply("Invalid mempool subscription type '" + std::string{m.data[0]} + "'"); - return; - } - - { - std::unique_lock lock{subs_mutex_}; - auto expiry = std::chrono::steady_clock::now() + 30min; - auto result = mempool_subs_.emplace(m.conn, mempool_sub{expiry, sub_type}); - if (!result.second) { - result.first->second.expiry = expiry; - if (result.first->second.type == sub_type) { - MTRACE("Renewed mempool subscription request from conn id " << m.conn << " @ " << m.remote); - m.send_reply("ALREADY"); - return; - } - result.first->second.type = sub_type; - } - MDEBUG("New " << (sub_type == mempool_sub_type::flash ? "flash" : "all") << " mempool subscription request from conn " << m.conn << " @ " << m.remote); - m.send_reply("OK"); - } + OnMempoolSubRequest(m); }); // New block subscriptions: [sub.block]. This sends a notification every time a new block is @@ -315,17 +290,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // containing the latest height/hash. (Note that blockhash is the hash in bytes, *not* the hex // encoded block hash). omq.add_request_command("sub", "block", [this](oxenmq::Message& m) { - std::unique_lock lock{subs_mutex_}; - auto expiry = std::chrono::steady_clock::now() + 30min; - auto result = block_subs_.emplace(m.conn, block_sub{expiry}); - if (!result.second) { - result.first->second.expiry = expiry; - MTRACE("Renewed block subscription request from conn id " << m.conn << " @ " << m.remote); - m.send_reply("ALREADY"); - } else { - MDEBUG("New block subscription request from conn " << m.conn << " @ " << m.remote); - m.send_reply("OK"); - } + OnBlockSubRequest(m); }); core_.get_blockchain_storage().hook_block_post_add([this] (const auto& info) { send_block_notifications(info.block); return true; }); @@ -388,5 +353,196 @@ void omq_rpc::send_mempool_notifications(const crypto::hash& id, const transacti }); } +void omq_rpc::OnGetBlocks(oxenmq::Message& m) +{ + if (m.data.size() == 0) + { + m.send_reply("Invalid rpc.get_blocks request: no parameters given."); + return; + } + + if (m.data[0].front() != 'd') + { + m.send_reply("Invalid rpc.get_blocks request: parameters must be bt-encoded."); + return; + } + + uint64_t start_height; + uint64_t max_count; + uint64_t size_limit; + try + { + get_values(m.data[0], + "max_count", required{max_count}, + "size_limit", required{size_limit}, + "start_height", required{start_height}); + } + catch (const std::exception& e) + { + m.send_reply(std::string("Invalid rpc.get_blocks request: ") + e.what()); + } + + size_limit = std::min(size_limit, 2000000); + + auto chain_height = core_.get_current_blockchain_height(); + if (start_height > chain_height) + { + m.send_reply("Invalid rpc.get_blocks request: start_height given is above current chain height."); + return; + } + + size_t message_size = 128; // initial size conservative overhead assumption + + auto end = chain_height; + if (max_count != 0) + end = std::min(start_height + max_count, chain_height); + + using bt_list = oxenc::bt_list; + using bt_dict = oxenc::bt_dict; + + std::vector bt_blocks; + + uint64_t i; + for (i = start_height; i < end; i++) + { + bt_dict block_bt; + + auto hash = core_.get_block_id_by_height(i); + block b; + if (!core_.get_block_by_height(i, b)) + { + m.send_reply("Unknown error fetching blocks."); + return; + } + + block_bt["hash"] = std::string_view{hash.data, sizeof(hash.data)}; + block_bt["height"] = i; + block_bt["timestamp"] = b.timestamp; + + std::vector txs; + core_.get_transactions(b.tx_hashes, txs); + if (txs.size() != b.tx_hashes.size()) + { + m.send_reply("Unknown error fetching transactions."); + return; + } + + bt_list tx_list_bt; + + std::vector indices; + + { + bt_dict tx_bt; + + crypto::hash miner_tx_hash; + cryptonote::get_transaction_hash(b.miner_tx, miner_tx_hash, nullptr); + + if (not core_.get_tx_outputs_gindexs(miner_tx_hash, indices)) + { + m.send_reply("Unknown error fetching output info."); + return; + } + + tx_bt["global_indices"] = bt_list(indices.begin(), indices.end()); + tx_bt["hash"] = std::string{miner_tx_hash.data, sizeof(miner_tx_hash.data)}; + tx_bt["tx"] = tx_to_blob(b.miner_tx); + + tx_list_bt.push_back(std::move(tx_bt)); + } + + for (size_t tx_index = 0; tx_index < txs.size(); tx_index++) + { + bt_dict tx_bt; + + indices.clear(); + + const auto& txhash = b.tx_hashes[tx_index]; + + if (not core_.get_tx_outputs_gindexs(txhash, indices)) + { + m.send_reply("Unknown error fetching output info."); + return; + } + + tx_bt["global_indices"] = bt_list(indices.begin(), indices.end()); + tx_bt["hash"] = std::string{txhash.data, sizeof(txhash.data)}; + tx_bt["tx"] = std::move(txs[tx_index]); + + tx_list_bt.push_back(std::move(tx_bt)); + } + + block_bt["transactions"] = std::move(tx_list_bt); + + auto block_str = oxenc::bt_serialize(block_bt); + size_t sz = block_str.size() + 16; // conservative estimate of 16 bytes wire overhead per block + + if (message_size + sz > size_limit) + { + // i is checked after loop to signal "end of chain", so decrement if we don't add the block + i--; + break; + } + + bt_blocks.push_back(std::move(block_str)); + } + + std::string status = "OK"; + if (i == chain_height) + status = "END"; + else if (bt_blocks.empty()) + status = "TOO BIG"; + + m.send_reply(status, oxenmq::send_option::data_parts(bt_blocks)); +} + +void omq_rpc::OnMempoolSubRequest(oxenmq::Message& m) +{ + if (m.data.size() != 1) { + m.send_reply("Invalid subscription request: no subscription type given"); + return; + } + + mempool_sub_type sub_type; + if (m.data[0] == "blink"sv) + sub_type = mempool_sub_type::blink; + else if (m.data[0] == "all"sv) + sub_type = mempool_sub_type::all; + else { + m.send_reply("Invalid mempool subscription type '" + std::string{m.data[0]} + "'"); + return; + } + + { + std::unique_lock lock{subs_mutex_}; + auto expiry = std::chrono::steady_clock::now() + 30min; + auto result = mempool_subs_.emplace(m.conn, mempool_sub{expiry, sub_type}); + if (!result.second) { + result.first->second.expiry = expiry; + if (result.first->second.type == sub_type) { + MTRACE("Renewed mempool subscription request from conn id " << m.conn << " @ " << m.remote); + m.send_reply("ALREADY"); + return; + } + result.first->second.type = sub_type; + } + MDEBUG("New " << (sub_type == mempool_sub_type::blink ? "blink" : "all") << " mempool subscription request from conn " << m.conn << " @ " << m.remote); + m.send_reply("OK"); + } +} + +void omq_rpc::OnBlockSubRequest(oxenmq::Message& m) +{ + std::unique_lock lock{subs_mutex_}; + auto expiry = std::chrono::steady_clock::now() + 30min; + auto result = block_subs_.emplace(m.conn, block_sub{expiry}); + if (!result.second) { + result.first->second.expiry = expiry; + MTRACE("Renewed block subscription request from conn id " << m.conn << " @ " << m.remote); + m.send_reply("ALREADY"); + } else { + MDEBUG("New block subscription request from conn " << m.conn << " @ " << m.remote); + m.send_reply("OK"); + } +} }} // namespace cryptonote::rpc diff --git a/src/rpc/lmq_server.h b/src/rpc/lmq_server.h index aa3ebfde4e3..030bc564278 100755 --- a/src/rpc/lmq_server.h +++ b/src/rpc/lmq_server.h @@ -68,6 +68,13 @@ class omq_rpc final{ void send_block_notifications(const block& block); void send_mempool_notifications(const crypto::hash& id, const transaction& tx, const std::string& blob, const tx_pool_options& opts); + +private: + void OnGetBlocks(oxenmq::Message& m); + + void OnMempoolSubRequest(oxenmq::Message& m); + + void OnBlockSubRequest(oxenmq::Message& m); }; } // namespace cryptonote::rpc diff --git a/src/rpc/param_parser.hpp b/src/rpc/param_parser.hpp new file mode 100644 index 00000000000..4db565c3335 --- /dev/null +++ b/src/rpc/param_parser.hpp @@ -0,0 +1,225 @@ +#pragma once + +#include "oxenc/bt_serialize.h" + +#include +#include +#include +#include +#include + +namespace cryptonote::rpc { + using nlohmann::json; + using rpc_input = std::variant; + + // Checks that key names are given in ascending order + template + void check_ascending_names(std::string_view name1, std::string_view name2, const Ignore&...) { + if (!(name2 > name1)) + throw std::runtime_error{"Internal error: request values must be retrieved in ascending order"}; + } + + // Wrapper around a reference for get_values that is used to indicate that the value is + // required, in which case an exception will be raised if the value is not found. Usage: + // + // int a_optional = 0, b_required; + // get_values(input, + // "a", a_optional, + // "b", required{b_required}, + // // ... + // ); + template + struct required { + T& value; + required(T& ref) : value{ref} {} + }; + template + constexpr bool is_required_wrapper = false; + template + constexpr bool is_required_wrapper> = true; + + template + constexpr bool is_std_optional = false; + template + constexpr bool is_std_optional> = true; + + using oxenc::bt_dict_consumer; + + using json_range = std::pair; + + // Advances the dict consumer to the first element >= the given name. Returns true if found, + // false if it advanced beyond the requested name. This is exactly the same as + // `d.skip_until(name)`, but is here so we can also overload an equivalent function for json + // iteration. + inline bool skip_until(oxenc::bt_dict_consumer& d, std::string_view name) { + return d.skip_until(name); + } + // Equivalent to the above but for a json object iterator. + inline bool skip_until(json_range& it_range, std::string_view name) { + auto& [it, end] = it_range; + while (it != end && it.key() < name) + ++it; + return it != end && it.key() == name; + } + + // List types that are expandable; for these we emplace_back for each element of the input + template constexpr bool is_expandable_list = false; + template constexpr bool is_expandable_list> = true; +// Don't currently need these, but they will work fine if uncommented: +// template constexpr bool is_expandable_list> = true; +// template constexpr bool is_expandable_list> = true; +// template constexpr bool is_expandable_list> = true; + + // Fixed size elements: tuples, pairs, and std::array's; we accept list input as long as the + // list length matches exactly. + template constexpr bool is_tuple_like = false; + template constexpr bool is_tuple_like> = true; + template constexpr bool is_tuple_like> = true; + template constexpr bool is_tuple_like> = true; + + template + void load_tuple_values(oxenc::bt_list_consumer&, TupleLike&, std::index_sequence); + + // Consumes the next value from the dict consumer into `val` + template + || std::is_same_v, + int> = 0> + void load_value(BTConsumer& c, T& val) { + if constexpr (std::is_integral_v) + val = c.template consume_integer(); + else if constexpr (std::is_same_v || std::is_same_v) + val = c.consume_string_view(); + else if constexpr (is_binary_parameter) + load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + else if constexpr (is_expandable_list) { + auto lc = c.consume_list_consumer(); + val.clear(); + while (!lc.is_finished()) + load_value(lc, val.emplace_back()); + } + else if constexpr (is_tuple_like) { + auto lc = c.consume_list_consumer(); + load_tuple_values(lc, val, std::make_index_sequence>{}); + } + else + static_assert(std::is_same_v, "Unsupported load_value type"); + } + // Copies the next value from the json range into `val`, and advances the iterator. Throws + // on unconvertible values. + template + void load_value(json_range& r, T& val) { + auto& key = r.first.key(); + auto& e = *r.first; + if constexpr (std::is_same_v) { + if (e.is_boolean()) + val = e.get(); + else if (e.is_number_unsigned()) { + // Also accept 0 or 1 for bools (mainly to be compatible with bt-encoding which doesn't + // have a distinct bool type). + auto b = e.get(); + if (b <= 1) + val = b; + else + throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; + } else { + throw std::domain_error{"Invalid value for '" + key + "': expected boolean"}; + } + } else if constexpr (std::is_unsigned_v) { + if (!e.is_number_unsigned()) + throw std::domain_error{"Invalid value for '" + key + "': non-negative value required"}; + auto i = e.get(); + if (sizeof(T) < sizeof(uint64_t) && i > std::numeric_limits::max()) + throw std::domain_error{"Invalid value for '" + key + "': value too large"}; + val = i; + } else if constexpr (std::is_integral_v) { + if (!e.is_number_integer()) + throw std::domain_error{"Invalid value for '" + key + "': value is not an integer"}; + auto i = e.get(); + if (sizeof(T) < sizeof(int64_t)) { + if (i < std::numeric_limits::lowest()) + throw std::domain_error{"Invalid value for '" + key + "': negative value magnitude is too large"}; + else if (i > std::numeric_limits::max()) + throw std::domain_error{"Invalid value for '" + key + "': value is too large"}; + } + val = i; + } else if constexpr (std::is_same_v || std::is_same_v) { + val = e.get(); + } else if constexpr (is_binary_parameter || is_expandable_list || is_tuple_like) { + try { e.get_to(val); } + catch (const std::exception& e) { throw std::domain_error{"Invalid values in '" + key + "'"}; } + } else { + static_assert(std::is_same_v, "Unsupported load type"); + } + ++r.first; + } + + template + void load_tuple_values(oxenc::bt_list_consumer& c, TupleLike& val, std::index_sequence) { + (load_value(c, std::get(val)), ...); + } + + // Gets the next value from a json object iterator or bt_dict_consumer. Leaves the iterator at + // the next value, i.e. found + 1 if found, or the next greater value if not found. (NB: + // nlohmann::json objects are backed by an *ordered* map and so both nlohmann iterators and + // bt_dict_consumer behave analogously here). + template + void get_next_value(In& in, std::string_view name, T& val) { + if constexpr (std::is_same_v) + ; + else if (skip_until(in, name)) { + if constexpr (is_required_wrapper) + return load_value(in, val.value); + else if constexpr (is_std_optional) + return load_value(in, val.emplace()); + else + return load_value(in, val); + } + if constexpr (is_required_wrapper) + throw std::runtime_error{"Required key '" + std::string{name} + "' not found"}; + } + + /// Accessor for simple, flat value retrieval from a json or bt_dict_consumer. In the later + /// case note that the given bt_dict_consumer will be advanced, so you *must* take care to + /// process keys in order, both for the keys passed in here *and* for use before and after this + /// call. + template + void get_values(Input& in, std::string_view name, T&& val, More&&... more) { + if constexpr (std::is_same_v) { + if (auto* json_in = std::get_if(&in)) { + json_range r{json_in->cbegin(), json_in->cend()}; + get_values(r, name, val, std::forward(more)...); + } else if (auto* dict = std::get_if(&in)) { + get_values(*dict, name, val, std::forward(more)...); + } else { + // A monostate indicates that no parameters field was provided at all + get_values(var::get(in), name, val, std::forward(more)...); + } + } + else if constexpr (std::is_same_v) + { + if (in.front() == 'd') + { + oxenc::bt_dict_consumer d{in}; + get_values(d, name, val, std::forward(more)...); + } + else + { + auto json_in = json::parse(in); + json_range r{json_in.cbegin(), json_in.cend()}; + get_values(r, name, val, std::forward(more)...); + } + } else { + static_assert( + std::is_same_v || + std::is_same_v || + std::is_same_v); + get_next_value(in, name, val); + if constexpr (sizeof...(More) > 0) { + check_ascending_names(name, more...); + get_values(in, std::forward(more)...); + } + } + } +} // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index af95cc32369..24a26a2ed84 100755 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -635,7 +635,7 @@ struct Wallet virtual int countBns() = 0; /** - * @brief listCurrentStakes - returns a list of the wallets locked stakes, provides both service node address and the staked amount + * @brief listCurrentStakes - returns a list of the wallets locked stakes, provides both master node address and the staked amount * @return */ virtual std::vector* listCurrentStakes() const = 0; From cafe4598fdaf6f90003c3168689024be123a4c83 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Mon, 28 Apr 2025 12:23:04 +0530 Subject: [PATCH 100/182] refactor: update function name --- src/rpc/lmq_server.cpp | 12 ++++++------ src/rpc/lmq_server.h | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index 18b381f2372..2c77a88648e 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -248,7 +248,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog } omq.add_request_command("rpc", "get_blocks", [this](oxenmq::Message& m) { - OnGetBlocks(m); + on_get_blocks(m); }); // Subscription commands @@ -275,7 +275,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // txblob are binary: in particular, txhash is *not* hex-encoded. // omq.add_request_command("sub", "mempool", [this](oxenmq::Message& m) { - OnMempoolSubRequest(m); + on_mempool_sub_request(m); }); // New block subscriptions: [sub.block]. This sends a notification every time a new block is @@ -290,7 +290,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // containing the latest height/hash. (Note that blockhash is the hash in bytes, *not* the hex // encoded block hash). omq.add_request_command("sub", "block", [this](oxenmq::Message& m) { - OnBlockSubRequest(m); + on_block_sub_request(m); }); core_.get_blockchain_storage().hook_block_post_add([this] (const auto& info) { send_block_notifications(info.block); return true; }); @@ -353,7 +353,7 @@ void omq_rpc::send_mempool_notifications(const crypto::hash& id, const transacti }); } -void omq_rpc::OnGetBlocks(oxenmq::Message& m) +void omq_rpc::on_get_blocks(oxenmq::Message& m) { if (m.data.size() == 0) { @@ -495,7 +495,7 @@ void omq_rpc::OnGetBlocks(oxenmq::Message& m) m.send_reply(status, oxenmq::send_option::data_parts(bt_blocks)); } -void omq_rpc::OnMempoolSubRequest(oxenmq::Message& m) +void omq_rpc::on_mempool_sub_request(oxenmq::Message& m) { if (m.data.size() != 1) { m.send_reply("Invalid subscription request: no subscription type given"); @@ -530,7 +530,7 @@ void omq_rpc::OnMempoolSubRequest(oxenmq::Message& m) } } -void omq_rpc::OnBlockSubRequest(oxenmq::Message& m) +void omq_rpc::on_block_sub_request(oxenmq::Message& m) { std::unique_lock lock{subs_mutex_}; auto expiry = std::chrono::steady_clock::now() + 30min; diff --git a/src/rpc/lmq_server.h b/src/rpc/lmq_server.h index 030bc564278..498f1e5e4c4 100755 --- a/src/rpc/lmq_server.h +++ b/src/rpc/lmq_server.h @@ -70,11 +70,11 @@ class omq_rpc final{ void send_mempool_notifications(const crypto::hash& id, const transaction& tx, const std::string& blob, const tx_pool_options& opts); private: - void OnGetBlocks(oxenmq::Message& m); + void on_get_blocks(oxenmq::Message& m); - void OnMempoolSubRequest(oxenmq::Message& m); + void on_mempool_sub_request(oxenmq::Message& m); - void OnBlockSubRequest(oxenmq::Message& m); + void on_block_sub_request(oxenmq::Message& m); }; } // namespace cryptonote::rpc From b34bbf3c4b1734655b304d2bc454fef4458adba1 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Mon, 28 Apr 2025 18:10:39 +0530 Subject: [PATCH 101/182] refactor wallet2 and simple wallet to use bt-rpc definitions This goes through the remaining code in wallet2 that needed updating to utilise our newly defined json_rpc methods rather than epee serialisation to call rpc methods. --- .../cryptonote_format_utils.cpp | 2 +- src/rpc/core_rpc_server_binary_commands.cpp | 10 + src/rpc/core_rpc_server_binary_commands.h | 3 + src/rpc/http_server_base.cpp | 2 +- src/simplewallet/simplewallet.cpp | 353 +++++----- src/wallet/node_rpc_proxy.cpp | 135 ++-- src/wallet/node_rpc_proxy.h | 11 +- src/wallet/wallet2.cpp | 635 +++++++++--------- src/wallet/wallet2.h | 30 +- src/wallet/wallet_rpc_server.cpp | 99 +-- 10 files changed, 688 insertions(+), 592 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 91a03187bfb..8c031c24ac1 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1149,7 +1149,7 @@ namespace cryptonote return buf; } - + //--------------------------------------------------------------- std::unordered_set tx_verification_failure_codes(const tx_verification_context& tvc) { std::unordered_set reasons; diff --git a/src/rpc/core_rpc_server_binary_commands.cpp b/src/rpc/core_rpc_server_binary_commands.cpp index 55a058e01b4..3c61c5c49b1 100644 --- a/src/rpc/core_rpc_server_binary_commands.cpp +++ b/src/rpc/core_rpc_server_binary_commands.cpp @@ -2,6 +2,16 @@ namespace cryptonote::rpc { + void to_json(nlohmann::json& j, const GET_BLOCKS_BIN::tx_output_indices& toi) + { + j = nlohmann::json{{"indices", toi.indices}}; + } + + void to_json(nlohmann::json& j, const GET_BLOCKS_BIN::block_output_indices& boi) + { + j = nlohmann::json{{"indices", boi.indices}}; + } + KV_SERIALIZE_MAP_CODE_BEGIN(GET_BLOCKS_BIN::request) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index 89b219fab69..a79df8d2655 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -87,6 +87,9 @@ namespace cryptonote::rpc { }; }; + void to_json(nlohmann::json& j, const GET_BLOCKS_BIN::tx_output_indices& toi); + void to_json(nlohmann::json& j, const GET_BLOCKS_BIN::block_output_indices& boi); + BELDEX_RPC_DOC_INTROSPECT // Get blocks by height. Binary request. struct GET_BLOCKS_BY_HEIGHT_BIN : PUBLIC, BINARY diff --git a/src/rpc/http_server_base.cpp b/src/rpc/http_server_base.cpp index d91c3531c2a..18e80026e27 100755 --- a/src/rpc/http_server_base.cpp +++ b/src/rpc/http_server_base.cpp @@ -85,7 +85,7 @@ namespace cryptonote::rpc { if (m_closing) res.writeHeader("Connection", "close"); res.end(nlohmann::json{ {"jsonrpc", "2.0"}, - {"id", std::move(id)}, + {"id", id.getinvoke_http(req, res); - std::string err = interpret_rpc_response(r, res.status); - if (r && err.empty() && res.untrusted) + nlohmann::json res; + try { + res = m_wallet->json_rpc("get_info", {}); + } catch (const std::exception& e) { + fail_msg_writer() << tr("wallet failed to connect to daemon when calling get_info at ") << m_wallet->get_daemon_address() << ": " << e.what() << ".\n"; + } + std::string err = interpret_rpc_response(true, res["status"]); + if (err.empty() && res["untrusted"].get()) message_writer(epee::console_color_yellow, true) << tr("Moreover, a daemon is also less secure when running in bootstrap mode"); } @@ -4628,21 +4631,19 @@ bool simple_wallet::start_mining(const std::vector& args) fail_msg_writer() << tr("wallet is null"); return true; } - rpc::START_MINING::request req{}; - req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); bool ok = true; size_t arg_size = args.size(); + nlohmann::json req_params{ + {"miner_address", m_wallet->get_account().get_public_address_str(m_wallet->nettype())}, + {"threads_count", 1}, + }; if(arg_size >= 1) { uint16_t num = 1; ok = string_tools::get_xtype_from_string(num, args[0]); ok = ok && 1 <= num; - req.threads_count = num; - } - else - { - req.threads_count = 1; + req_params["threads_count"] = num; } if (!ok) @@ -4651,9 +4652,13 @@ bool simple_wallet::start_mining(const std::vector& args) return true; } - rpc::START_MINING::response res{}; - bool r = m_wallet->invoke_http(req, res); - std::string err = interpret_rpc_response(r, res.status); + nlohmann::json res; + try { + res = m_wallet->json_rpc("start_mining", req_params); + } catch (const std::exception& e) { + fail_msg_writer() << tr("wallet failed to communicate with daemon when calling start_mining at ") << m_wallet->get_daemon_address() << ": " << e.what() << ".\n"; + } + std::string err = interpret_rpc_response(true, res["status"].get()); if (err.empty()) success_msg_writer() << tr("Mining started in daemon"); else @@ -4672,9 +4677,13 @@ bool simple_wallet::stop_mining(const std::vector& args) return true; } - rpc::STOP_MINING::response res{}; - bool r = m_wallet->invoke_http({}, res); - std::string err = interpret_rpc_response(r, res.status); + nlohmann::json res; + try { + res = m_wallet->json_rpc("stop_mining", {}); + } catch (const std::exception& e) { + fail_msg_writer() << tr("wallet failed to communicate with daemon when calling stop_mining at ") << m_wallet->get_daemon_address() << ": " << e.what() << ".\n"; + } + std::string err = interpret_rpc_response(true, res["status"].get()); if (err.empty()) success_msg_writer() << tr("Mining stopped in daemon"); else @@ -4740,10 +4749,13 @@ bool simple_wallet::save_bc(const std::vector& args) fail_msg_writer() << tr("wallet is null"); return true; } - rpc::SAVE_BC::request req{}; - rpc::SAVE_BC::response res{}; - bool r = m_wallet->invoke_http(req, res); - std::string err = interpret_rpc_response(r, res.status); + nlohmann::json res; + try { + res = m_wallet->json_rpc("save_bc", {}); + } catch (const std::exception& e) { + fail_msg_writer() << tr("wallet failed to connect to daemon when calling save_bc at ") << m_wallet->get_daemon_address() << ": " << e.what() << ".\n"; + } + std::string err = interpret_rpc_response(true, res["status"]); if (err.empty()) success_msg_writer() << tr("Blockchain saved"); else @@ -6262,53 +6274,53 @@ bool simple_wallet::query_locked_stakes(bool print_result, bool print_key_images using namespace cryptonote; auto response = m_wallet->list_current_stakes(); - for (rpc::GET_MASTER_NODES::response::entry const &node_info : response) + for (const auto& node_info : response) { bool only_once = true; - for (const auto& contributor : node_info.contributors) + for (const auto& contributor : node_info["contributors"]) { std::unordered_set printed_addresses; - for (size_t i = 0; i < contributor.locked_contributions.size(); ++i) + for (size_t i = 0; i < contributor["locked_contributions"].size(); ++i) { - const auto& contribution = contributor.locked_contributions[i]; + const auto& contribution = contributor["locked_contributions"][i]; has_locked_stakes = true; if (!print_result) continue; - auto required = cryptonote::print_money(node_info.staking_requirement); + auto required = cryptonote::print_money(node_info["staking_requirement"]); msg_buf.reserve(512); std::string walletaddress = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); if (only_once) { - if((node_info.contributors.size() - 1)==0) - msg_buf.append(fmt::format(fg(fmt::color::sky_blue) | fmt::emphasis::bold,"Master Node :{}\n",node_info.master_node_pubkey)); + if((node_info["contributors"].size() - 1)==0) + msg_buf.append(fmt::format(fg(fmt::color::sky_blue) | fmt::emphasis::bold,"Master Node :{}\n",node_info["master_node_pubkey"])); else - msg_buf.append(fmt::format(fg(fmt::color::sky_blue) | fmt::emphasis::bold,"Master Node :{} ({} {})\n",node_info.master_node_pubkey,(node_info.contributors.size() - 1),(node_info.contributors.size()-1)==0 ? "": ((node_info.contributors.size()-1) > 1 ? "Contributions" : "Contribution"))); + msg_buf.append(fmt::format(fg(fmt::color::sky_blue) | fmt::emphasis::bold,"Master Node :{} ({} {})\n",node_info["master_node_pubkey"],(node_info["contributors"].size() - 1),(node_info["contributors"].size()-1)==0 ? "": ((node_info["contributors"].size()-1) > 1 ? "Contributions" : "Contribution"))); - if (node_info.requested_unlock_height != master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) + if (node_info["requested_unlock_height"] != master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) { - msg_buf.append(fmt::format("Unlock Height :{}\n",std::to_string(node_info.requested_unlock_height))); + msg_buf.append(fmt::format("Unlock Height :{}\n",std::to_string(node_info["requested_unlock_height"]))); } - if(walletaddress == contributor.address) + if(walletaddress == contributor["address"]) { - msg_buf.append(fmt::format("Operator's Contribution :{} of {} BDX required\n",cryptonote::print_money(contributor.amount),required)); + msg_buf.append(fmt::format("Operator's Contribution :{} of {} BDX required\n",cryptonote::print_money(contributor["amount"]),required)); } else { - msg_buf.append(fmt::format("Operator's Contribution :{} of {} BDX required ({})\n",cryptonote::print_money(contributor.amount),required,contributor.address)); + msg_buf.append(fmt::format("Operator's Contribution :{} of {} BDX required ({})\n",cryptonote::print_money(contributor["amount"]),required,contributor["address"])); } - printed_addresses.insert(contributor.address); + printed_addresses.insert(contributor["address"]); } - if(!only_once && printed_addresses.find(contributor.address) == printed_addresses.end()) + if(!only_once && printed_addresses.find(contributor["address"]) == printed_addresses.end()) { - msg_buf.append(fmt::format(" Total_Contributions:{} ({})\n",cryptonote::print_money(contributor.amount),(walletaddress == contributor.address)? "YOURS" : contributor.address)); - printed_addresses.insert(contributor.address); + msg_buf.append(fmt::format(" Total_Contributions:{} ({})\n",cryptonote::print_money(contributor["amount"]),(walletaddress == contributor["address"])? "YOURS" : contributor["address"])); + printed_addresses.insert(contributor["address"]); } only_once = false; - msg_buf.append(fmt::format(" ● BDX :{}\n",cryptonote::print_money(contribution.amount))); + msg_buf.append(fmt::format(" ● BDX :{}\n",cryptonote::print_money(contribution["amount"]))); if(print_key_images) { - msg_buf.append(fmt::format(" Key Image :{}\n",contribution.key_image)); + msg_buf.append(fmt::format(" Key Image :{}\n",contribution["key_image"])); } msg_buf.append("\n"); } @@ -6329,9 +6341,9 @@ bool simple_wallet::query_locked_stakes(bool print_result, bool print_key_images crypto::key_image key_image; for (const auto& entry : response) { - if (!tools::hex_to_type(entry.key_image, key_image)) + if (!tools::hex_to_type(entry["key_image"], key_image)) { - fail_msg_writer() << tr("Failed to parse hex representation of key image: ") << entry.key_image; + fail_msg_writer() << tr("Failed to parse hex representation of key image: ") << entry["key_image"]; continue; } @@ -6353,15 +6365,15 @@ bool simple_wallet::query_locked_stakes(bool print_result, bool print_key_images msg_buf.append("Blacklisted Stakes\n"); once_only = false; } - msg_buf.append(fmt::format(" Unlock Height : {}\n", std::to_string(entry.unlock_height))); + msg_buf.append(fmt::format(" Unlock Height : {}\n", std::to_string(entry["unlock_height"]))); if(print_key_images) { - msg_buf.append(fmt::format(" Key Image : {}\n", entry.key_image)); + msg_buf.append(fmt::format(" Key Image : {}\n", entry["key_image"])); } - if (entry.amount > 0) + if (entry["amount"] > 0) { // version >= master_nodes::key_image_blacklist_entry::version_1_serialize_amount - msg_buf.append(fmt::format(" Total Locked : {}\n", cryptonote::print_money(entry.amount))); + msg_buf.append(fmt::format(" Total Locked : {}\n", cryptonote::print_money(entry["amount"]))); } msg_buf.append("\n"); @@ -6611,7 +6623,7 @@ bool simple_wallet::bns_renew_mapping(std::vector args) SCOPED_WALLET_UNLOCK(); std::string reason; std::vector ptx_vector; - std::vector response; + nlohmann::json response; try { ptx_vector = m_wallet->bns_create_renewal_tx( @@ -6643,7 +6655,7 @@ bool simple_wallet::bns_renew_mapping(std::vector args) std::optional blocks = bns::expiry_blocks(m_wallet->nettype(), *mapping_years); fmt::print(fmt::format(tr("Renewal {} {} ({} blocks)\n"),(years > 1) ? "years :" : "year :", years, *blocks)); - fmt::print(fmt::format(tr("New expiry : Block {}\n"), (*response[0].expiration_height + *blocks))); + fmt::print(fmt::format(tr("New expiry : Block {}\n"), (response[0]["expiration_height"].get() + *blocks))); std::cout << std::flush; if (!confirm_and_send_tx(dsts, ptx_vector, false /*flash*/)) @@ -6683,7 +6695,7 @@ bool simple_wallet::bns_update_mapping(std::vector args) SCOPED_WALLET_UNLOCK(); std::string reason; std::vector ptx_vector; - std::vector response; + nlohmann::json response; for(std::string check : {owner, backup_owner, value_wallet}) { @@ -6726,7 +6738,7 @@ bool simple_wallet::bns_update_mapping(std::vector args) return true; } - auto &enc_bchat_hex = response[0].encrypted_bchat_value; + auto enc_bchat_hex = response[0]["encrypted_bchat_value"].get(); if (!oxenc::is_hex(enc_bchat_hex) || enc_bchat_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) { LOG_ERROR("invalid BNS data returned from beldexd"); @@ -6734,7 +6746,7 @@ bool simple_wallet::bns_update_mapping(std::vector args) return true; } - auto &enc_wallet_hex = response[0].encrypted_wallet_value; + auto enc_wallet_hex = response[0]["encrypted_wallet_value"].get(); if (!oxenc::is_hex(enc_wallet_hex) || enc_wallet_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) { LOG_ERROR("invalid BNS data returned from beldexd"); @@ -6742,7 +6754,7 @@ bool simple_wallet::bns_update_mapping(std::vector args) return true; } - auto &enc_belnet_hex = response[0].encrypted_belnet_value; + auto enc_belnet_hex = response[0]["encrypted_belnet_value"].get(); if (!oxenc::is_hex(enc_belnet_hex) || enc_belnet_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) { LOG_ERROR("invalid BNS data returned from beldexd"); @@ -6750,7 +6762,7 @@ bool simple_wallet::bns_update_mapping(std::vector args) return true; } - auto &enc_eth_hex = response[0].encrypted_eth_addr_value; + auto enc_eth_hex = response[0]["encrypted_eth_addr_value"].get(); if (!oxenc::is_hex(enc_eth_hex) || enc_eth_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) { LOG_ERROR("invalid BNS data returned from beldexd"); @@ -6876,22 +6888,22 @@ bool simple_wallet::bns_update_mapping(std::vector args) if (owner.size()) { - fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Owner : {}\n"), response[0].owner)); + fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Owner : {}\n"), response[0]["owner"])); fmt::print(fmt::fg(fmt::color::light_green),fmt::format(tr("New Owner : {}\n"), owner)); } else { - fmt::print(fmt::format(tr("Owner : {} (unchanged)\n"), response[0].owner)); + fmt::print(fmt::format(tr("Owner : {} (unchanged)\n"), response[0]["owner"])); } if (backup_owner.size()) { - fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Backup Owner : {}\n"), response[0].backup_owner.value_or(NULL_STR))); + fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Backup Owner : {}\n"), response[0]["backup_owner"])); fmt::print(fmt::fg(fmt::color::light_green),fmt::format(tr("New Backup Owner : {}\n"), backup_owner)); } else { - fmt::print(fmt::format(tr("Backup Owner : {} (unchanged)\n"), response[0].backup_owner.value_or(NULL_STR))); + fmt::print(fmt::format(tr("Backup Owner : {} (unchanged)\n"), response[0]["backup_owner"])); } if (value_bchat.size() && (value_bchat == bchat.to_readable_value(m_wallet->nettype(), bns::mapping_type::bchat))) @@ -7056,14 +7068,18 @@ bool simple_wallet::bns_lookup(std::vector args) return true; } - rpc::BNS_NAMES_TO_OWNERS::request request = {}; + nlohmann::json req_params{ + {"entries", {}} + }; for (auto& name : args) { name = tools::lowercase_ascii_string(std::move(name)); - request.entries.push_back(bns::name_to_base64_hash(name)); + req_params["entries"].emplace_back(nlohmann::json{ + {"name_hash", bns::name_to_base64_hash(name)} + }); } - auto [success, response] = m_wallet->bns_names_to_owners(request); + auto [success, response] = m_wallet->bns_names_to_owners(req_params); if (!success) { fail_msg_writer() << "Connection to daemon failed when requesting BNS owners"; @@ -7073,40 +7089,40 @@ bool simple_wallet::bns_lookup(std::vector args) int last_index = -1; for (auto const &mapping : response) { - auto& enc_bchat_hex = mapping.encrypted_bchat_value; - if (mapping.entry_index >= args.size() || !oxenc::is_hex(enc_bchat_hex) || enc_bchat_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) + auto enc_bchat_hex = mapping["encrypted_bchat_value"]; + if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_bchat_hex) || enc_bchat_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } - auto& enc_wallet_hex = mapping.encrypted_wallet_value; - if (mapping.entry_index >= args.size() || !oxenc::is_hex(enc_wallet_hex) || enc_wallet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) + auto enc_wallet_hex = mapping["encrypted_wallet_value"]; + if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_wallet_hex) || enc_wallet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } - auto& enc_belnet_hex = mapping.encrypted_belnet_value; - if (mapping.entry_index >= args.size() || !oxenc::is_hex(enc_belnet_hex) || enc_belnet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) + auto enc_belnet_hex = mapping["encrypted_belnet_value"]; + if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_belnet_hex) || enc_belnet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } - auto& enc_eth_hex = mapping.encrypted_eth_addr_value; - if (mapping.entry_index >= args.size() || !oxenc::is_hex(enc_eth_hex) || enc_eth_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) + auto enc_eth_hex = mapping["encrypted_eth_addr_value"]; + if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_eth_hex) || enc_eth_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } // Print any skipped (i.e. not registered) results: - for (size_t i = last_index + 1; i < mapping.entry_index; i++) + for (size_t i = last_index + 1; i < mapping["entry_index"]; i++) fail_msg_writer() << args[i] << " not found\n"; - last_index = mapping.entry_index; + last_index = mapping["entry_index"]; - const auto& name = args[mapping.entry_index]; + const auto& name = args[mapping["entry_index"]]; //BCHAT bns::mapping_value value_bchat{}; @@ -7188,13 +7204,13 @@ bool simple_wallet::bns_lookup(std::vector args) if(!enc_eth_hex.empty()) writer << "\n Value ethAddress : " << value_eth.to_readable_value(m_wallet->nettype(), bns::mapping_type::eth_addr); writer - << "\n Owner : " << mapping.owner; + << "\n Owner : " << mapping["owner"]; if (mapping.backup_owner) writer - << "\n Backup owner : " << *mapping.backup_owner; + << "\n Backup owner : " << *mapping["backup_owner"]; writer - << "\n Last updated height : " << mapping.update_height; + << "\n Last updated height : " << mapping["update_height"]; if (mapping.expiration_height) writer - << "\n Expiration height : " << *mapping.expiration_height; + << "\n Expiration height : " << mapping["expiration_height"]; writer << "\n Encrypted bchat value : " << (enc_bchat_hex.empty() ? "(none)" :enc_bchat_hex); writer @@ -7209,7 +7225,7 @@ bool simple_wallet::bns_lookup(std::vector args) tools::wallet2::bns_detail detail = { name, - mapping.name_hash}; + mapping["name_hash"]}; m_wallet->set_bns_cache_record(detail); } for (size_t i = last_index + 1; i < args.size(); i++) @@ -7223,8 +7239,9 @@ bool simple_wallet::bns_by_owner(const std::vector& args) if (!try_connect_to_daemon()) return false; - std::vector> rpc_results; - std::vector requests(1); + nlohmann::json req_params{ + {"entries", {}} + }; std::unordered_map cache = m_wallet->get_bns_cache(); @@ -7232,10 +7249,7 @@ bool simple_wallet::bns_by_owner(const std::vector& args) { for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(m_current_subaddress_account); ++index) { - - if (requests.back().entries.size() >= cryptonote::rpc::BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES) - requests.emplace_back(); - requests.back().entries.push_back(m_wallet->get_subaddress_as_str({m_current_subaddress_account, index})); + req_params["entries"].push_back(m_wallet->get_subaddress_as_str({m_current_subaddress_account, index})); } } else @@ -7255,105 +7269,92 @@ bool simple_wallet::bns_by_owner(const std::vector& args) return false; } - if (requests.back().entries.size() >= cryptonote::rpc::BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES) - requests.emplace_back(); - requests.back().entries.push_back(arg); + req_params["entries"].push_back(arg); } } - rpc_results.reserve(requests.size()); - for (auto const &request : requests) + auto [success, result] = m_wallet->bns_owners_to_names(req_params); + if (!success) { - auto [success, result] = m_wallet->bns_owners_to_names(request); - if (!success) - { - fail_msg_writer() << "Connection to daemon failed when requesting BNS names"; - return false; - } - rpc_results.emplace_back(std::move(result)); + fail_msg_writer() << "Connection to daemon failed when requesting BNS names"; + return false; } - auto nettype = m_wallet->nettype(); - for (size_t i = 0; i < rpc_results.size(); i++) - { - auto const &rpc = rpc_results[i]; - for (auto const &entry : rpc) - { - std::string_view name; - std::string value_bchat, value_wallet, value_belnet, value_eth; - if (auto got = cache.find(entry.name_hash); got != cache.end()) - { - name = got->second.name; - //BCHAT - { - bns::mapping_value mv; - const auto type = bns::mapping_type::bchat; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_bchat_value), &mv) - && mv.decrypt(name, type)) - value_bchat = mv.to_readable_value(nettype, type); - } - //WALLET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::wallet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_wallet_value), &mv) - && mv.decrypt(name, type)) - value_wallet = mv.to_readable_value(nettype,type); - } - //BELNET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::belnet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_belnet_value), &mv) - && mv.decrypt(name, type)) - value_belnet = mv.to_readable_value(nettype, type); - } - - //ETH_ADDRESS - { - bns::mapping_value mv; - const auto type = bns::mapping_type::eth_addr; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_eth_addr_value), &mv) - && mv.decrypt(name, type)) - value_eth = mv.to_readable_value(nettype, type); - } - } - - auto writer = tools::msg_writer(); - writer - << fmt::format(fg(fmt::color::sky_blue) , " Name (hashed) : {}", entry.name_hash); - if (!name.empty()) writer - << fmt::format(fg(fmt::color::sky_blue) , "\n Name : {}", name); - if (!value_bchat.empty()) writer - << "\n Value bchat : " << value_bchat; - if (!value_wallet.empty()) writer - << "\n Value wallet : " << value_wallet; - if (!value_belnet.empty()) writer - << "\n Value belnet : " << value_belnet; - if (!value_eth.empty()) writer - << "\n Value ethAddress : " << value_eth; - writer - << "\n Owner : " << entry.owner; - if (entry.backup_owner) writer - << "\n Backup owner : " << *entry.backup_owner; - writer - << "\n Last updated height : " << entry.update_height; - if (entry.expiration_height) writer - << "\n Expiration height : " << *entry.expiration_height; - writer - << "\n Encrypted bchat value : " << (entry.encrypted_bchat_value.empty() ? "(none)" : entry.encrypted_bchat_value); - writer - << "\n Encrypted wallet value : " << (entry.encrypted_wallet_value.empty() ? "(none)" : entry.encrypted_wallet_value); - writer - << "\n Encrypted belnet value : " << (entry.encrypted_belnet_value.empty() ? "(none)" : entry.encrypted_belnet_value); - writer - << "\n Encrypted Eth value : " << (entry.encrypted_eth_addr_value.empty() ? "(none)" : entry.encrypted_eth_addr_value); - writer - << "\n"; - } - } - return true; + for (auto const &entry : result["entries"]) + { + std::string_view name; + std::string value_bchat, value_wallet, value_belnet, value_eth; + if (auto got = cache.find(entry["name_hash"]); got != cache.end()) + { + name = got->second.name; + //BCHAT + { + bns::mapping_value mv; + const auto type = bns::mapping_type::bchat; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_bchat_value"].get()), &mv) + && mv.decrypt(name, type)) + value_bchat = mv.to_readable_value(nettype, type); + } + //WALLET + { + bns::mapping_value mv; + const auto type = bns::mapping_type::wallet; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_wallet_value"].get()), &mv) + && mv.decrypt(name, type)) + value_wallet = mv.to_readable_value(nettype,type); + } + //BELNET + { + bns::mapping_value mv; + const auto type = bns::mapping_type::belnet; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_belnet_value"].get()), &mv) + && mv.decrypt(name, type)) + value_belnet = mv.to_readable_value(nettype, type); + } + + //ETH_ADDRESS + { + bns::mapping_value mv; + const auto type = bns::mapping_type::eth_addr; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_eth_addr_value"]), &mv) + && mv.decrypt(name, type)) + value_eth = mv.to_readable_value(nettype, type); + } + } + auto writer = tools::msg_writer(); + writer + << fmt::format(fg(fmt::color::sky_blue) , " Name (hashed) : {}", entry["name_hash"]); + if (!name.empty()) writer + << fmt::format(fg(fmt::color::sky_blue) , "\n Name : {}", name); + if (!value_bchat.empty()) writer + << "\n Value bchat : " << value_bchat; + if (!value_wallet.empty()) writer + << "\n Value wallet : " << value_wallet; + if (!value_belnet.empty()) writer + << "\n Value belnet : " << value_belnet; + if (!value_eth.empty()) writer + << "\n Value ethAddress : " << value_eth; + writer + << "\n Owner : " << entry["owner"]; + if (entry["backup_owner"]) writer + << "\n Backup owner : " << *entry["backup_owner"]; + writer + << "\n Last updated height : " << entry["update_height"]; + if (entry["expiration_height"]) writer + << "\n Expiration height : " << *entry["expiration_height"]; + writer + << "\n Encrypted bchat value : " << (entry["encrypted_bchat_value"].empty() ? "(none)" : entry["encrypted_bchat_value"]); + writer + << "\n Encrypted wallet value : " << (entry["encrypted_wallet_value"].empty() ? "(none)" : entry["encrypted_wallet_value"]); + writer + << "\n Encrypted belnet value : " << (entry["encrypted_belnet_value"].empty() ? "(none)" : entry["encrypted_belnet_value"]); + writer + << "\n Encrypted Eth value : " << (entry["encrypted_eth_addr_value"].empty() ? "(none)" : entry["encrypted_eth_addr_value"]); + writer + << "\n"; + } + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::coin_burn(std::vector args) @@ -8534,7 +8535,7 @@ bool simple_wallet::check_tx_proof(const std::vector &args) try { uint64_t received; - bool in_pool; + bool in_pool = false; uint64_t confirmations; if (m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, args.size() == 4 ? args[3] : "", sig_str, received, in_pool, confirmations)) { diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 4289179819f..9c43741ffc2 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -82,8 +82,8 @@ bool NodeRPCProxy::get_rpc_version(rpc::version_t &rpc_version) const if (m_rpc_version == rpc::version_t{0, 0}) { try { - auto res = invoke_json_rpc({}); - m_rpc_version = rpc::make_version(res.version); + auto res = m_http_client.json_rpc("get_version", {}); + m_rpc_version = rpc::make_version(res["version"].get()); } catch (...) { return false; } } rpc_version = m_rpc_version; @@ -105,11 +105,15 @@ bool NodeRPCProxy::get_info() const if (now >= m_get_info_time + 30s) // re-cache every 30 seconds { try { - auto resp_t = invoke_json_rpc({}); - m_height = resp_t.height; - m_target_height = resp_t.target_height; - m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit; - m_immutable_height = resp_t.immutable_height; + auto res = m_http_client.json_rpc("get_info", {}); + m_height = res["height"].get(); + m_target_height = res["target_height"].get(); + auto it_block_weight_limit = res.find("block_weight_limit"); + if (it_block_weight_limit != res.end()) + m_block_weight_limit = res["block_weight_limit"]; + else + m_block_weight_limit = res["block_size_limit"]; + m_immutable_height = res["immutable_height"].get(); m_get_info_time = now; m_height_time = now; } catch (...) { @@ -159,15 +163,14 @@ bool NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_heigh if (m_earliest_height[version] == 0) { - rpc::HARD_FORK_INFO::request req_t{}; - req_t.version = version; + nlohmann::json req_params{ + {"version", version} + }; try { - auto resp_t = invoke_json_rpc(req_t); - - if (!resp_t.earliest_height) + auto res = m_http_client.json_rpc("hard_fork_info", req_params); + if (!res["earliest_height"]) return false; - - m_earliest_height[version] = *resp_t.earliest_height; + m_earliest_height[version] = res["earliest_height"].get(); } catch (...) { return false; } } earliest_height = m_earliest_height[version]; @@ -180,7 +183,8 @@ std::optional NodeRPCProxy::get_hardfork_version() const return std::nullopt; try { - return invoke_json_rpc({}).version; + auto res = m_http_client.json_rpc("hard_fork_info", {}); + return res["version"].get(); }catch (...) {} return std::nullopt; @@ -194,14 +198,15 @@ bool NodeRPCProxy::refresh_dynamic_base_fee_cache(uint64_t grace_blocks) const if (m_dynamic_base_fee_estimate_cached_height != height || m_dynamic_base_fee_estimate_grace_blocks != grace_blocks) { - rpc::GET_BASE_FEE_ESTIMATE::request req_t{}; - req_t.grace_blocks = grace_blocks; + nlohmann::json req_params{ + {"grace_blocks", grace_blocks} + }; try { - auto resp_t = invoke_json_rpc(req_t); - m_dynamic_base_fee_estimate = {resp_t.fee_per_byte, resp_t.fee_per_output}; + auto res = m_http_client.json_rpc("get_base_fee_estimate", req_params); + m_dynamic_base_fee_estimate = {res["fee_per_byte"].get(), res["fee_per_output"].get()}; m_dynamic_base_fee_estimate_cached_height = height; m_dynamic_base_fee_estimate_grace_blocks = grace_blocks; - m_fee_quantization_mask = resp_t.quantization_mask; + m_fee_quantization_mask = res["quantization_mask"].get(); } catch (...) { return false; } } return true; @@ -229,11 +234,22 @@ bool NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) co return true; } -std::pair> NodeRPCProxy::get_master_nodes(std::vector pubkeys) const +std::pair NodeRPCProxy::get_master_nodes(std::vector pubkeys) const { - rpc::GET_MASTER_NODES::request req{}; - req.master_node_pubkeys = std::move(pubkeys); - return get_result_pair(req, [](auto&& res) { return std::move(res.master_node_states); }); + std::pair result; + auto& [success, resolved] = result; + success = false; + nlohmann::json req_params{ + {"master_node_pubkeys", pubkeys} + }; + try { + auto res = m_http_client.json_rpc("get_master_nodes", pubkeys); + resolved = res["master_node_states"]; + } catch (...) { + return result; + } + success = true; + return result; } // Updates the cache of all master nodes; the mutex lock must be already held @@ -242,18 +258,18 @@ bool NodeRPCProxy::update_all_master_nodes_cache(uint64_t height) const { return false; try { - auto res = invoke_json_rpc({}); + auto res = m_http_client.json_rpc("get_master_nodes", {}); m_all_master_nodes_cached_height = height; - m_all_master_nodes = std::move(res.master_node_states); + m_all_master_nodes = std::move(res["master_node_states"]); } catch (...) { return false; } return true; } -std::pair> NodeRPCProxy::get_all_master_nodes() const +std::pair NodeRPCProxy::get_all_master_nodes() const { - std::pair> result; + std::pair result; auto& [success, mns] = result; success = false; @@ -275,9 +291,9 @@ std::pair> // Filtered version of the above that caches the filtered result as long as used on the same // contributor at the same height (which is very common, for example, for wallet balance lookups). -std::pair> NodeRPCProxy::get_contributed_master_nodes(const std::string &contributor) const +std::pair NodeRPCProxy::get_contributed_master_nodes(const std::string &contributor) const { - std::pair> result; + std::pair result; auto& [success, mns] = result; success = false; @@ -295,8 +311,8 @@ std::pair> std::copy_if(m_all_master_nodes.begin(), m_all_master_nodes.end(), std::back_inserter(m_contributed_master_nodes), [&contributor](const auto& mn) { - return std::any_of(mn.contributors.begin(), mn.contributors.end(), - [&contributor](const auto& c) { return contributor == c.address; }); + return std::any_of(mn["contributors"].begin(), mn["contributors"].end(), + [&contributor](const nlohmann::json& c) { return contributor == c["address"].get(); }); } ); m_contributed_master_nodes_cached_height = height; @@ -310,9 +326,9 @@ std::pair> return result; } -std::pair> NodeRPCProxy::get_master_node_blacklisted_key_images() const +std::pair NodeRPCProxy::get_master_node_blacklisted_key_images() const { - std::pair> result; + std::pair result; auto& [success, mns] = result; success = false; @@ -325,9 +341,9 @@ std::pair({}); + auto res = m_http_client.json_rpc("get_master_node_blacklisted_key_images", {}); m_master_node_blacklisted_key_images_cached_height = height; - m_master_node_blacklisted_key_images = std::move(res.blacklist); + m_master_node_blacklisted_key_images = std::move(res["blacklist"]); } catch (...) { return result; } @@ -340,28 +356,57 @@ std::pair> NodeRPCProxy::bns_owners_to_names(cryptonote::rpc::BNS_OWNERS_TO_NAMES::request const &request) const +std::pair NodeRPCProxy::bns_owners_to_names(nlohmann::json const &request) const { - return get_result_pair(request, [](auto&& res) { return std::move(res.entries); }); + std::pair result; + auto& [success, resolved] = result; + success = false; + + if (m_offline || !get_info()) + return result; + + try { + auto res = m_http_client.json_rpc("bns_owners_to_names", request); + resolved = res; + } catch (...) { + return result; + } + success = true; + return result; } -std::pair> NodeRPCProxy::bns_names_to_owners(cryptonote::rpc::BNS_NAMES_TO_OWNERS::request const &request) const +std::pair NodeRPCProxy::bns_names_to_owners(nlohmann::json const& request) const { - return get_result_pair(request, [](auto&& res) { return std::move(res.entries); }); + std::pair result; + auto& [success, resolved] = result; + success = false; + + if (m_offline || !get_info()) + return result; + + try { + auto res = m_http_client.json_rpc("bns_names_to_owners", request); + resolved = res; + } catch (...) { + return result; + } + success = true; + return result; } -std::pair NodeRPCProxy::bns_resolve(cryptonote::rpc::BNS_RESOLVE::request const &request) const + +std::pair NodeRPCProxy::bns_resolve(nlohmann::json const& request) const +{ { - std::pair result; + std::pair result; auto& [success, resolved] = result; success = false; - uint64_t height; - if (m_offline || !get_height(height)) + if (m_offline || !get_info()) return result; { try { - auto res = m_http_client.json_rpc(rpc::BNS_RESOLVE::names().front(), request); + auto res = m_http_client.json_rpc("bns_resolve", request); resolved = res; } catch (...) { return result; diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 08eddd32c0e..f371988f1cf 100755 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -59,11 +59,10 @@ class NodeRPCProxy std::pair get_master_nodes(std::vector pubkeys) const; std::pair get_all_master_nodes() const; std::pair get_contributed_master_nodes(const std::string& contributor) const; - std::pair> get_master_node_blacklisted_key_images() const; - std::pair> bns_owners_to_names(cryptonote::rpc::BNS_OWNERS_TO_NAMES::request const &request) const; - std::pair> bns_names_to_owners(cryptonote::rpc::BNS_NAMES_TO_OWNERS::request const &request) const; - std::pair - bns_resolve(nlohmann::json const &request) const; + std::pair get_master_node_blacklisted_key_images() const; + std::pair bns_owners_to_names(nlohmann::json const &request) const; + std::pair bns_names_to_owners(nlohmann::json const &request) const; + std::pair bns_resolve(nlohmann::json const &request) const; private: bool get_info() const; @@ -117,7 +116,7 @@ class NodeRPCProxy bool m_offline; mutable uint64_t m_master_node_blacklisted_key_images_cached_height; - mutable std::vector m_master_node_blacklisted_key_images; + mutable nlohmann::json m_master_node_blacklisted_key_images; bool update_all_master_nodes_cache(uint64_t height) const; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c27510474ba..201e4ada896 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -212,15 +212,48 @@ namespace { return false; } - std::string get_text_reason(const rpc::SEND_RAW_TX::response &res, cryptonote::transaction const *tx, bool flash) + std::string get_text_reason(const nlohmann::json& res, cryptonote::transaction const *tx, bool flash) { if (flash) { - return res.reason; + return res["reason"].get(); } else { - std::string reason = print_tx_verification_context (res.tvc, tx); - reason += print_vote_verification_context(res.tvc.m_vote_ctx); - return reason; + std::ostringstream os; + + if (res["tvc"]["m_verbose_error"].size()) os << res["tvc"]["m_verbose_error"].get() << "\n"; + + if (res["tvc"]["m_verifivation_failed"].get()) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection + if (res["tvc"]["m_verifivation_impossible"].get()) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain + if (!res["tvc"]["m_should_be_relayed"].get()) os << "TX should NOT be relayed, "; + if (res["tvc"]["m_added_to_pool"].get()) os << "TX added to pool, "; + if (res["tvc"]["m_low_mixin"].get()) os << "Insufficient mixin, "; + if (res["tvc"]["m_double_spend"].get()) os << "Double spend TX, "; + if (res["tvc"]["m_invalid_input"].get()) os << "Invalid inputs, "; + if (res["tvc"]["m_invalid_output"].get()) os << "Invalid outputs, "; + if (res["tvc"]["m_too_few_outputs"].get()) os << "Need at least 2 outputs, "; + if (res["tvc"]["m_too_big"].get()) os << "TX too big, "; + if (res["tvc"]["m_overspend"].get()) os << "Overspend, "; + if (res["tvc"]["m_fee_too_low"].get()) os << "Fee too low, "; + if (res["tvc"]["m_invalid_version"].get()) os << "TX has invalid version, "; + if (res["tvc"]["m_invalid_type"].get()) os << "TX has invalid type, "; + if (res["tvc"]["m_key_image_locked_by_mnode"].get()) os << "Key image is locked by master node, "; + if (res["tvc"]["m_key_image_blacklisted"].get()) os << "Key image is blacklisted on the master node network, "; + + if (res["tvc"]["m_vote_ctx"]["m_validator_index_out_of_bounds"].get()) os << "Validator index out of bounds"; + if (res["tvc"]["m_vote_ctx"]["m_signature_not_valid"].get()) os << "Signature not valid, "; + if (res["tvc"]["m_vote_ctx"]["m_added_to_pool"].get()) os << "Added to pool, "; + if (res["tvc"]["m_vote_ctx"]["m_not_enough_votes"].get()) os << "Not enough votes, "; + if (res["tvc"]["m_vote_ctx"]["m_incorrect_voting_group"].get()) os << "Incorrect voting group specified,"; + if (res["tvc"]["m_vote_ctx"]["m_votes_not_sorted"].get()) os << "Votes are not stored in ascending order"; + + if (tx) + os << "TX Version: " << tx->version << ", Type: " << tx->type; + + std::string buf = os.str(); + if (buf.size() >= 2 && buf[buf.size() - 2] == ',') + buf.resize(buf.size() - 2); + + return buf; } } @@ -854,35 +887,35 @@ void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet) shim->get_tx_pub_key_from_received_outs = [wallet] (const auto& td) { return wallet->get_tx_pub_key_from_received_outs(td); }; } -bool get_pruned_tx(const rpc::GET_TRANSACTIONS::entry &entry, cryptonote::transaction &tx, crypto::hash &tx_hash) +bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, crypto::hash &tx_hash) { cryptonote::blobdata bd; // easy case if we have the whole tx - if (entry.as_hex || (entry.prunable_as_hex && entry.pruned_as_hex)) + if (entry["as_hex"] || (entry["prunable"] && entry["pruned"])) { - if (entry.as_hex) { - CHECK_AND_ASSERT_MES(oxenc::is_hex(*entry.as_hex), false, "Failed to parse tx data"); - bd = oxenc::from_hex(*entry.as_hex); + if (entry["as_hex"]) { + CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["as_hex"]).get(), false, "Failed to parse tx data"); + bd = oxenc::from_hex(*entry.["as_hex"]); } else { - CHECK_AND_ASSERT_MES(oxenc::is_hex(*entry.pruned_as_hex) && oxenc::is_hex(*entry.prunable_as_hex), false, "Failed to parse tx data"); - bd.reserve(oxenc::from_hex_size(entry.pruned_as_hex->size() + entry.prunable_as_hex->size())); - oxenc::from_hex(entry.pruned_as_hex->begin(), entry.pruned_as_hex->end(), std::back_inserter(bd)); - oxenc::from_hex(entry.prunable_as_hex->begin(), entry.prunable_as_hex->end(), std::back_inserter(bd)); + CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()) && oxenc::is_hex(entry["prunable"].get()), false, "Failed to parse tx data"); + bd.reserve(oxenc::from_hex_size(entry["pruned"]->size() + entry["prunable"]->size())); + oxenc::from_hex(entry["pruned"]->begin(), entry["pruned"]->end(), std::back_inserter(bd)); + oxenc::from_hex(entry["prunable"]->begin(), entry["prunable"]->end(), std::back_inserter(bd)); } CHECK_AND_ASSERT_MES(cryptonote::parse_and_validate_tx_from_blob(bd, tx), false, "Invalid tx data"); tx_hash = cryptonote::get_transaction_hash(tx); // if the hash was given, check it matches - CHECK_AND_ASSERT_MES(entry.tx_hash.empty() || tools::type_to_hex(tx_hash) == entry.tx_hash, false, + CHECK_AND_ASSERT_MES(entry["tx_hash"].empty() || tools::type_to_hex(tx_hash) == entry["tx_hash"], false, "Response claims a different hash than the data yields"); return true; } // case of a pruned tx with its prunable data hash - if (entry.pruned_as_hex && entry.prunable_hash) + if (entry["pruned_as_hex"] && entry["prunable_hash"]) { crypto::hash ph; - CHECK_AND_ASSERT_MES(tools::hex_to_type(*entry.prunable_hash, ph), false, "Failed to parse prunable hash"); - CHECK_AND_ASSERT_MES(oxenc::is_hex(*entry.pruned_as_hex), false, "Failed to parse pruned data"); + CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["prunable_hash"].get(), ph), false, "Failed to parse prunable hash"); + CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()), false, "Failed to parse pruned data"); bd = oxenc::from_hex(*entry.pruned_as_hex); CHECK_AND_ASSERT_MES(parse_and_validate_tx_base_from_blob(bd, tx), false, "Invalid base tx data"); // only v2 txes can calculate their txid after pruned @@ -893,7 +926,7 @@ bool get_pruned_tx(const rpc::GET_TRANSACTIONS::entry &entry, cryptonote::transa else { // for v1, we trust the dameon - CHECK_AND_ASSERT_MES(tools::hex_to_type(entry.tx_hash, tx_hash), false, "Failed to parse tx hash"); + CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["tx_hash"].get(), tx_hash), false, "Failed to parse tx hash"); } return true; } @@ -2625,35 +2658,35 @@ bool wallet2::should_skip_block(const cryptonote::block &b, uint64_t height) con //---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector &tx_cache_data, size_t tx_cache_data_offset, std::map, size_t> *output_tracker_cache) { - THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != parsed_block.o_indices.indices.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != parsed_block.o_indices["indices"].size(), error::wallet_internal_error, "block transactions=" + std::to_string(bche.txs.size()) + - " not match with daemon response size=" + std::to_string(parsed_block.o_indices.indices.size())); + " not match with daemon response size=" + std::to_string(parsed_block.o_indices["indices"].size())); //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup if (!should_skip_block(b, height)) { - TIME_MEASURE_START(miner_tx_handle_time); + auto miner_tx_handle_time_start = std::chrono::steady_clock::now(); if (m_refresh_type != RefreshNoCoinbase) - process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.major_version, b.timestamp, true, false, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache); + process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices["indices"][0]["indices"], height, b.major_version, b.timestamp, true, false, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache); ++tx_cache_data_offset; - TIME_MEASURE_FINISH(miner_tx_handle_time); + auto miner_tx_handle_time_duration = std::chrono::steady_clock::now() - miner_tx_handle_time_start; - TIME_MEASURE_START(txs_handle_time); + auto txs_handle_time_start = std::chrono::steady_clock::now(); THROW_WALLET_EXCEPTION_IF(bche.txs.size() != b.tx_hashes.size(), error::wallet_internal_error, "Wrong amount of transactions for block"); THROW_WALLET_EXCEPTION_IF(bche.txs.size() != parsed_block.txes.size(), error::wallet_internal_error, "Wrong amount of transactions for block"); for (size_t idx = 0; idx < b.tx_hashes.size(); ++idx) { - process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.major_version, b.timestamp, false, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache); + process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices["indices"][idx+1]["indices"], height, b.major_version, b.timestamp, false, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache); } - TIME_MEASURE_FINISH(txs_handle_time); + auto txs_handle_time_duration = std::chrono::steady_clock::now() - txs_handle_time_start; m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); if (height > 0 && ((height % 2000) == 0)) LOG_PRINT_L0("Blockchain sync progress: " << bl_id << ", height " << height); - LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); + LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << tools::friendly_duration(miner_tx_handle_time_duration + txs_handle_time_duration) << "(" << tools::friendly_duration(miner_tx_handle_time_duration) << "/" << tools::friendly_duration(txs_handle_time_duration) <<")ms"); }else { if (!(height % 128)) @@ -2703,10 +2736,10 @@ void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::bl error = !cryptonote::parse_and_validate_block_from_blob(blob, bl, bl_id); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list &short_chain_history, std::vector &blocks, std::vector &o_indices, uint64_t ¤t_height) +void wallet2::pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list& short_chain_history, std::vector& blocks, std::vector& o_indices, uint64_t& current_height) { - cryptonote::rpc::GET_BLOCKS_FAST::request req{}; - cryptonote::rpc::GET_BLOCKS_FAST::response res{}; + cryptonote::rpc::GET_BLOCKS_BIN::request req{}; + cryptonote::rpc::GET_BLOCKS_BIN::response res{}; req.block_ids = short_chain_history; MDEBUG("Pulling blocks: start_height " << start_height); @@ -2714,7 +2747,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, req.prune = true; req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; - bool r = invoke_http(req, res); + bool r = invoke_http(req, res); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status == rpc::STATUS_BUSY, error::daemon_busy, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status != rpc::STATUS_OK, error::get_blocks_error, get_rpc_status(res.status)); @@ -2733,12 +2766,12 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, //---------------------------------------------------------------------------------------------------- void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const std::list &short_chain_history, std::vector &hashes) { - cryptonote::rpc::GET_HASHES_FAST::request req{}; - cryptonote::rpc::GET_HASHES_FAST::response res{}; + cryptonote::rpc::GET_HASHES_BIN::request req{}; + cryptonote::rpc::GET_HASHES_BIN::response res{}; req.block_ids = short_chain_history; req.start_height = start_height; - bool r = invoke_http(req, res); + bool r = invoke_http(req, res); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gethashes.bin"); THROW_WALLET_EXCEPTION_IF(res.status == rpc::STATUS_BUSY, error::daemon_busy, "gethashes.bin"); THROW_WALLET_EXCEPTION_IF(res.status != rpc::STATUS_OK, error::get_hashes_error, get_rpc_status(res.status)); @@ -2926,7 +2959,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks } // pull the new blocks - std::vector o_indices; + std::vector o_indices; uint64_t current_height; pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height); THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "Mismatched sizes of blocks and o_indices"); @@ -2946,6 +2979,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks error = true; break; } + // TODO sean -> parsed_blocks o_indices is now a nlohmann::json and o_indices is the struct from the binary parsed_blocks[i].o_indices = std::move(o_indices[i]); } @@ -3055,25 +3089,6 @@ void wallet2::cancel_long_poll() m_long_poll_client.cancel(); } -// Requests transactions transactions; throws a wallet exception on error. -rpc::GET_TRANSACTIONS::response wallet2::request_transactions(std::vector txids_hex) -{ - rpc::GET_TRANSACTIONS::request req{}; - req.txs_hashes = std::move(txids_hex); - req.decode_as_json = false; - req.prune = true; - - rpc::GET_TRANSACTIONS::response res{}; - bool ok = invoke_http(req, res); - - THROW_WALLET_EXCEPTION_IF(!ok, error::no_connection_to_daemon, "Failed to get transaction(s) from daemon: HTTP request failed"); - THROW_WALLET_EXCEPTION_IF(res.status == rpc::STATUS_BUSY, error::daemon_busy, "Failed to get transaction(s) from daemon: daemon busy"); - THROW_WALLET_EXCEPTION_IF(res.status != rpc::STATUS_OK, error::wallet_internal_error, "Failed to get transaction(s) from daemon: daemon returned " + get_rpc_status(res.status)); - THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, "Failed to get transaction(s) from daemon: expected " + std::to_string(req.txs_hashes.size()) + " txes, got " + std::to_string(res.txs.size())); - - return res; -} - template >, int> = 0> static std::vector hashes_to_hex(It begin, It end) { @@ -3085,12 +3100,6 @@ static std::vector hashes_to_hex(It begin, It end) return hexes; } -rpc::GET_TRANSACTIONS::response wallet2::request_transactions(const std::vector& txids) -{ - return request_transactions(hashes_to_hex(txids.begin(), txids.end())); -} - - //---------------------------------------------------------------------------------------------------- std::vector wallet2::get_pool_state(bool refreshed) { @@ -3250,21 +3259,24 @@ std::vector wallet2::get_pool_state(bool refreshed) // get those txes if (!txids.empty()) { - cryptonote::rpc::GET_TRANSACTIONS::response res; + nlohmann::json res; std::vector hex_hashes; hex_hashes.reserve(txids.size()); for (const auto &p: txids) hex_hashes.push_back(tools::type_to_hex(p.first)); try { - res = request_transactions(std::move(hex_hashes)); + nlohmann::json get_transactions_params{ + {"tx_hashes", hex_hashes} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); } catch (const std::exception& e) { LOG_PRINT_L0("Failed to retrieve transactions: " << e.what()); return process_txs; } - for (const auto &tx_entry: res.txs) + for (const auto &tx_entry: res["txs"]) { - if (tx_entry.in_pool) + if (tx_entry["in_pool"]) { cryptonote::transaction tx; cryptonote::blobdata bd; @@ -3276,7 +3288,7 @@ std::vector wallet2::get_pool_state(bool refreshed) [tx_hash](const std::pair &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_txs.push_back({std::move(tx), tx_hash, tx_entry.double_spend_seen, tx_entry.flash}); + process_txs.push_back({std::move(tx), tx_hash, tx_entry["double_spend_seen"], tx_entry["flash"]}); } else { @@ -3745,16 +3757,14 @@ bool wallet2::get_output_blacklist(std::vector &blacklist) } MDEBUG("Daemon is recent enough, requesting output blacklist"); - cryptonote::rpc::GET_OUTPUT_BLACKLIST::response res = {}; - bool r = invoke_http({}, res); - - if (!r) - { + try { + auto res = m_http_client.json_rpc("get_output_blacklist", {}); + blacklist = std::move(res["blacklist"].get>()); + } catch (...) { MWARNING("Failed to request output blacklist: no connection to daemon"); return false; } - blacklist = std::move(res.blacklist); return true; } //---------------------------------------------------------------------------------------------------- @@ -5635,10 +5645,9 @@ bool wallet2::check_connection(rpc::version_t *version, bool *ssl, bool throw_on if (!m_rpc_version) { - cryptonote::rpc::GET_VERSION::response resp_t{}; - bool r = invoke_http({}, resp_t, throw_on_http_error); - if(!r || resp_t.status != rpc::STATUS_OK) return false; - m_rpc_version = resp_t.version; + auto res = m_http_client.json_rpc("get_version", {}); + if(res["status"] != rpc::STATUS_OK) return false; + m_rpc_version = res["version"]; } if (version) *version = rpc::make_version(m_rpc_version); @@ -5848,14 +5857,14 @@ void wallet2::trim_hashchain() if (!m_blockchain.empty() && m_blockchain.size() == m_blockchain.offset()) { MINFO("Fixing empty hashchain"); - cryptonote::rpc::GET_BLOCK_HEADER_BY_HEIGHT::request req{}; - cryptonote::rpc::GET_BLOCK_HEADER_BY_HEIGHT::response res{}; - req.height = m_blockchain.size() - 1; - bool r = invoke_http(req, res); - if (r && res.status == rpc::STATUS_OK) + nlohmann::json req_params{ + {"height", m_blockchain.size() - 1} + }; + auto res = m_http_client.json_rpc("get_block_header_by_height", req_params); + if (res["status"] == rpc::STATUS_OK) { crypto::hash hash; - tools::hex_to_type(res.block_header->hash, hash); + tools::hex_to_type(res["block_header"]["hash"].get(), hash); m_blockchain.refill(hash); } else @@ -6494,11 +6503,14 @@ std::optional wallet2::resolve_address(std::string address, uint64_ if (bns::validate_bns_name(name, &reason)) { std::string b64_hashed_name = bns::name_to_base64_hash(name); - rpc::BNS_RESOLVE::request lookup_req{1, b64_hashed_name}; - auto [success, addr_response] = resolve(lookup_req); - if (success && addr_response.encrypted_value) + nlohmann::json req_params{ + {"type", 1}, + {"name_hash", b64_hashed_name} + }; + auto [success, addr_response] = resolve(req_params); + if (success && addr_response["encrypted_value"]) { - std::optional addr_info = bns::encrypted_wallet_value_to_info(name, *addr_response.encrypted_value, *addr_response.nonce); + std::optional addr_info = bns::encrypted_wallet_value_to_info(name, addr_response["encrypted_value"], addr_response["nonce"]); if (addr_info) { info = std::move(*addr_info); @@ -6538,18 +6550,21 @@ void wallet2::rescan_spent() { const size_t n_outputs = std::min(chunk_size, m_transfers.size() - start_offset); MDEBUG("Calling is_key_image_spent on " << start_offset << " - " << (start_offset + n_outputs - 1) << ", out of " << m_transfers.size()); - rpc::IS_KEY_IMAGE_SPENT::request req{}; - rpc::IS_KEY_IMAGE_SPENT::response daemon_resp{}; + std::vector key_images; + key_images.reserve(n_outputs); for (size_t n = start_offset; n < start_offset + n_outputs; ++n) - req.key_images.push_back(tools::type_to_hex(m_transfers[n].m_key_image)); - bool r = invoke_http(req, daemon_resp); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == rpc::STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != rpc::STATUS_OK, error::is_key_image_spent_error, get_rpc_status(daemon_resp.status)); - THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error, + key_images.push_back(tools::type_to_hex(m_transfers[n].m_key_image)); + + nlohmann::json req_params{ + {"key_images", key_images} + }; + auto kispent_res = m_http_client.json_rpc("is_key_image_spent", req_params); + THROW_WALLET_EXCEPTION_IF(kispent_res["status"] == rpc::STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(kispent_res["status"] != rpc::STATUS_OK, error::is_key_image_spent_error, get_rpc_status(kispent_res["status"])); + THROW_WALLET_EXCEPTION_IF(kispent_res["spent_status"].size() != n_outputs, error::wallet_internal_error, "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + - std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs)); - std::copy(daemon_resp.spent_status.begin(), daemon_resp.spent_status.end(), std::back_inserter(spent_status)); + std::to_string(kispent_res["spent_status"].size()) + ", expected " + std::to_string(n_outputs)); + std::copy(kispent_res["spent_status"].begin(), kispent_res["spent_status"].end(), std::back_inserter(spent_status)); } // update spent status @@ -6559,7 +6574,7 @@ void wallet2::rescan_spent() // a view wallet may not know about key images if (!td.m_key_image_known || td.m_key_image_partial) continue; - if (td.m_spent != (spent_status[i] != rpc::IS_KEY_IMAGE_SPENT::UNSPENT)) + if (td.m_spent != (static_cast(spent_status[i]) != rpc::IS_KEY_IMAGE_SPENT::SPENT::UNSPENT)) { if (td.m_spent) { @@ -6644,12 +6659,12 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, return true; } - for (cryptonote::rpc::GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES::entry const &entry : blacklist) + for (auto const& entry : blacklist) { crypto::key_image check_image; - if(!tools::hex_to_type(entry.key_image, check_image)) + if(!tools::hex_to_type(entry["key_image"].get(), check_image)) { - MERROR("Failed to parse hex representation of key image: " << entry.key_image); + MERROR("Failed to parse hex representation of key image: " << entry["key_image"]); break; } @@ -6668,19 +6683,19 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, return true; } - for (cryptonote::rpc::GET_MASTER_NODES::response::entry const &entry : master_nodes_states) + for (auto const& entry : master_nodes_states) { - for (auto const& contributor : entry.contributors) + for (auto const& contributor : entry["contributors"]) { - if (primary_address != contributor.address) + if (primary_address != contributor["address"]) continue; - for (auto const &contribution : contributor.locked_contributions) + for (auto const &contribution : contributor["locked_contributions"]) { crypto::key_image check_image; - if(!tools::hex_to_type(contribution.key_image, check_image)) + if(!tools::hex_to_type(contribution["key_image"].get(), check_image)) { - MERROR("Failed to parse hex representation of key image: " << contribution.key_image); + MERROR("Failed to parse hex representation of key image: " << contribution["key_image"]); break; } @@ -6931,18 +6946,19 @@ void wallet2::commit_tx(pending_tx& ptx, bool flash) else { // Normal submit - rpc::SEND_RAW_TX::request req{}; - req.tx_as_hex = oxenc::to_hex(tx_to_blob(ptx.tx)); - req.do_not_relay = false; - req.flash = flash; - rpc::SEND_RAW_TX::response daemon_send_resp{}; - bool r = invoke_http(req, daemon_send_resp); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction"); - THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == rpc::STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); + nlohmann::json send_transaction_params{ + {"tx_as_hex", oxenc::to_hex(tx_to_blob(ptx.tx))}, + {"do_not_relay", false}, + {"do_sanity_checks", true}, + {"flash", flash}, + + }; + auto daemon_send_resp = m_http_client.json_rpc("send_raw_transaction", send_transaction_params); + THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] == rpc::STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); if (flash) - THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != rpc::STATUS_OK, error::tx_flash_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp, &ptx.tx, flash)); + THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] != rpc::STATUS_OK, error::tx_flash_rejected, ptx.tx, get_rpc_status(daemon_send_resp["status"]), get_text_reason(daemon_send_resp, &ptx.tx, flash)); else - THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != rpc::STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp, &ptx.tx, flash)); + THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] != rpc::STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp["status"]), get_text_reason(daemon_send_resp, &ptx.tx, flash)); // sanity checks for (size_t idx: ptx.selected_transfers) { @@ -7950,11 +7966,14 @@ bool wallet2::unset_ring(const crypto::hash &txid) if (!m_ringdb) return false; - auto res = request_transaction(txid); - + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); + cryptonote::transaction tx; crypto::hash tx_hash; - if (!get_pruned_tx(res.txs.front(), tx, tx_hash)) + if (!get_pruned_tx(res["txs"].front(), tx, tx_hash)) return false; THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); @@ -7986,12 +8005,15 @@ bool wallet2::find_and_save_rings(bool force) for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE) { size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE; - auto res = request_transactions(hashes_to_hex(txs_hashes.begin() + slice, txs_hashes.begin() + ntxes)); + nlohmann::json get_transactions_params{ + {"tx_hashes", hashes_to_hex(txs_hashes.begin() + slice, txs_hashes.begin() + ntxes)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); - MDEBUG("Scanning " << res.txs.size() << " transactions"); - for (size_t i = 0; i < res.txs.size(); ++i, ++it) + MDEBUG("Scanning " << res["txs"].size() << " transactions"); + for (size_t i = 0; i < res["txs"].size(); ++i, ++it) { - const auto &tx_info = res.txs[i]; + const auto &tx_info = res["txs"][i]; cryptonote::transaction tx; crypto::hash tx_hash; THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error, @@ -8098,29 +8120,30 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ } const auto& mnode_info = response.front(); - if (amount == 0) amount = mnode_info.staking_requirement * fraction; + if (amount == 0) + amount = mnode_info["staking_requirement"].get() * fraction; size_t total_existing_contributions = 0; // Count both contributions and reserved spots - for (auto const &contributor : mnode_info.contributors) + for (auto const &contributor : mnode_info["contributors"]) { - total_existing_contributions += contributor.locked_contributions.size(); // contribution - if (contributor.reserved > contributor.amount) + total_existing_contributions += contributor["locked_contributions"].size(); // contribution + if (contributor["reserved"].get() > contributor["amount"].get()) total_existing_contributions++; // reserved contributor spot } - uint64_t max_contrib_total = mnode_info.staking_requirement - mnode_info.total_reserved; - uint64_t min_contrib_total = master_nodes::get_min_node_contribution(*hf_version, mnode_info.staking_requirement, mnode_info.total_reserved, total_existing_contributions); + uint64_t max_contrib_total = mnode_info["staking_requirement"].get() - mnode_info["total_reserved"].get(); + uint64_t min_contrib_total = master_nodes::get_min_node_contribution(hf_version, mnode_info["staking_requirement"], mnode_info["total_reserved"], total_existing_contributions); bool is_preexisting_contributor = false; - for (const auto& contributor : mnode_info.contributors) + for (const auto& contributor : mnode_info["contributors"]) { address_parse_info info; - if (!cryptonote::get_account_address_from_str(info, m_nettype, contributor.address)) + if (!cryptonote::get_account_address_from_str(info, m_nettype, contributor["address"].get())) continue; if (info.address == addr_info.address) { - uint64_t const reserved_amount_not_contributed_yet = contributor.reserved - contributor.amount; + uint64_t const reserved_amount_not_contributed_yet = contributor["reserved"].get() - contributor["amount"].get(); max_contrib_total += reserved_amount_not_contributed_yet; is_preexisting_contributor = true; @@ -8137,7 +8160,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ return result; } - const bool full = mnode_info.contributors.size() >= beldex::MAX_NUMBER_OF_CONTRIBUTORS; + const bool full = mnode_info["contributors"].size() >= MAX_NUMBER_OF_CONTRIBUTORS; if (full && !is_preexisting_contributor) { result.status = stake_result_status::master_node_contributors_maxed; @@ -8543,23 +8566,24 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry } cryptonote::account_public_address const primary_address = get_address(); - std::vector const *contributions = nullptr; - rpc::GET_MASTER_NODES::response::entry const &node_info = response[0]; rpc::master_node_contributor const *p_contributor = nullptr; - for (auto const &contributor : node_info.contributors) + nlohmann::json contributions{}; + bool found = false; + auto const& node_info = response[0]; + for (auto const &contributor : node_info["contributors"]) { address_parse_info address_info = {}; - cryptonote::get_account_address_from_str(address_info, nettype(), contributor.address); + cryptonote::get_account_address_from_str(address_info, nettype(), contributor["address"].get()); if (address_info.address != primary_address) continue; - contributions = &contributor.locked_contributions; + found = true; + contributions = contributor["locked_contributions"]; p_contributor = &contributor; - break; } - if (!contributions) + if (!found) { result.msg = tr("No contributions recognised by this wallet in master node: ") + mn_key_as_str; return result; @@ -8592,27 +8616,27 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry } result.msg.reserve(1024); - auto const &contribution = (*contributions)[0]; - if (node_info.requested_unlock_height != 0) + auto const &contribution = contributions[0]; + if (node_info["requested_unlock_height"] != 0) { result.msg.append("Key image: "); - result.msg.append(contribution.key_image); + result.msg.append(contribution["key_image"]); result.msg.append(" has already been requested to be unlocked, unlocking at height: "); - result.msg.append(std::to_string(node_info.requested_unlock_height)); + result.msg.append(node_info["requested_unlock_height"].get()); result.msg.append(" (about "); - result.msg.append(tools::get_human_readable_timespan(std::chrono::seconds((node_info.requested_unlock_height - curr_height) * TARGET_BLOCK_TIME))); + result.msg.append(tools::get_human_readable_timespan(std::chrono::seconds((node_info["requested_unlock_height"].get() - curr_height) * TARGET_BLOCK_TIME))); result.msg.append(")"); return result; } result.msg.append("You are requesting to unlock a stake of: "); - result.msg.append(cryptonote::print_money(contribution.amount)); + result.msg.append(cryptonote::print_money(contribution["amount"])); result.msg.append(" Beldex from the master node network.\nThis will schedule the master node: "); - result.msg.append(node_info.master_node_pubkey); + result.msg.append(node_info["master_node_pubkey"]); result.msg.append(" for deactivation."); - if (node_info.contributors.size() > 1) { + if (node_info["contributors"].size() > 1) { result.msg.append(" The stakes of the master node's "); - result.msg.append(std::to_string(node_info.contributors.size() - 1)); + result.msg.append(std::to_string(node_info["contributors"].size() - 1)); result.msg.append(" other contributors will unlock at the same time."); } result.msg.append("\n\n"); @@ -8622,16 +8646,16 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; return result; } - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(nettype(), curr_height,*hf_version); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(nettype(), node_info["registration_height"], curr_height); result.msg.append("You will continue receiving rewards until the master node expires at the estimated height: "); result.msg.append(std::to_string(unlock_height)); result.msg.append(" (about "); result.msg.append(tools::get_human_readable_timespan(std::chrono::seconds((unlock_height - curr_height) * TARGET_BLOCK_TIME))); result.msg.append(")"); - if(!tools::hex_to_type(contribution.key_image, unlock.key_image)) + if(!tools::hex_to_type(contribution["key_image"].get(), unlock.key_image)) { - result.msg = tr("Failed to parse hex representation of key image: ") + contribution.key_image; + result.msg = tr("Failed to parse hex representation of key image: ") + contribution["key_image"].get(); return result; } @@ -8645,7 +8669,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry try { if (!generate_signature_for_request_stake_unlock(unlock.key_image, unlock.signature)) { - result.msg = tr("Failed to generate signature to sign request. The key image: ") + contribution.key_image + tr(" doesn't belong to this wallet"); + result.msg = tr("Failed to generate signature to sign request. The key image: ") + contribution["key_image"].get() + tr(" doesn't belong to this wallet"); return result; } } catch (const std::exception& e) { @@ -8718,7 +8742,7 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons bns::bns_tx_type txtype, uint32_t account_index, std::string *reason, - std::vector *response) + nlohmann::json *response) { bns_prepared_args result = {}; if (priority == tools::tx_priority_flash) @@ -8787,12 +8811,13 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons return {}; { - cryptonote::rpc::BNS_NAMES_TO_OWNERS::request request = {}; - { - request.entries.push_back(oxenc::to_base64(tools::view_guts(result.name_hash))); - } - - auto [success, response_] = wallet.bns_names_to_owners(request); + nlohmann::json req_params{ + {"entries", { + {"name_hash", oxenc::to_base64(tools::view_guts(result.name_hash))}, + } + }, + }; + auto [success, response_] = wallet.bns_names_to_owners(req_params); if (!response) response = &response_; else @@ -8803,11 +8828,11 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons return result; } - if (response->size()) + if ((*response)["entries"].size()) { - if (!tools::hex_to_type((*response)[0].txid, result.prev_txid)) + if (!tools::hex_to_type((*response)["entries"][0]["txid"].get(), result.prev_txid)) { - if (reason) *reason = "Failed to convert response txid=" + (*response)[0].txid + " from the daemon into a 32 byte hash, it must be a 64 char hex string"; + if (reason) *reason = "Failed to convert response txid=" + (*response)["entries"][0]["txid"].get() + " from the daemon into a 32 byte hash, it must be a 64 char hex string"; return result; } } @@ -8822,18 +8847,18 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons cryptonote::address_parse_info curr_owner_parsed = {}; cryptonote::address_parse_info curr_backup_owner_parsed = {}; - auto& rowner = response->front().owner; - auto& rbackup_owner = response->front().backup_owner; - bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner); - bool curr_backup_owner = rbackup_owner && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), *rbackup_owner); + auto& rowner = (*response)["entries"].front()["owner"]; + auto& rbackup_owner = (*response)["entries"].front()["backup_owner"]; + bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner.get()); + bool curr_backup_owner = rbackup_owner && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), rbackup_owner.get()); if (!try_generate_bns_signature(wallet, rowner, owner, backup_owner, result)) { - if (!rbackup_owner || !try_generate_bns_signature(wallet, *rbackup_owner, owner, backup_owner, result)) + if (!rbackup_owner || !try_generate_bns_signature(wallet, rbackup_owner, owner, backup_owner, result)) { if (reason) { - *reason = "Signature requested when preparing BNS TX, but this wallet is not the owner of the record owner=" + rowner; - if (rbackup_owner) *reason += ", backup_owner=" + *rbackup_owner; + *reason = "Signature requested when preparing ONS TX, but this wallet is not the owner of the record owner=" + rowner.get(); + if (rbackup_owner) *reason += ", backup_owner=" + rbackup_owner.get(); } return result; } @@ -8858,7 +8883,7 @@ std::vector wallet2::bns_create_buy_mapping_tx(bns::mapping uint32_t account_index, std::set subaddr_indices) { - std::vector response; + nlohmann::json response; constexpr bool make_signature = false; bns_prepared_args prepared_args = prepare_tx_extra_beldex_name_system_values(*this, priority, name, value_bchat, value_wallet, value_belnet,value_eth_addr, owner, backup_owner, make_signature, bns::bns_tx_type::buy, account_index, reason, &response); if (!owner) @@ -8932,7 +8957,7 @@ std::vector wallet2::bns_create_renewal_tx( uint32_t priority, uint32_t account_index, std::set subaddr_indices, - std::vector *response + nlohmann::json *response ) { constexpr bool make_signature = false; @@ -8981,7 +9006,7 @@ std::vector wallet2::bns_create_update_mapping_tx(std::stri uint32_t priority, uint32_t account_index, std::set subaddr_indices, - std::vector *response) + nlohmann::json *response) { if (!value_bchat && !value_wallet && !value_belnet && !value_eth_addr && !owner && !backup_owner) { @@ -9068,7 +9093,7 @@ bool wallet2::bns_make_update_mapping_signature(std::string name, uint32_t account_index, std::string *reason) { - std::vector response; + nlohmann::json response; constexpr bool make_signature = true; bns_prepared_args prepared_args = prepare_tx_extra_beldex_name_system_values(*this, tx_priority_unimportant, name, value_bchat, value_wallet, value_belnet, value_eth_addr, owner, backup_owner, make_signature, bns::bns_tx_type::update, account_index, reason, &response); if (!prepared_args) return false; @@ -9304,11 +9329,10 @@ void wallet2::get_outs(std::vector> // if we have at least one rct out, get the distribution, or fall back to the previous system uint64_t rct_start_height; std::vector rct_offsets; + std::vector amounts; const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets); // get histogram for the amounts we need - cryptonote::rpc::GET_OUTPUT_HISTOGRAM::request req_t{}; - cryptonote::rpc::GET_OUTPUT_HISTOGRAM::response resp_t{}; { uint64_t max_rct_index = 0; for (size_t idx: selected_transfers) @@ -9321,7 +9345,7 @@ void wallet2::get_outs(std::vector> // request histogram for all outputs, except 0 if we have the rct distribution if (!m_transfers[idx].is_rct() || !has_rct_distribution) { - req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); + amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); } } @@ -9347,17 +9371,20 @@ void wallet2::get_outs(std::vector> << "), please notify the Beldex developers"); } - if (!req_t.amounts.empty()) + nlohmann::json res; + if (!amounts.empty()) { - std::sort(req_t.amounts.begin(), req_t.amounts.end()); - auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); - req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); - req_t.unlocked = true; - req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; - bool r = invoke_http(req_t, resp_t); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == rpc::STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != rpc::STATUS_OK, error::get_histogram_error, get_rpc_status(resp_t.status)); + std::sort(amounts.begin(), amounts.end()); + auto end = std::unique(amounts.begin(), amounts.end()); + amounts.resize(std::distance(amounts.begin(), end)); + nlohmann::json req_params{ + {"amounts", amounts}, + {"unlocked", true}, + {"recent_cutoff", time(NULL) - RECENT_OUTPUT_ZONE} + }; + res = m_http_client.json_rpc("get_output_histogram", req_params); + THROW_WALLET_EXCEPTION_IF(res["status"] == rpc::STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(res["status"] != rpc::STATUS_OK, error::get_histogram_error, get_rpc_status(res["status"])); } // if we want to segregate fake outs pre or post fork, get distribution @@ -9446,14 +9473,14 @@ void wallet2::get_outs(std::vector> { // if there are just enough outputs to mix with, use all of them. // Eventually this should become impossible. - for (const auto &he: resp_t.histogram) + for (const auto &he: res["histogram"]) { - if (he.amount == amount) + if (he["amount"].get() == amount) { - LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, " - << he.unlocked_instances << " unlocked, " << he.recent_instances << " recent"); - num_outs = he.unlocked_instances; - num_recent_outs = he.recent_instances; + LOG_PRINT_L2("Found " << print_money(amount) << ": " << he["total_instances"] << " total, " + << he["unlocked_instances"] << " unlocked, " << he["recent_instances"] << " recent"); + num_outs = he["unlocked_instances"].get(); + num_recent_outs = he["recent_instances"].get(); break; } } @@ -9784,11 +9811,11 @@ void wallet2::get_outs(std::vector> const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; if (is_after_segregation_fork && m_segregate_pre_fork_outputs && output_is_pre_fork) num_outs = segregation_limit[amount].first; - else for (const auto &he: resp_t.histogram) + else for (const auto &he: res["histogram"]) { - if (he.amount == amount) + if (he["amount"].get() == amount) { - num_outs = he.unlocked_instances; + num_outs = he["unlocked_instances"].get(); break; } } @@ -12167,23 +12194,21 @@ std::vector wallet2::get_unspent_amounts_vector(bool strict) const //---------------------------------------------------------------------------------------------------- std::vector wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct) { - cryptonote::rpc::GET_OUTPUT_HISTOGRAM::request req_t{}; - cryptonote::rpc::GET_OUTPUT_HISTOGRAM::response resp_t{}; - if (is_trusted_daemon()) - req_t.amounts = get_unspent_amounts_vector(false); - req_t.min_count = count; - req_t.max_count = 0; - req_t.unlocked = unlocked; - req_t.recent_cutoff = 0; - bool r = invoke_http(req_t, resp_t); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_outputs_from_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == rpc::STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != rpc::STATUS_OK, error::get_histogram_error, resp_t.status); + nlohmann::json req_params{ + {"amounts", get_unspent_amounts_vector(false)}, + {"min_count", count}, + {"max_count", 0}, + {"unlocked", unlocked}, + {"recent_cutoff", 0} + }; + auto res = m_http_client.json_rpc("get_output_histogram", req_params); + THROW_WALLET_EXCEPTION_IF(res["status"] == rpc::STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(res["status"] != rpc::STATUS_OK, error::get_histogram_error, res["status"]); std::set mixable; - for (const auto &i: resp_t.histogram) + for (const auto &i: res["histogram"]) { - mixable.insert(i.amount); + mixable.insert(i["amount"].get()); } return select_available_outputs([mixable, atleast, allow_rct](const transfer_details &td) { @@ -12204,21 +12229,20 @@ std::vector wallet2::select_available_outputs_from_histogram(uint64_t co //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_num_rct_outputs() { - cryptonote::rpc::GET_OUTPUT_HISTOGRAM::request req_t{}; - cryptonote::rpc::GET_OUTPUT_HISTOGRAM::response resp_t{}; - req_t.amounts.push_back(0); - req_t.min_count = 0; - req_t.max_count = 0; - req_t.unlocked = true; - req_t.recent_cutoff = 0; - bool r = invoke_http(req_t, resp_t); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_num_rct_outputs"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == rpc::STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != rpc::STATUS_OK, error::get_histogram_error, resp_t.status); - THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); - THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); + nlohmann::json req_params{ + {"amounts", std::vector{0}}, + {"min_count", 0}, + {"max_count", 0}, + {"unlocked", true}, + {"recent_cutoff", 0} + }; + auto res = m_http_client.json_rpc("get_output_histogram", req_params); + THROW_WALLET_EXCEPTION_IF(res["status"] == rpc::STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(res["status"] != rpc::STATUS_OK, error::get_histogram_error, res["status"]); + THROW_WALLET_EXCEPTION_IF(res["histogram"].size() != 1, error::get_histogram_error, "Expected exactly one response"); + THROW_WALLET_EXCEPTION_IF(res["histogram"][0]["amount"].get() != 0, error::get_histogram_error, "Expected 0 amount"); - return resp_t.histogram[0].total_instances; + return res["histogram"][0]["total_instances"].get(); } //---------------------------------------------------------------------------------------------------- const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const @@ -12330,12 +12354,15 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s // Load missing tx prefix hash if (tx_key_data.tx_prefix_hash.empty()) { - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash{}; crypto::hash tx_prefix_hash{}; - const auto& res_tx = res.txs.front(); + const auto& res_tx = res["txs"].front(); THROW_WALLET_EXCEPTION_IF(!oxenc::is_hex(*res_tx.pruned_as_hex) || !oxenc::is_hex(res_tx.prunable_as_hex.value_or(""s)),error::wallet_internal_error, "Failed to parse transaction from daemon"); std::string tx_data; @@ -12373,10 +12400,13 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys) { // fetch tx from daemon and check if secret keys agree with corresponding public keys - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash; - THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res["txs"][0], tx, tx_hash), error::wallet_internal_error, "Failed to get transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); std::vector tx_extra_fields; @@ -12409,11 +12439,14 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, std::string_view "get_spend_proof requires spend secret key and is not available for a watch-only wallet"); // fetch tx from daemon - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash; - THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "Failed to get tx from daemon"); + THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res["txs"][0], tx, tx_hash), error::wallet_internal_error, "Failed to get tx from daemon"); std::vector> signatures; @@ -12506,11 +12539,14 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, std::string_view messa sig_str.remove_prefix(SPEND_PROOF_MAGIC.size()); // fetch tx from daemon - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash; - THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, "failed to get tx from daemon"); + THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res["txs"][0], tx, tx_hash), error::wallet_internal_error, "failed to get tx from daemon"); // check signature size size_t num_sigs = 0; @@ -12659,10 +12695,13 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) { - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash; - bool ok = get_pruned_tx(res.txs.front(), tx, tx_hash); + bool ok = get_pruned_tx(res["txs"].front(), tx, tx_hash); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); @@ -12671,25 +12710,28 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de check_tx_key_helper(tx, derivation, additional_derivations, address, received); - in_pool = res.txs.front().in_pool; + in_pool = res["txs"].front()["in_pool"]; confirmations = 0; if (!in_pool) { std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - confirmations = bc_height - res.txs.front().block_height; + confirmations = bc_height - res["txs"].front()["block_height"].get(); } } std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, std::string_view message) { // fetch tx pubkey from the daemon - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash; - bool ok = get_pruned_tx(res.txs.front(), tx, tx_hash); + bool ok = get_pruned_tx(res["txs"].front(), tx, tx_hash); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); @@ -12819,25 +12861,28 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, std::string_view message, std::string_view sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) { // fetch tx pubkey from the daemon - auto res = request_transaction(txid); + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); cryptonote::transaction tx; crypto::hash tx_hash; - bool ok = get_pruned_tx(res.txs.front(), tx, tx_hash); + bool ok = get_pruned_tx(res["txs"].front(), tx, tx_hash); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); if (!check_tx_proof(tx, address, is_subaddress, message, sig_str, received)) return false; - in_pool = res.txs.front().in_pool; + in_pool = res["txs"].front()["in_pool"]; confirmations = 0; if (!in_pool) { std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - confirmations = bc_height - res.txs.front().block_height; + confirmations = bc_height - res["txs"].front()["block_height"].get(); } return true; @@ -13102,26 +13147,32 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); // fetch txes from daemon - auto gettx_res = request_transactions(std::move(txids_hex)); + nlohmann::json get_transactions_params{ + {"tx_hashes", std::move(txids_hex)} + }; + auto gettx_res = m_http_client.json_rpc("get_transactions", get_transactions_params); // check spent status - rpc::IS_KEY_IMAGE_SPENT::request kispent_req{}; - rpc::IS_KEY_IMAGE_SPENT::response kispent_res{}; + std::vector key_images; + key_images.reserve(proofs.size()); for (size_t i = 0; i < proofs.size(); ++i) - kispent_req.key_images.push_back(tools::type_to_hex(proofs[i].key_image)); - bool ok = invoke_http(kispent_req, kispent_res); - THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), + key_images.push_back(tools::type_to_hex(proofs[i].key_image)); + nlohmann::json req_params{ + {"key_images", key_images} + }; + auto kispent_res = m_http_client.json_rpc("is_key_image_spent", req_params); + THROW_WALLET_EXCEPTION_IF(kispent_res["spent_status"].size() != proofs.size(), error::wallet_internal_error, "Failed to get key image spent status from daemon"); total = spent = 0; for (size_t i = 0; i < proofs.size(); ++i) { const reserve_proof_entry& proof = proofs[i]; - THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed"); + THROW_WALLET_EXCEPTION_IF(gettx_res["txs"][i]["in_pool"], error::wallet_internal_error, "Tx is unconfirmed"); cryptonote::transaction tx; crypto::hash tx_hash; - ok = get_pruned_tx(gettx_res.txs[i], tx, tx_hash); + bool ok = get_pruned_tx(gettx_res["txs"][i], tx, tx_hash); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); @@ -13200,7 +13251,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr amount = rct::h2d(ecdh_info.amount); } total += amount; - if (kispent_res.spent_status[i]) + if (kispent_res["spent_status"][i]) spent += amount; } @@ -13265,36 +13316,9 @@ uint64_t wallet2::get_approximate_blockchain_height() const return approx_blockchain_height; } -std::vector wallet2::list_current_stakes() +nlohmann::json wallet2::list_current_stakes() { - - std::vector master_node_states; - - auto [success, all_nodes] = this->get_all_master_nodes(); - if (!success) - { - return master_node_states; - } - - cryptonote::account_public_address const primary_address = this->get_address(); - for (rpc::GET_MASTER_NODES::response::entry const &node_info : all_nodes) - { - for (const auto& contributor : node_info.contributors) - { - address_parse_info address_info = {}; - if (!cryptonote::get_account_address_from_str(address_info, this->nettype(), contributor.address)) - { - continue; - } - - if (primary_address != address_info.address) - continue; - - master_node_states.push_back(node_info); - break; // not necessory to check the other contributor in the same masternode once we got our info - } - } - + auto [success, master_node_states] = this->get_all_master_nodes(); return master_node_states; } @@ -13708,8 +13732,6 @@ uint64_t wallet2::import_key_images_from_file(const fs::path& filename, uint64_t uint64_t wallet2::import_key_images(const std::vector> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent) { PERF_TIMER(import_key_images_lots); - rpc::IS_KEY_IMAGE_SPENT::request req{}; - rpc::IS_KEY_IMAGE_SPENT::response daemon_resp{}; THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); THROW_WALLET_EXCEPTION_IF(signed_key_images.size() > m_transfers.size() - offset, error::wallet_internal_error, @@ -13722,7 +13744,8 @@ uint64_t wallet2::import_key_images(const std::vector key_images{}; + key_images.reserve(signed_key_images.size()); PERF_TIMER_START(import_key_images_A_validate_and_extract_key_images); for (size_t n = 0; n < signed_key_images.size(); ++n) @@ -13758,7 +13781,7 @@ uint64_t wallet2::import_key_images(const std::vector(req, daemon_resp); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == rpc::STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != rpc::STATUS_OK, error::is_key_image_spent_error, daemon_resp.status); - THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error, + is_key_image_spent_response = m_http_client.json_rpc("is_key_image_spent", req_params); + THROW_WALLET_EXCEPTION_IF(is_key_image_spent_response["status"] == rpc::STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(is_key_image_spent_response["status"] != rpc::STATUS_OK, error::is_key_image_spent_error, is_key_image_spent_response["status"]); + THROW_WALLET_EXCEPTION_IF(is_key_image_spent_response["spent_status"].size() != signed_key_images.size(), error::wallet_internal_error, "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + - std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size())); - for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n) + std::to_string(is_key_image_spent_response["spent_status"].size()) + ", expected " + std::to_string(signed_key_images.size())); + for (size_t n = 0; n < is_key_image_spent_response["spent_status"].size(); ++n) { transfer_details &td = m_transfers[n + offset]; - td.m_spent = daemon_resp.spent_status[n] != rpc::IS_KEY_IMAGE_SPENT::UNSPENT; + td.m_spent = is_key_image_spent_response["spent_status"][n] != rpc::IS_KEY_IMAGE_SPENT::SPENT::UNSPENT; } } std::unordered_set spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input. @@ -13830,9 +13856,9 @@ uint64_t wallet2::import_key_images(const std::vector::const_iterator skii = spent_key_images.find(td.m_key_image); if (skii == spent_key_images.end()) @@ -13849,7 +13875,10 @@ uint64_t wallet2::import_key_images(const std::vectorsecond, e.block_height); + set_spent(it->second, e["block_height"]); if (m_callback) - m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index); + m_callback->on_money_spent(e["block_height"], *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index); if (subaddr_account != (uint32_t)-1 && subaddr_account != td.m_subaddr_index.major) LOG_PRINT_L0("WARNING: This tx spends outputs received by different subaddress accounts, which isn't supposed to happen"); subaddr_account = td.m_subaddr_index.major; @@ -13938,7 +13967,7 @@ uint64_t wallet2::import_key_images(const std::vector(req, res); + bool r = invoke_http(req, res); if (!r || res.status != rpc::STATUS_OK) { std::ostringstream oss; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 4d21e7e607a..5c5918e9596 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -46,6 +46,7 @@ #include "cryptonote_basic/account_boost_serialization.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/core_rpc_server_binary_commands.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_core/cryptonote_tx_utils.h" #include "cryptonote_core/beldex_name_system.h" @@ -817,11 +818,10 @@ namespace tools auto get_all_master_nodes() const { return m_node_rpc_proxy.get_all_master_nodes(); } auto get_master_nodes(std::vector const &pubkeys) const { return m_node_rpc_proxy.get_master_nodes(pubkeys); } auto get_master_node_blacklisted_key_images() const { return m_node_rpc_proxy.get_master_node_blacklisted_key_images(); } - nlohmann::json list_current_stakes(); - auto bns_owners_to_names(cryptonote::rpc::BNS_OWNERS_TO_NAMES::request const &request) const { return m_node_rpc_proxy.bns_owners_to_names(request); } + auto bns_owners_to_names(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_owners_to_names(request); } auto bns_names_to_owners(cryptonote::rpc::BNS_NAMES_TO_OWNERS::request const &request) const { return m_node_rpc_proxy.bns_names_to_owners(request); } auto resolve(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_resolve(request); } - + nlohmann::json list_current_stakes(); struct bns_detail { std::string name; @@ -1310,6 +1310,11 @@ namespace tools return false; } + nlohmann::json json_rpc(std::string command, nlohmann::json params) + { + return m_http_client.json_rpc(command, params); + } + bool set_ring_database(fs::path filename); const fs::path& get_ring_database() const { return m_ring_database; } bool get_ring(const crypto::key_image &key_image, std::vector &outs); @@ -1405,11 +1410,9 @@ namespace tools // signature: (Optional) If set, use the signature given, otherwise by default derive the signature from the wallet spend key as an ed25519 key. // The signature is derived from the hash of the previous txid blob and previous value blob of the mapping. By default this is signed using the wallet's spend key as an ed25519 keypair. - std::vector bns_create_update_mapping_tx(std::string name, std::string const *value_bchat, std::string const *value_wallet, std::string const *value_belnet, std::string const *value_eth_addr, std::string const *owner, std::string const *backup_owner, std::string const *signature, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set subaddr_indices = {}, std::vector *response = {}); - + std::vector bns_create_update_mapping_tx(std::string name, std::string const *value_bchat, std::string const *value_wallet, std::string const *value_belnet, std::string const *value_eth_addr, std::string const *owner, std::string const *backup_owner, std::string const *signature, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set subaddr_indices = {}, nlohmann::json *response = {}); // BNS renewal (for belnet registrations, not for bchat/wallet) - std::vector bns_create_renewal_tx(bns::mapping_years map_years, std::string name, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set subaddr_indices = {}, std::vector *response = {}); - + std::vector bns_create_renewal_tx(bns::mapping_years map_years, std::string name, std::string *reason, uint32_t priority = 0, uint32_t account_index = 0, std::set subaddr_indices = {}, nlohmann::json *response = {}); // Generate just the signature required for putting into bns_update_mapping command in the wallet bool bns_make_update_mapping_signature(std::string name, std::string const *value_bchat, std::string const *value_wallet, std::string const *value_belnet, std::string const *value_eth_addr, std::string const *owner, std::string const *backup_owner, bns::generic_signature &signature, uint32_t account_index = 0, std::string *reason = nullptr); @@ -1447,17 +1450,6 @@ namespace tools std::atomic m_long_poll_disabled; static std::string get_default_daemon_address(); - /// Requests transactions from daemon given hex strings of the tx ids; throws a wallet exception - /// on error, otherwise returns the response. - cryptonote::rpc::GET_TRANSACTIONS::response request_transactions(std::vector txids_hex); - - /// Requests transactions from daemon given a vector of crypto::hash. Throws a wallet exception - /// on error, otherwise returns the response. - cryptonote::rpc::GET_TRANSACTIONS::response request_transactions(const std::vector& txids); - - /// Same as above, but for a single transaction. - cryptonote::rpc::GET_TRANSACTIONS::response request_transaction(const crypto::hash& txid) { return request_transactions(std::vector{{txid}}); } - // The wallet's RPC client; public for advanced configuration purposes. cryptonote::rpc::http_client m_http_client; @@ -1490,7 +1482,7 @@ namespace tools void get_short_chain_history(std::list& ids, uint64_t granularity = 1) const; bool clear(); void clear_soft(bool keep_key_images=false); - void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list &short_chain_history, std::vector &blocks, std::vector &o_indices, uint64_t ¤t_height); + void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list& short_chain_history, std::vector& blocks, std::vector& o_indices, uint64_t& current_height); void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list &short_chain_history, std::vector &hashes); void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list &short_chain_history, bool force = false); void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list &short_chain_history, const std::vector &prev_blocks, const std::vector &prev_parsed_blocks, std::vector &blocks, std::vector &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index beaa69a31cb..f13b57eb5c9 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -221,10 +221,12 @@ namespace tools epee::serialization::portable_storage ps; if(!ps.load_from_json(body)) - return jsonrpc_error_response(res, -32700, "Parse error"); + return jsonrpc_error_response(res, -32700, "Parse error", {}); - epee::serialization::storage_entry id{std::string{}}; - ps.get_value("id", id, nullptr); + epee::serialization::storage_entry epee_id{std::string{}}; + ps.get_value("id", epee_id, nullptr); + + nlohmann::json id = var::get(epee_id); std::string method; if(!ps.get_value("method", method, nullptr)) @@ -246,7 +248,7 @@ namespace tools // If it's a restricted command and we're in restricted mode then deny it if (restricted && m_restricted) { MWARNING("JSON RPC request for restricted command " << method << " in restricted mode from " << get_remote_address(res)); - return jsonrpc_error_response(res, error_code::DENIED, method + " is not available in restricted mode."); + return jsonrpc_error_response(res, error_code::DENIED, method + " is not available in restricted mode.", {}); } // Try to load "params" into a generic epee value; if it fails (because there is no "params") @@ -259,7 +261,7 @@ namespace tools wallet_rpc_error json_error{-32603, "Internal error"}; try { - result = invoke_ptr(ps, std::move(id), std::move(params), *this); + result = invoke_ptr(ps, std::move(epee_id), std::move(params), *this); json_error.code = 0; } catch (const parse_error& e) { json_error = {-32602, "Invalid params"}; // Reserved json code/message value for specifically this failure @@ -302,7 +304,7 @@ namespace tools } if (json_error.code != 0) - return jsonrpc_error_response(res, json_error.code, std::move(json_error.message)); + return jsonrpc_error_response(res, json_error.code, std::move(json_error.message), {}); res.writeHeader("Server", server_header()); res.writeHeader("Content-Type", "application/json"); @@ -2397,24 +2399,35 @@ namespace tools if (req.threads_count < 1 || max_mining_threads_count < req.threads_count) throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "The specified number of threads is inappropriate."}; - rpc::START_MINING::request daemon_req{}; - daemon_req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); - daemon_req.threads_count = req.threads_count; - - rpc::START_MINING::response daemon_res{}; - bool r = m_wallet->invoke_http(daemon_req, daemon_res); - if (!r || daemon_res.status != rpc::STATUS_OK) + nlohmann::json req_params{ + {"miner_address", m_wallet->get_account().get_public_address_str(m_wallet->nettype())}, + {"threads_count", req.threads_count} + }; + try + { + nlohmann::json res = m_wallet->json_rpc("start_mining", req_params); + if (res["status"] != rpc::STATUS_OK) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Couldn't start mining due to unknown error."}; + } + catch (...) { throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Couldn't start mining due to unknown error."}; + } + return {}; } //------------------------------------------------------------------------------------------------------------------------------ STOP_MINING::response wallet_rpc_server::invoke(STOP_MINING::request&& req) { require_open(); - rpc::STOP_MINING::response daemon_res{}; - bool r = m_wallet->invoke_http({}, daemon_res); - if (!r || daemon_res.status != rpc::STATUS_OK) + try + { + nlohmann::json res = m_wallet->json_rpc("stop_mining", {}); + if (res["status"] != rpc::STATUS_OK) + throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Couldn't stop mining due to unknown error."}; + } + catch (...) { throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "Couldn't stop mining due to unknown error."}; + } return {}; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2484,12 +2497,15 @@ namespace { if (!req.hardware_wallet) wal->set_seed_language(req.language); - rpc::GET_HEIGHT::request hreq{}; - rpc::GET_HEIGHT::response hres{}; - hres.height = 0; - bool r = wal->invoke_http(hreq, hres); - if (r) - wal->set_refresh_from_block_height(hres.height); + nlohmann::json req_params{ + {"height", 0} + }; + try + { + nlohmann::json res = wal->json_rpc("get_height", req_params); + wal->set_refresh_from_block_height(res["height"].get()); + } + catch (...) {} if (req.hardware_wallet) wal->restore_from_device(wallet_file, req.password, req.device_name.empty() ? "Ledger" : req.device_name); @@ -3299,8 +3315,10 @@ namespace { } auto nettype = m_wallet->nettype(); - rpc::BNS_NAMES_TO_OWNERS::request lookup_req{}; - lookup_req.include_expired = req.include_expired; + nlohmann::json req_params{ + {"include_expired", req.include_expired }, + {"entries", {}} + }; uint64_t curr_height = req.include_expired ? m_wallet->get_blockchain_current_height() : 0; @@ -3311,37 +3329,36 @@ namespace { const auto end = num_entries < rpc::BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES ? res.known_names.end() : it + rpc::BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES; - lookup_req.entries.clear(); - lookup_req.entries.reserve(std::distance(it, end)); for (auto it2 = it; it2 != end; it2++) { - auto& name_hash = lookup_req.entries.emplace_back(); - name_hash = it2->hashed; + auto& name_hash = req_params["entries"].emplace_back(nlohmann::json{ + {"name_hash", it2->hashed}, + }); } - if (auto [success, records] = m_wallet->bns_names_to_owners(lookup_req); success) + if (auto [success, records] = m_wallet->bns_names_to_owners(req_params); success) { size_t type_offset = std::distance(res.known_names.begin(), it); for (auto& rec : records) { - if (rec.entry_index >= num_entries) + if (rec["entry_index"].get() >= num_entries) { - MWARNING("Got back invalid entry_index " << rec.entry_index << " for a request for " << num_entries << " entries"); + MWARNING("Got back invalid entry_index " << rec["entry_index"] << " for a request for " << num_entries << " entries"); continue; } - auto& res_e = *(it + rec.entry_index); - res_e.owner = std::move(rec.owner); - res_e.backup_owner = std::move(rec.backup_owner); - res_e.encrypted_bchat_value = std::move(rec.encrypted_bchat_value); - res_e.encrypted_wallet_value = std::move(rec.encrypted_wallet_value); - res_e.encrypted_belnet_value = std::move(rec.encrypted_belnet_value); - res_e.encrypted_eth_addr_value = std::move(rec.encrypted_eth_addr_value); - res_e.update_height = rec.update_height; - res_e.expiration_height = rec.expiration_height; + auto& res_e = *(it + rec["entry_index"]); + res_e.owner = std::move(rec["owner"]); + res_e.backup_owner = std::move(rec["backup_owner"]); + res_e.encrypted_bchat_value = std::move(rec["encrypted_bchat_value"]); + res_e.encrypted_wallet_value = std::move(rec["encrypted_wallet_value"]); + res_e.encrypted_belnet_value = std::move(rec["encrypted_belnet_value"]); + res_e.encrypted_eth_addr_value = std::move(rec["encrypted_eth_addr_value"]); + res_e.update_height = rec["update_height"]; + res_e.expiration_height = rec["expiration_height"]; if (req.include_expired && res_e.expiration_height) res_e.expired = *res_e.expiration_height < curr_height; - res_e.txid = std::move(rec.txid); + res_e.txid = std::move(rec["txid"]); //BCHAT if (req.decrypt && !res_e.encrypted_bchat_value.empty() && oxenc::is_hex(res_e.encrypted_bchat_value)) From 4bed1ead5a7e288d862ef4cc096cee83f19a353b Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 29 Apr 2025 12:17:09 +0530 Subject: [PATCH 102/182] move and refactor common RPC code --- src/daemon/daemon.cpp | 2 +- src/daemon/main.cpp | 2 +- src/rpc/CMakeLists.txt | 21 +--- src/rpc/common/CMakeLists.txt | 16 +++ src/rpc/common/command_decorators.cpp | 12 ++ src/rpc/common/command_decorators.h | 93 +++++++++++++++ src/rpc/{ => common}/http_server_base.cpp | 0 src/rpc/{ => common}/http_server_base.h | 0 src/rpc/common/json_bt.cpp | 33 ++++++ src/rpc/common/json_bt.h | 12 ++ src/rpc/{ => common}/param_parser.hpp | 0 src/rpc/{ => common}/rpc_args.cpp | 0 src/rpc/{ => common}/rpc_args.h | 0 src/rpc/{ => common}/rpc_binary.cpp | 0 src/rpc/{ => common}/rpc_binary.h | 0 src/rpc/common/rpc_command.h | 126 +++++++++++++++++++++ src/rpc/common/rpc_version.h | 21 ++++ src/rpc/core_rpc_server.cpp | 99 ++++------------ src/rpc/core_rpc_server.h | 75 +----------- src/rpc/core_rpc_server_command_parser.cpp | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 6 +- src/rpc/core_rpc_server_commands_defs.h | 125 ++------------------ src/rpc/http_server.cpp | 3 +- src/rpc/http_server.h | 4 +- src/rpc/lmq_server.cpp | 2 +- src/wallet/wallet_rpc_server.cpp | 2 +- src/wallet/wallet_rpc_server.h | 2 +- 27 files changed, 364 insertions(+), 294 deletions(-) create mode 100644 src/rpc/common/CMakeLists.txt create mode 100644 src/rpc/common/command_decorators.cpp create mode 100644 src/rpc/common/command_decorators.h rename src/rpc/{ => common}/http_server_base.cpp (100%) rename src/rpc/{ => common}/http_server_base.h (100%) create mode 100644 src/rpc/common/json_bt.cpp create mode 100644 src/rpc/common/json_bt.h rename src/rpc/{ => common}/param_parser.hpp (100%) rename src/rpc/{ => common}/rpc_args.cpp (100%) rename src/rpc/{ => common}/rpc_args.h (100%) rename src/rpc/{ => common}/rpc_binary.cpp (100%) rename src/rpc/{ => common}/rpc_binary.h (100%) create mode 100644 src/rpc/common/rpc_command.h create mode 100644 src/rpc/common/rpc_version.h diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 7b07789c214..b8ae389b4c8 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -41,7 +41,7 @@ #if defined(PER_BLOCK_CHECKPOINT) #include "blocks/blocks.h" #endif -#include "rpc/rpc_args.h" +#include "rpc/common/rpc_args.h" #include "rpc/http_server.h" #include "rpc/lmq_server.h" // #include "rpc/bootstrap_daemon.h" diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 20a5eeb31d1..67ab747a330 100755 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -39,7 +39,7 @@ #include "daemonizer/daemonizer.h" #include "epee/misc_log_ex.h" #include "p2p/net_node.h" -#include "rpc/rpc_args.h" +#include "rpc/common/rpc_args.h" #include "rpc/core_rpc_server.h" #include "daemon/command_line_args.h" #include "version.h" diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index e9f75d35014..99350f91a1d 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -27,16 +27,12 @@ # 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. +add_subdirectory(common) + add_library(rpc_commands core_rpc_server_commands_defs.cpp core_rpc_server_binary_commands.cpp core_rpc_server_command_parser.cpp - rpc_binary.cpp - ) - -add_library(rpc_server_base - rpc_args.cpp - http_server_base.cpp ) add_library(rpc @@ -54,6 +50,7 @@ add_library(rpc_http_client ) target_link_libraries(rpc_commands PUBLIC + rpc_common common cpr::cpr nlohmann_json::nlohmann_json @@ -61,19 +58,11 @@ target_link_libraries(rpc_commands cryptonote_protocol extra) -target_link_libraries(rpc_server_base - PUBLIC - common - uWebSockets - nlohmann_json::nlohmann_json - PRIVATE - extra) - target_link_libraries(rpc PUBLIC cryptonote_core rpc_commands - rpc_server_base + rpc_common net version PRIVATE @@ -85,7 +74,7 @@ target_link_libraries(rpc target_link_libraries(daemon_rpc_server PRIVATE oxenc - rpc_server_base + rpc_common rpc Boost::thread extra) diff --git a/src/rpc/common/CMakeLists.txt b/src/rpc/common/CMakeLists.txt new file mode 100644 index 00000000000..c1d6b0cdcfc --- /dev/null +++ b/src/rpc/common/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(rpc_common + rpc_binary.cpp + command_decorators.cpp + json_bt.cpp + rpc_args.cpp + http_server_base.cpp + ) + +target_link_libraries(rpc_common + PUBLIC + common + uWebSockets + nlohmann_json::nlohmann_json + oxenmq::oxenmq + PRIVATE + extra) \ No newline at end of file diff --git a/src/rpc/common/command_decorators.cpp b/src/rpc/common/command_decorators.cpp new file mode 100644 index 00000000000..7d4d1b45662 --- /dev/null +++ b/src/rpc/common/command_decorators.cpp @@ -0,0 +1,12 @@ + +#include "command_decorators.h" + +namespace cryptonote::rpc { + +void RPC_COMMAND::set_bt() { + bt = true; + response_b64.format = json_binary_proxy::fmt::bt; + response_hex.format = json_binary_proxy::fmt::bt; +} + +} // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/common/command_decorators.h b/src/rpc/common/command_decorators.h new file mode 100644 index 00000000000..83ac433fc05 --- /dev/null +++ b/src/rpc/common/command_decorators.h @@ -0,0 +1,93 @@ +#pragma once + +#include "rpc_binary.h" + +#include + +namespace cryptonote::rpc { + + /// Returns a constexpr std::array of string_views from an arbitrary list of string literals + /// Used to specify RPC names as: + /// static constexpr auto names() { return NAMES("primary_name", "some_alias"); } + template + constexpr std::array NAMES(const char (&...names)[N]) { + static_assert(sizeof...(N) > 0, "RPC command must have at least one name"); + return {std::string_view{names, N-1}...}; + } + + /// Base class that all RPC commands must inherit from (either directly or via one or more of the + /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC + /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be + /// at `whatever`. This base class is also where response objects are stored. + struct RPC_COMMAND { + private: + bool bt = false; + public: + /// Indicates whether this response is to be bt (true) or json (false) encoded. Do not set. + bool is_bt() const { return bt; } + + /// Called early in the request to indicate that this request is a bt-encoded one. + void set_bt(); + + /// The response data. For bt-encoded responses we convert this on the fly, with the + /// following notes: + /// - boolean values become 0 or 1 + /// - key-value pairs with null values are omitted from the object + /// - other null values are not permitted at all: an exception will be raised if the json + /// contains such a value. + /// - double values are not permitted; if a double is absolutely needed then check `is_bt` + /// and, when bt, encode it in some documented, endpoint-specific way. + /// - binary values in strings *are* permitted, but the caller must take care because they + /// will not be permitted for actual json responses (json serialization will fail): the caller + /// is expected to do something like: + /// + /// std::string binary = some_binary_data(); + /// cmd.response["binary_value"] = is_bt ? binary : oxenmq::to_hex(binary); + /// + /// or, more conveniently, using the shortcut interface: + /// + /// cmd.response_hex["binary_value"] = some_binary_data(); + /// + nlohmann::json response; + + /// Proxy object that is used to set binary data in `response`, encoding it as hex if this + /// data is being returned as json. If this response is to be bt-encoded then the binary + /// value is left as-is (which isn't valid for json, but can be transported inside the json + /// value as we never dump() when going to bt-encoded). + /// + /// Usage: + /// std::string data = "abc"; + /// rpc.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" + json_binary_proxy response_hex{response, json_binary_proxy::fmt::hex}; + + /// Proxy object that encodes binary data as base64 for json, leaving it as binary for + /// bt-encoded responses. + /// + /// Usage: + /// std::string data = "abc"; + /// rpc.response_b64["foo"]["bar"] = data; // json: "YWJj", bt: "abc" + json_binary_proxy response_b64{response, json_binary_proxy::fmt::base64}; + }; + + /// Tag types that are used (via inheritance) to set rpc endpoint properties + + /// Specifies that the RPC call is public (i.e. available through restricted rpc). If this is + /// *not* inherited from then the command is restricted (i.e. only available to admins). For LMQ, + /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). + struct PUBLIC : virtual RPC_COMMAND {}; + + /// For Wallet RPC, specifies that the RPC call is restricted, meaning the user must authenticate + /// to the RPC listener by some means. + struct RESTRICTED : virtual RPC_COMMAND {}; + + /// Specifies that the RPC call takes no input arguments. (A dictionary of parameters may still + /// be passed, but will be ignored). + struct NO_ARGS : virtual RPC_COMMAND {}; + + /// Specifies a "legacy" JSON RPC command, available via HTTP JSON at /whatever (in addition to + /// json_rpc as "whatever"). When accessed via legacy mode the result is just the .result element + /// of the JSON RPC response. (Only applies to the HTTP RPC interface, and does nothing if BINARY + /// if specified). + struct LEGACY : virtual RPC_COMMAND {}; + +} // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/http_server_base.cpp b/src/rpc/common/http_server_base.cpp similarity index 100% rename from src/rpc/http_server_base.cpp rename to src/rpc/common/http_server_base.cpp diff --git a/src/rpc/http_server_base.h b/src/rpc/common/http_server_base.h similarity index 100% rename from src/rpc/http_server_base.h rename to src/rpc/common/http_server_base.h diff --git a/src/rpc/common/json_bt.cpp b/src/rpc/common/json_bt.cpp new file mode 100644 index 00000000000..faabc446862 --- /dev/null +++ b/src/rpc/common/json_bt.cpp @@ -0,0 +1,33 @@ +#include "json_bt.h" + +namespace beldex { + +oxenc::bt_value json_to_bt(json&& j) { + if (j.is_object()) { + oxenc::bt_dict res; + for (auto& [k, v] : j.items()) { + if (v.is_null()) + continue; // skip k-v pairs with a null v (for other nulls we fail). + res[k] = json_to_bt(std::move(v)); + } + return res; + } + if (j.is_array()) { + oxenc::bt_list res; + for (auto& v : j) + res.push_back(json_to_bt(std::move(v))); + return res; + } + if (j.is_string()) { + return std::move(j.get_ref()); + } + if (j.is_boolean()) + return j.get() ? 1 : 0; + if (j.is_number_unsigned()) + return j.get(); + if (j.is_number_integer()) + return j.get(); + throw std::domain_error{"internal error: encountered some unhandled/invalid type in json-to-bt translation"}; +} + +} // namespace beldex \ No newline at end of file diff --git a/src/rpc/common/json_bt.h b/src/rpc/common/json_bt.h new file mode 100644 index 00000000000..4e155d7c1bb --- /dev/null +++ b/src/rpc/common/json_bt.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +using nlohmann::json; + +namespace beldex { + +oxenc::bt_value json_to_bt(json&& j); + +} // namespace beldex \ No newline at end of file diff --git a/src/rpc/param_parser.hpp b/src/rpc/common/param_parser.hpp similarity index 100% rename from src/rpc/param_parser.hpp rename to src/rpc/common/param_parser.hpp diff --git a/src/rpc/rpc_args.cpp b/src/rpc/common/rpc_args.cpp similarity index 100% rename from src/rpc/rpc_args.cpp rename to src/rpc/common/rpc_args.cpp diff --git a/src/rpc/rpc_args.h b/src/rpc/common/rpc_args.h similarity index 100% rename from src/rpc/rpc_args.h rename to src/rpc/common/rpc_args.h diff --git a/src/rpc/rpc_binary.cpp b/src/rpc/common/rpc_binary.cpp similarity index 100% rename from src/rpc/rpc_binary.cpp rename to src/rpc/common/rpc_binary.cpp diff --git a/src/rpc/rpc_binary.h b/src/rpc/common/rpc_binary.h similarity index 100% rename from src/rpc/rpc_binary.h rename to src/rpc/common/rpc_binary.h diff --git a/src/rpc/common/rpc_command.h b/src/rpc/common/rpc_command.h new file mode 100644 index 00000000000..de89413cc49 --- /dev/null +++ b/src/rpc/common/rpc_command.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include "json_bt.h" + + +namespace cryptonote::rpc { + +using nlohmann::json; +using beldex::json_to_bt; + +using rpc_input = std::variant; + +/// Exception when trying to invoke an RPC command that indicate a parameter parse failure (will +/// give an invalid params error for JSON-RPC, for example). +struct parse_error : std::runtime_error { using std::runtime_error::runtime_error; }; + +/// Exception used to signal various types of errors with a request back to the caller. This +/// exception indicates that the caller did something wrong: bad data, invalid value, etc., but +/// don't indicate a local problem (and so we'll log them only at debug). For more serious, +/// internal errors a command should throw some other stl error (e.g. std::runtime_error or +/// perhaps std::logic_error), which will result in a local daemon warning (and a generic internal +/// error response to the user). +/// +/// For JSON RPC these become an error response with the code as the error.code value and the +/// string as the error.message. +/// For HTTP JSON these become a 500 Internal Server Error response with the message as the body. +/// For OxenMQ the code becomes the first part of the response and the message becomes the +/// second part of the response. +struct rpc_error : std::runtime_error { + /// \param code - a signed, 16-bit numeric code. 0 must not be used (as it is used for a + /// success code in OxenMQ), and values in the -32xxx range are reserved by JSON-RPC. + /// + /// \param message - a message to send along with the error code (see general description above). + rpc_error(int16_t code, std::string message) + : std::runtime_error{"RPC error " + std::to_string(code) + ": " + message}, + code{code}, message{std::move(message)} {} + + int16_t code; + std::string message; +}; + +enum struct rpc_source : uint8_t { internal, http, omq }; + +/// Contains the context of the invocation, which must be filled out by the glue code (e.g. HTTP +/// RPC server) with requester-specific context details. +struct rpc_context { + // Specifies that the requestor has admin permissions (e.g. is on an unrestricted RPC port, or + // is a local internal request). This can be used to provide different results for an admin + // versus non-admin when invoking a public RPC command. (Note that non-public RPC commands do + // not need to check this field for authentication: a non-public invoke() is not called in the + // first place if attempted by a public requestor). + bool admin = false; + + // The RPC engine source of the request, i.e. internal, HTTP, OMQ + rpc_source source = rpc_source::internal; + + // A free-form identifier (meant for humans) identifiying the remote address of the request; + // this might be IP:PORT, or could contain a pubkey, or ... + std::string remote; +}; + +struct rpc_request { + // The request body: + // - for an HTTP, non-JSONRPC POST request the string or string_view will be populated with the + // unparsed request body. + // - for an HTTP JSONRPC request with a "params" value the nlohmann::json will be set to the + // parsed "params" value of the request. + // - for OMQ requests with a data part the string or string_view will be set to the provided value + // - for all other requests (i.e. JSONRPC with no params; HTTP GET requests; no-data OMQ + // requests) the variant will contain a std::monostate. + // + // If something goes wrong, throw. + std::variant body; + + // Returns a string_view of the body, if the body is a string or string_view. Returns + // std::nullopt if the body is empty (std::monostate) or parsed jsonrpc params. + std::optional body_view() const { + if (auto* sv = std::get_if(&body)) return *sv; + if (auto* s = std::get_if(&body)) return *s; + return std::nullopt; + } + + // Values to pass through to the invoke() call + rpc_context context; +}; + +// Note: to use, parse_request(RPC, rpc_input) must be defined for each typename RPC +// this is used on. +template +auto make_invoke() { + return [](rpc_request&& request, RPCServer& server) -> typename RPCCallback::result_type { + RPC rpc{}; + + try { + if (auto body = request.body_view()) { + if (body->front() == 'd') { // Looks like a bt dict + rpc.set_bt(); + parse_request(rpc, oxenc::bt_dict_consumer{*body}); + } + else + parse_request(rpc, json::parse(*body)); + } else if (auto* j = std::get_if(&request.body)) { + parse_request(rpc, std::move(*j)); + } else { + assert(std::holds_alternative(request.body)); + parse_request(rpc, std::monostate{}); + } + } catch (const std::exception& e) { + throw parse_error{"Failed to parse request parameters: "s + e.what()}; + } + + server.invoke(rpc, std::move(request.context)); + + if (rpc.response.is_null()) + rpc.response = json::object(); + + if (rpc.is_bt()) + return json_to_bt(std::move(rpc.response)); + else + return std::move(rpc.response); + }; +} + +} // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/common/rpc_version.h b/src/rpc/common/rpc_version.h new file mode 100644 index 00000000000..5fa296dc825 --- /dev/null +++ b/src/rpc/common/rpc_version.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace cryptonote::rpc { + + using version_t = std::pair; + + /// Makes a version array from a packed 32-bit integer version + constexpr version_t make_version(uint32_t version) + { + return {static_cast(version >> 16), static_cast(version & 0xffff)}; + } + /// Packs a version array into a packed 32-bit integer version + constexpr uint32_t pack_version(version_t version) + { + return (uint32_t(version.first) << 16) | version.second; + } + +} // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a1938bcc3c6..7a7da70a665 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -53,7 +53,9 @@ #include "core_rpc_server_binary_commands.h" #include "core_rpc_server_command_parser.h" #include "core_rpc_server_error_codes.h" -#include "rpc_args.h" +#include "rpc/common/rpc_args.h" +#include "rpc/common/json_bt.h" +#include "rpc/common/rpc_command.h" #include "common/command_line.h" #include "common/beldex.h" #include "common/sha256sum.h" @@ -77,35 +79,8 @@ namespace cryptonote::rpc { using nlohmann::json; + using beldex::json_to_bt; namespace { - - oxenc::bt_value json_to_bt(json&& j) { - if (j.is_object()) { - oxenc::bt_dict res; - for (auto& [k, v] : j.items()) { - if (v.is_null()) - continue; // skip k-v pairs with a null v (for other nulls we fail). - res[k] = json_to_bt(std::move(v)); - } - return res; - } - if (j.is_array()) { - oxenc::bt_list res; - for (auto& v : j) - res.push_back(json_to_bt(std::move(v))); - return res; - } - if (j.is_string()) { - return std::move(j.get_ref()); - } - if (j.is_boolean()) - return j.get() ? 1 : 0; - if (j.is_number_unsigned()) - return j.get(); - if (j.is_number_integer()) - return j.get(); - throw std::domain_error{"internal error: encountered some unhandled/invalid type in json-to-bt translation"}; - } template void register_rpc_command(std::unordered_map>& regs) @@ -118,36 +93,7 @@ namespace cryptonote::rpc { // Temporary: remove once RPC conversion is complete static_assert(!FIXME_has_nested_response_v); - cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { - RPC rpc{}; - try { - if (auto body = request.body_view()) { - if (body->front() == 'd') { // Looks like a bt dict - rpc.set_bt(); - parse_request(rpc, oxenc::bt_dict_consumer{*body}); - } - else - parse_request(rpc, json::parse(*body)); - } else if (auto* j = std::get_if(&request.body)) { - parse_request(rpc, std::move(*j)); - } else { - assert(std::holds_alternative(request.body)); - parse_request(rpc, std::monostate{}); - } - } catch (const std::exception& e) { - throw parse_error{"Failed to parse request parameters: "s + e.what()}; - } - - server.invoke(rpc, std::move(request.context)); - - if (rpc.response.is_null()) - rpc.response = json::object(); - - if (rpc.is_bt()) - return json_to_bt(std::move(rpc.response)); - else - return std::move(rpc.response); - }; + cmd->invoke = make_invoke(); for (const auto& name : RPC::names()) regs.emplace(name, cmd); @@ -201,23 +147,18 @@ namespace cryptonote::rpc { const std::unordered_map> rpc_commands = register_rpc_commands(rpc::core_rpc_types{}, rpc::core_rpc_binary_types{}); - // const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { - // "bootstrap-daemon-address" - // , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." - // , "" - // }; + /*const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { + "bootstrap-daemon-address" + , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." + , "" + }; - // const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_login = { - // "bootstrap-daemon-login" - // , "Specify username:password for the bootstrap daemon login" - // , "" - // }; + const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_login = { + "bootstrap-daemon-login" + , "Specify username:password for the bootstrap daemon login" + , "" + };*/ - std::optional rpc_request::body_view() const { - if (auto* sv = std::get_if(&body)) return *sv; - if (auto* s = std::get_if(&body)) return *s; - return std::nullopt; - } //----------------------------------------------------------------------------------- void core_rpc_server::init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden) @@ -1593,10 +1534,10 @@ namespace cryptonote::rpc { response.tx_hashes.push_back(tools::type_to_hex(tx_hash)); } } -/* + /// All the common (untemplated) code for use_bootstrap_daemon_if_necessary. Returns a held lock /// if we need to bootstrap, an unheld one if we don't. - std::unique_lock core_rpc_server::should_bootstrap_lock() + /*std::unique_lock core_rpc_server::should_bootstrap_lock() { // TODO - support bootstrapping via a remote LMQ RPC; requires some argument fiddling @@ -2676,9 +2617,8 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD& get_master_node_registration_cmd, rpc_context context) + void core_rpc_server::invoke(GET_MASTER_NODE_REGISTRATION_CMD& get_master_node_registration_cmd, rpc_context context) { - PERF_TIMER(on_get_master_node_registration_cmd); if (!m_core.master_node()) @@ -2708,7 +2648,7 @@ namespace cryptonote::rpc { throw std::runtime_error("Mismatch in sizes of addresses and amounts"); } - for (size_t i = 0; i < addresses.size(); ++i) { + for (size_t i = 0; i < addresses.size(); ++i) { uint64_t num_portions = master_nodes::get_portions_to_make_amount(staking_requirement, amounts[i]); args.push_back(addresses[i]); @@ -2726,6 +2666,7 @@ namespace cryptonote::rpc { res.registration_cmd = req_old.response["registration_cmd"]; return res; } + //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES& get_master_node_blacklisted_key_images, rpc_context context) { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 88b967cf2a1..592894154a9 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -42,6 +42,7 @@ #include "core_rpc_server_binary_commands.h" #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "rpc/common/rpc_command.h" #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) #include "common/beldex_integration_test_hooks.h" @@ -67,80 +68,6 @@ namespace cryptonote::rpc { struct FIXME_has_nested_response> : std::true_type {}; template constexpr bool FIXME_has_nested_response_v = FIXME_has_nested_response::value; - /// Exception when trying to invoke an RPC command that indicate a parameter parse failure (will - /// give an invalid params error for JSON-RPC, for example). - struct parse_error : std::runtime_error { using std::runtime_error::runtime_error; }; - - /// Exception used to signal various types of errors with a request back to the caller. This - /// exception indicates that the caller did something wrong: bad data, invalid value, etc., but - /// don't indicate a local problem (and so we'll log them only at debug). For more serious, - /// internal errors a command should throw some other stl error (e.g. std::runtime_error or - /// perhaps std::logic_error), which will result in a local daemon warning (and a generic internal - /// error response to the user). - /// - /// For JSON RPC these become an error response with the code as the error.code value and the - /// string as the error.message. - /// For HTTP JSON these become a 500 Internal Server Error response with the message as the body. - /// For OxenMQ the code becomes the first part of the response and the message becomes the - /// second part of the response. - struct rpc_error : std::runtime_error { - /// \param code - a signed, 16-bit numeric code. 0 must not be used (as it is used for a - /// success code in OxenMQ), and values in the -32xxx range are reserved by JSON-RPC. - /// - /// \param message - a message to send along with the error code (see general description above). - rpc_error(int16_t code, std::string message) - : std::runtime_error{"RPC error " + std::to_string(code) + ": " + message}, - code{code}, message{std::move(message)} {} - - int16_t code; - std::string message; - }; - - /// FIXME: kill this. - /// Junk that epee makes us deal with to pass in a generically parsed json value - using jsonrpc_params = std::pair; - - enum struct rpc_source : uint8_t { internal, http, omq }; - - /// Contains the context of the invocation, which must be filled out by the glue code (e.g. HTTP - /// RPC server) with requester-specific context details. - struct rpc_context { - // Specifies that the requestor has admin permissions (e.g. is on an unrestricted RPC port, or - // is a local internal request). This can be used to provide different results for an admin - // versus non-admin when invoking a public RPC command. (Note that non-public RPC commands do - // not need to check this field for authentication: a non-public invoke() is not called in the - // first place if attempted by a public requestor). - bool admin = false; - - // The RPC engine source of the request, i.e. internal, HTTP, OMQ - rpc_source source = rpc_source::internal; - - // A free-form identifier (meant for humans) identifiying the remote address of the request; - // this might be IP:PORT, or could contain a pubkey, or ... - std::string remote; - }; - - struct rpc_request { - // The request body: - // - for an HTTP, non-JSONRPC POST request the string or string_view will be populated with the - // unparsed request body. - // - for an HTTP JSONRPC request with a "params" value the nlohmann::json will be set to the - // parsed "params" value of the request. - // - for OMQ requests with a data part the string or string_view will be set to the provided value - // - for all other requests (i.e. JSONRPC with no params; HTTP GET requests; no-data OMQ - // requests) the variant will contain a std::monostate. - // - // If something goes wrong, throw. - std::variant body; - - // Returns a string_view of the body, if the body is a string or string_view. Returns - // std::nullopt if the body is empty (std::monostate) or parsed jsonrpc params. - std::optional body_view() const; - - // Values to pass through to the invoke() call - rpc_context context; - }; - class core_rpc_server; /// Stores an RPC command callback. These are set up in core_rpc_server.cpp. diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 142624fd36b..f853862a7ac 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -1,5 +1,5 @@ #include "core_rpc_server_command_parser.h" -#include "param_parser.hpp" +#include "rpc/common/param_parser.hpp" namespace cryptonote::rpc { diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 2022ae28736..3957f53ae6b 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -183,9 +183,9 @@ void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) }; } -KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(STATUS) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(EMPTY) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e98212b13bd..658df6af082 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -44,6 +44,10 @@ // setlocal comments+=:/// // setlocal comments+=:// +#include "rpc/common/rpc_version.h" +#include "rpc/common/rpc_binary.h" +#include "rpc/common/command_decorators.h" + #include "crypto/crypto.h" #include "epee/string_tools.h" @@ -66,7 +70,6 @@ #include "cryptonote_core/master_node_list.h" #include "common/beldex.h" -#include "rpc_binary.h" #include #include #include @@ -85,27 +88,14 @@ namespace master_nodes { /// access, and data type), and added to `core_rpc_types` list at the bottom of the file. namespace cryptonote::rpc { - using version_t = std::pair; - -// When making *any* change here, bump minor -// If the change is incompatible, then bump major and set minor to 0 -// This ensures rpc::VERSION always increases, that every change -// has its own version, and that clients can just test major to see -// whether they can talk to a given daemon without having to know in -// advance which version they will stop working with + // When making *any* change here, bump minor + // If the change is incompatible, then bump major and set minor to 0 + // This ensures rpc::VERSION always increases, that every change + // has its own version, and that clients can just test major to see + // whether they can talk to a given daemon without having to know in + // advance which version they will stop working with constexpr version_t VERSION = {4, 1}; - /// Makes a version array from a packed 32-bit integer version - constexpr version_t make_version(uint32_t version) - { - return {static_cast(version >> 16), static_cast(version & 0xffff)}; - } - /// Packs a version array into a packed 32-bit integer version - constexpr uint32_t pack_version(version_t version) - { - return (uint32_t(version.first) << 16) | version.second; - } - const static std::string STATUS_OK = "OK", STATUS_FAILED = "FAILED", @@ -113,100 +103,9 @@ namespace cryptonote::rpc { STATUS_NOT_MINING = "NOT MINING", STATUS_TX_LONG_POLL_TIMED_OUT = "Long polling client timed out before txpool had an update"; - - namespace { - /// Returns a constexpr std::array of string_views from an arbitrary list of string literals - /// Used to specify RPC names as: - /// static constexpr auto names() { return NAMES("primary_name", "some_alias"); } - template - constexpr std::array NAMES(const char (&...names)[N]) { - static_assert(sizeof...(N) > 0, "RPC command must have at least one name"); - return {std::string_view{names, N-1}...}; - } - } - - /// Base class that all RPC commands must inherit from (either directly or via one or more of the - /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC - /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be - /// at `whatever`. This base class is also where response objects are stored. - struct RPC_COMMAND { - private: - bool bt = false; - public: - /// Indicates whether this response is to be bt (true) or json (false) encoded. Do not set. - bool is_bt() const { return bt; } - - /// Called early in the request to indicate that this request is a bt-encoded one. - void set_bt(); - - /// The response data. For bt-encoded responses we convert this on the fly, with the - /// following notes: - /// - boolean values become 0 or 1 - /// - key-value pairs with null values are omitted from the object - /// - other null values are not permitted at all: an exception will be raised if the json - /// contains such a value. - /// - double values are not permitted; if a double is absolutely needed then check `is_bt` - /// and, when bt, encode it in some documented, endpoint-specific way. - /// - binary values in strings *are* permitted, but the caller must take care because they - /// will not be permitted for actual json responses (json serialization will fail): the caller - /// is expected to do something like: - /// - /// std::string binary = some_binary_data(); - /// cmd.response["binary_value"] = is_bt ? binary : oxenc::to_hex(binary); - /// - /// or, more conveniently, using the shortcut interface: - /// - /// cmd.response_hex["binary_value"] = some_binary_data(); - nlohmann::json response; - - /// Proxy object that is used to set binary data in `response`, encoding it as hex if this - /// data is being returned as json. If this response is to be bt-encoded then the binary - /// value is left as-is (which isn't valid for json, but can be transported inside the json - /// value as we never dump() when going to bt-encoded). - /// - /// Usage: - /// std::string data = "abc"; - /// rpc.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" - json_binary_proxy response_hex{response, json_binary_proxy::fmt::hex}; - - /// Proxy object that encodes binary data as base64 for json, leaving it as binary for - /// bt-encoded responses. - /// - /// Usage: - /// std::string data = "abc"; - /// rpc.response_b64["foo"]["bar"] = data; // json: "YWJj", bt: "abc" - json_binary_proxy response_b64{response, json_binary_proxy::fmt::base64}; - }; - - /// Tag types that are used (via inheritance) to set rpc endpoint properties - - /// Specifies that the RPC call is public (i.e. available through restricted rpc). If this is - /// *not* inherited from then the command is restricted (i.e. only available to admins). For LMQ, - /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). - struct PUBLIC : virtual RPC_COMMAND {}; - - /// Specifies that the RPC call takes no input arguments. (A dictionary of parameters may still - /// be passed, but will be ignored). - struct NO_ARGS : virtual RPC_COMMAND {}; - - /// Specifies a "legacy" JSON RPC command, available via HTTP JSON at /whatever (in addition to - /// json_rpc as "whatever"). When accessed via legacy mode the result is just the .result element - /// of the JSON RPC response. (Only applies to the HTTP RPC interface, and does nothing if BINARY - /// if specified). - struct LEGACY : virtual RPC_COMMAND {}; - - - /// (Not a tag). Generic, serializable, no-argument request type, use as `struct request : EMPTY {};` + /// Generic, serializable, no-argument request or response type, use as + /// `struct request : EMPTY {};` or `using response = EMPTY;` struct EMPTY { KV_MAP_SERIALIZABLE }; - - /// (Not a tag). Generic response which contains only a status string; use as `struct response : STATUS {};` - struct STATUS - { - std::string status; // General RPC error code. "OK" means everything looks good. - - KV_MAP_SERIALIZABLE - }; - /// Get the node's current height. /// diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index 7beaeb7bfc4..fca49ac5e62 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -10,7 +10,7 @@ #include "cryptonote_core/cryptonote_core.h" #include "epee/net/jsonrpc_structs.h" #include "rpc/core_rpc_server_commands_defs.h" -#include "rpc/rpc_args.h" +#include "rpc/common/rpc_args.h" #include "version.h" #undef BELDEX_DEFAULT_LOG_CATEGORY @@ -169,6 +169,7 @@ namespace cryptonote::rpc { error_response(*res, HTTP_FORBIDDEN); }; + //note: rpc_commands is a pseudo-global in core_rpc_server.h for (auto& [name, call] : rpc_commands) { if (call->is_legacy || call->is_binary) { if (!call->is_public && m_restricted) diff --git a/src/rpc/http_server.h b/src/rpc/http_server.h index 23753a268ff..9fed69d81d0 100755 --- a/src/rpc/http_server.h +++ b/src/rpc/http_server.h @@ -34,8 +34,8 @@ #include "common/command_line.h" #include "common/password.h" #include "core_rpc_server.h" -#include "http_server_base.h" -#include "rpc/rpc_args.h" +#include "rpc/common/http_server_base.h" +#include "rpc/common/rpc_args.h" namespace cryptonote::rpc { diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index 2c77a88648e..1947eb35b3d 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -1,6 +1,6 @@ #include "lmq_server.h" -#include "param_parser.hpp" +#include "rpc/common/param_parser.hpp" #include "cryptonote_config.h" #include "oxenmq/oxenmq.h" #include "oxenc/bt.h" diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index f13b57eb5c9..11810cbe333 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -51,7 +51,7 @@ #include "epee/wipeable_string.h" #include "crypto/hash.h" #include "mnemonics/electrum-words.h" -#include "rpc/rpc_args.h" +#include "rpc/common/rpc_args.h" #include "rpc/core_rpc_server_commands_defs.h" #include "daemonizer/daemonizer.h" #include "cryptonote_core/beldex_name_system.h" diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 42ee4b69080..700c302c065 100755 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -42,7 +42,7 @@ #include "common/periodic_task.h" #include "wallet_rpc_server_commands_defs.h" #include "wallet2.h" -#include "rpc/http_server_base.h" +#include "rpc/common/http_server_base.h" #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "wallet.rpc" From f17beb9ace35abc0bb70e766cc7e34bff33f4ae9 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 29 Apr 2025 13:07:43 +0530 Subject: [PATCH 103/182] refactor: simplify logic, update datatype, and add error handling --- src/simplewallet/simplewallet.cpp | 10 +-- src/wallet/wallet2.cpp | 113 +++++++++++++++++------------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 531a2c5651f..78e524c9fcb 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6898,12 +6898,12 @@ bool simple_wallet::bns_update_mapping(std::vector args) if (backup_owner.size()) { - fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Backup Owner : {}\n"), response[0]["backup_owner"])); + fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Backup Owner : {}\n"), response[0].value("backup_owner", ""))); fmt::print(fmt::fg(fmt::color::light_green),fmt::format(tr("New Backup Owner : {}\n"), backup_owner)); } else { - fmt::print(fmt::format(tr("Backup Owner : {} (unchanged)\n"), response[0]["backup_owner"])); + fmt::print(fmt::format(tr("Backup Owner : {} (unchanged)\n"), response[0].value("backup_owner", ""))); } if (value_bchat.size() && (value_bchat == bchat.to_readable_value(m_wallet->nettype(), bns::mapping_type::bchat))) @@ -7205,11 +7205,11 @@ bool simple_wallet::bns_lookup(std::vector args) << "\n Value ethAddress : " << value_eth.to_readable_value(m_wallet->nettype(), bns::mapping_type::eth_addr); writer << "\n Owner : " << mapping["owner"]; - if (mapping.backup_owner) writer - << "\n Backup owner : " << *mapping["backup_owner"]; + if (mapping["backup_owner"]) writer + << "\n Backup owner : " << mapping["backup_owner"]; writer << "\n Last updated height : " << mapping["update_height"]; - if (mapping.expiration_height) writer + if (mapping["expiration_height"]) writer << "\n Expiration height : " << mapping["expiration_height"]; writer << "\n Encrypted bchat value : " << (enc_bchat_hex.empty() ? "(none)" :enc_bchat_hex); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 201e4ada896..bf760335066 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -220,31 +220,32 @@ namespace { else { std::ostringstream os; - if (res["tvc"]["m_verbose_error"].size()) os << res["tvc"]["m_verbose_error"].get() << "\n"; - - if (res["tvc"]["m_verifivation_failed"].get()) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection - if (res["tvc"]["m_verifivation_impossible"].get()) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain - if (!res["tvc"]["m_should_be_relayed"].get()) os << "TX should NOT be relayed, "; - if (res["tvc"]["m_added_to_pool"].get()) os << "TX added to pool, "; - if (res["tvc"]["m_low_mixin"].get()) os << "Insufficient mixin, "; - if (res["tvc"]["m_double_spend"].get()) os << "Double spend TX, "; - if (res["tvc"]["m_invalid_input"].get()) os << "Invalid inputs, "; - if (res["tvc"]["m_invalid_output"].get()) os << "Invalid outputs, "; - if (res["tvc"]["m_too_few_outputs"].get()) os << "Need at least 2 outputs, "; - if (res["tvc"]["m_too_big"].get()) os << "TX too big, "; - if (res["tvc"]["m_overspend"].get()) os << "Overspend, "; - if (res["tvc"]["m_fee_too_low"].get()) os << "Fee too low, "; - if (res["tvc"]["m_invalid_version"].get()) os << "TX has invalid version, "; - if (res["tvc"]["m_invalid_type"].get()) os << "TX has invalid type, "; - if (res["tvc"]["m_key_image_locked_by_mnode"].get()) os << "Key image is locked by master node, "; - if (res["tvc"]["m_key_image_blacklisted"].get()) os << "Key image is blacklisted on the master node network, "; - - if (res["tvc"]["m_vote_ctx"]["m_validator_index_out_of_bounds"].get()) os << "Validator index out of bounds"; - if (res["tvc"]["m_vote_ctx"]["m_signature_not_valid"].get()) os << "Signature not valid, "; - if (res["tvc"]["m_vote_ctx"]["m_added_to_pool"].get()) os << "Added to pool, "; - if (res["tvc"]["m_vote_ctx"]["m_not_enough_votes"].get()) os << "Not enough votes, "; - if (res["tvc"]["m_vote_ctx"]["m_incorrect_voting_group"].get()) os << "Incorrect voting group specified,"; - if (res["tvc"]["m_vote_ctx"]["m_votes_not_sorted"].get()) os << "Votes are not stored in ascending order"; + const auto tvc = res["tvc"]; + if (auto got = tvc.find("m_verbose_error"); got != tvc.end()) os << res["tvc"]["m_verbose_error"].get() << "\n"; + if (auto got = tvc.find("m_verifivation_failed"); got != tvc.end()) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection + if (auto got = tvc.find("m_verifivation_impossible"); got != tvc.end()) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain + if (auto got = tvc.find("m_should_be_relayed"); got == tvc.end()) os << "TX should NOT be relayed, "; + if (auto got = tvc.find("m_added_to_pool"); got != tvc.end()) os << "TX added to pool, "; + if (auto got = tvc.find("m_low_mixin"); got != tvc.end()) os << "Insufficient mixin, "; + if (auto got = tvc.find("m_double_spend"); got != tvc.end()) os << "Double spend TX, "; + if (auto got = tvc.find("m_invalid_input"); got != tvc.end()) os << "Invalid inputs, "; + if (auto got = tvc.find("m_invalid_output"); got != tvc.end()) os << "Invalid outputs, "; + if (auto got = tvc.find("m_too_few_outputs"); got != tvc.end()) os << "Need at least 2 outputs, "; + if (auto got = tvc.find("m_too_big"); got != tvc.end()) os << "TX too big, "; + if (auto got = tvc.find("m_overspend"); got != tvc.end()) os << "Overspend, "; + if (auto got = tvc.find("m_fee_too_low"); got != tvc.end()) os << "Fee too low, "; + if (auto got = tvc.find("m_invalid_version"); got != tvc.end()) os << "TX has invalid version, "; + if (auto got = tvc.find("m_invalid_type"); got != tvc.end()) os << "TX has invalid type, "; + if (auto got = tvc.find("m_key_image_locked_by_mnode"); got != tvc.end()) os << "Key image is locked by master node, "; + if (auto got = tvc.find("m_key_image_blacklisted"); got != tvc.end()) os << "Key image is blacklisted on the master node network, "; + + const auto m_vote_ctx = tvc["m_vote_ctx"]; + if (auto got = m_vote_ctx.find("m_validator_index_out_of_bounds"); got != m_vote_ctx.end()) os << "Validator index out of bounds"; + if (auto got = m_vote_ctx.find("m_signature_not_valid"); got != m_vote_ctx.end()) os << "Signature not valid, "; + if (auto got = m_vote_ctx.find("m_added_to_pool"); got != m_vote_ctx.end()) os << "Added to pool, "; + if (auto got = m_vote_ctx.find("m_not_enough_votes"); got != m_vote_ctx.end()) os << "Not enough votes, "; + if (auto got = m_vote_ctx.find("m_incorrect_voting_group"); got != m_vote_ctx.end()) os << "Incorrect voting group specified,"; + if (auto got = m_vote_ctx.find("m_votes_not_sorted"); got != m_vote_ctx.end()) os << "Votes are not stored in ascending order"; if (tx) os << "TX Version: " << tx->version << ", Type: " << tx->type; @@ -5645,9 +5646,13 @@ bool wallet2::check_connection(rpc::version_t *version, bool *ssl, bool throw_on if (!m_rpc_version) { - auto res = m_http_client.json_rpc("get_version", {}); - if(res["status"] != rpc::STATUS_OK) return false; - m_rpc_version = res["version"]; + try { + auto res = m_http_client.json_rpc("get_version", {}); + if(res["status"] != rpc::STATUS_OK) return false; + m_rpc_version = res["version"]; + } catch(...) { + return false; + } } if (version) *version = rpc::make_version(m_rpc_version); @@ -5860,18 +5865,25 @@ void wallet2::trim_hashchain() nlohmann::json req_params{ {"height", m_blockchain.size() - 1} }; - auto res = m_http_client.json_rpc("get_block_header_by_height", req_params); - if (res["status"] == rpc::STATUS_OK) - { - crypto::hash hash; - tools::hex_to_type(res["block_header"]["hash"].get(), hash); - m_blockchain.refill(hash); + try { + auto res = m_http_client.json_rpc("get_block_header_by_height", req_params); + if (res["status"] == rpc::STATUS_OK) + { + crypto::hash hash; + tools::hex_to_type(res["block_header"]["hash"].get(), hash); + m_blockchain.refill(hash); + } + else + { + MERROR("Failed to request block header from daemon, hash chain may be unable to sync till the wallet is loaded with a usable daemon"); + } } - else + catch (const std::exception &e) { - MERROR("Failed to request block header from daemon, hash chain may be unable to sync till the wallet is loaded with a usable daemon"); + MERROR("Failed to request block header from daemon when requesting get_block_header_by_height, hash chain may be unable to sync till the wallet is loaded with a usable daemon"); } } + if (height > 0 && m_blockchain.size() > height) { --height; @@ -7966,16 +7978,17 @@ bool wallet2::unset_ring(const crypto::hash &txid) if (!m_ringdb) return false; - nlohmann::json get_transactions_params{ - {"tx_hashes", tools::type_to_hex(txid)} - }; - auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); - + nlohmann::json get_transactions_params{ + {"tx_hashes", tools::type_to_hex(txid)} + }; cryptonote::transaction tx; - crypto::hash tx_hash; - if (!get_pruned_tx(res["txs"].front(), tx, tx_hash)) - return false; - THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + try { + auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); + crypto::hash tx_hash; + if (!get_pruned_tx(res["txs"].front(), tx, tx_hash)) + return false; + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + } catch (const std::exception &e) { return false; } try { return m_ringdb->remove_rings(get_ringdb_key(), tx); } catch (const std::exception &e) { return false; } @@ -8617,7 +8630,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg.reserve(1024); auto const &contribution = contributions[0]; - if (node_info["requested_unlock_height"] != 0) + if (node_info["requested_unlock_height"].get() != 0) { result.msg.append("Key image: "); result.msg.append(contribution["key_image"]); @@ -8848,17 +8861,17 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons cryptonote::address_parse_info curr_owner_parsed = {}; cryptonote::address_parse_info curr_backup_owner_parsed = {}; auto& rowner = (*response)["entries"].front()["owner"]; - auto& rbackup_owner = (*response)["entries"].front()["backup_owner"]; + std::string* rbackup_owner = (*response)["entries"].front().value("backup_owner", nullptr); bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner.get()); - bool curr_backup_owner = rbackup_owner && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), rbackup_owner.get()); + bool curr_backup_owner = rbackup_owner && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), *rbackup_owner); if (!try_generate_bns_signature(wallet, rowner, owner, backup_owner, result)) { - if (!rbackup_owner || !try_generate_bns_signature(wallet, rbackup_owner, owner, backup_owner, result)) + if (!rbackup_owner || !try_generate_bns_signature(wallet, *rbackup_owner, owner, backup_owner, result)) { if (reason) { *reason = "Signature requested when preparing ONS TX, but this wallet is not the owner of the record owner=" + rowner.get(); - if (rbackup_owner) *reason += ", backup_owner=" + rbackup_owner.get(); + if (rbackup_owner) *reason += ", backup_owner=" + *rbackup_owner; } return result; } From 7a2f1df2698d98350ebded8425a9ae0d179f4466 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 29 Apr 2025 13:16:39 +0530 Subject: [PATCH 104/182] add not-equal operator to rct::key --- src/ringct/rctTypes.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 95366aa01ab..aee1c2cea4f 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -79,7 +79,8 @@ namespace rct { unsigned char operator[](int i) const { return bytes[i]; } - bool operator==(const key &k) const { return !crypto_verify_32(bytes, k.bytes); } + bool operator==(const key& k) const { return !crypto_verify_32(bytes, k.bytes); } + bool operator!=(const key& k) const { return !operator==(k); } unsigned char bytes[32]; }; typedef std::vector keyV; //vector of keys From 096a44ae1065793a79e607ded80396033100e97e Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 29 Apr 2025 13:27:37 +0530 Subject: [PATCH 105/182] fix: throw error on empty tx input indices --- src/cryptonote_basic/cryptonote_format_utils.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 8c031c24ac1..9a17659ecee 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1498,9 +1498,11 @@ namespace cryptonote std::vector absolute_output_offsets_to_relative(const std::vector& off) { std::vector res = off; - if(!off.size()) - return res; - std::sort(res.begin(), res.end());//just to be sure, actually it is already should be sorted + CHECK_AND_ASSERT_THROW_MES(not off.empty(), "absolute index to relative offset, no indices provided"); + + // vector must be sorted before calling this, else an index' offset would be negative + CHECK_AND_ASSERT_THROW_MES(std::is_sorted(res.begin(), res.end()), "absolute index to relative offset, indices not sorted"); + for(size_t i = res.size()-1; i != 0; i--) res[i] -= res[i-1]; From 7bdddeb61788efbd41d671eddff5bc290ee042ba Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Tue, 29 Apr 2025 15:19:51 +0530 Subject: [PATCH 106/182] minor fixes of RPC: GET_OUTPUTS --- .../cryptonote_format_utils.h | 3 +- src/rpc/common/rpc_binary.h | 18 +++++++++++ src/rpc/core_rpc_server.cpp | 31 +++++++++++-------- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index b1b029bb0e3..a2b95f68a18 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -275,7 +275,8 @@ namespace cryptonote return true; } catch (const std::exception& e) { LOG_ERROR("Serialization of " << tools::type_name(typeid(T)) << " failed: " << e.what()); - return false; + // return false; + throw; } } //--------------------------------------------------------------- diff --git a/src/rpc/common/rpc_binary.h b/src/rpc/common/rpc_binary.h index 75da319cf19..f202a6bb6b7 100644 --- a/src/rpc/common/rpc_binary.h +++ b/src/rpc/common/rpc_binary.h @@ -95,6 +95,24 @@ namespace cryptonote::rpc { json_binary_proxy{a.emplace_back(), format} = val; return e = std::move(a); } + /// Emplaces a new nlohman::json to the end of an underlying list and returns a + /// json_binary_proxy wrapping it. + /// + /// Example: + /// + /// auto child = wrappedelem.emplace_back({"key1": 1}, {"key2": 2}); + /// child["binary-key"] = some_binary_thing; + template + json_binary_proxy emplace_back(Args&&... args) { + return json_binary_proxy{e.emplace_back(std::forward(args)...), format}; + } + + /// Adds an element to an underlying list, then copies or moves the given argument onto it via + /// json_binary_proxy assignment. + template + void push_back(T&& val) { + emplace_back() = std::forward(val); + } }; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7a7da70a665..03bf9d3c7f0 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -584,27 +584,32 @@ namespace cryptonote::rpc { return; } + auto binary_format = get_outputs.is_bt() ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex; + auto& outs = (get_outputs.response["outs"] = json::array()); if (!get_outputs.request.as_tuple) { for (auto& outkey : res_bin.outs) { - outs.push_back(json{ - {"key", std::move(outkey.key)}, - {"mask", std::move(outkey.mask)}, - {"unlocked", outkey.unlocked}, - {"height", outkey.height} - }); + json o; + json_binary_proxy b{o, binary_format}; + b["key"] = std::move(outkey.key); + b["mask"] = std::move(outkey.mask); + o["unlocked"] = outkey.unlocked; + o["height"] = outkey.height; if (get_outputs.request.get_txid) - outs.back()["txid"] = std::move(outkey.txid); + b["txid"] = std::move(outkey.txid); + outs.push_back(std::move(o)); } } else { for (auto& outkey : res_bin.outs) { - outs.push_back(json::array({ - std::move(outkey.key), - std::move(outkey.mask), - outkey.unlocked, - outkey.height})); + auto o = json::array(); + json_binary_proxy b{o, binary_format}; + b.push_back(std::move(outkey.key)); + b.push_back(std::move(outkey.mask)); + o.push_back(outkey.unlocked); + o.push_back(outkey.height); if (get_outputs.request.get_txid) - outs.back().push_back(std::move(outkey.txid)); + b.push_back(std::move(outkey.txid)); + outs.push_back(o); } } From 621ffcebe329489e9c359b7c36b1a262579b75ea Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 29 Apr 2025 15:22:45 +0530 Subject: [PATCH 107/182] small status/error message change --- src/rpc/common/http_server_base.cpp | 2 +- src/rpc/core_rpc_server.cpp | 18 +++++++++--------- src/rpc/core_rpc_server_commands_defs.h | 3 --- src/wallet/node_rpc_proxy.cpp | 3 +-- src/wallet/node_rpc_proxy.h | 1 + src/wallet/wallet2.cpp | 5 ++--- src/wallet/wallet2.h | 2 +- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/rpc/common/http_server_base.cpp b/src/rpc/common/http_server_base.cpp index 18e80026e27..d91c3531c2a 100755 --- a/src/rpc/common/http_server_base.cpp +++ b/src/rpc/common/http_server_base.cpp @@ -85,7 +85,7 @@ namespace cryptonote::rpc { if (m_closing) res.writeHeader("Connection", "close"); res.end(nlohmann::json{ {"jsonrpc", "2.0"}, - {"id", id.get 0 ? get_staking_requirement.request.height : m_core.get_current_blockchain_height(); - get_staking_requirement.response["staking_requirement"] = master_nodes::get_staking_requirement(nettype(), get_staking_requirement.request.height); + get_staking_requirement.response["staking_requirement"] = master_nodes::get_staking_requirement(get_staking_requirement.response["height"]); get_staking_requirement.response["status"] = STATUS_OK; return; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 658df6af082..1c2a9a1765d 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1370,7 +1370,6 @@ namespace cryptonote::rpc { std::vector txids; // List of transactions IDs to relay from pool. } request; - struct response : STATUS {}; }; /// Get node synchronisation information. This returns information on the node's syncing "spans" @@ -2277,8 +2276,6 @@ namespace cryptonote::rpc { bool bad_txs; // Clear the cache storing TXs that failed verification. bool bad_blocks; // Clear the cache storing blocks that failed verfication. } request; - - struct response : STATUS { }; }; /// List of all supported rpc command structs to allow compile-time enumeration of all supported diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 9c43741ffc2..7dcb2529533 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -184,7 +184,7 @@ std::optional NodeRPCProxy::get_hardfork_version() const try { auto res = m_http_client.json_rpc("hard_fork_info", {}); - return res["version"].get(); + return res["version"].get(); }catch (...) {} return std::nullopt; @@ -395,7 +395,6 @@ std::pair NodeRPCProxy::bns_names_to_owners(nlohmann::json } std::pair NodeRPCProxy::bns_resolve(nlohmann::json const& request) const -{ { std::pair result; auto& [success, resolved] = result; diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index f371988f1cf..96ef189ba29 100755 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -33,6 +33,7 @@ #include #include "rpc/http_client.h" #include "rpc/core_rpc_server_commands_defs.h" +#include namespace tools { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index bf760335066..fb3a44761e7 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -56,7 +56,6 @@ #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" -#include "epee/profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" #include "serialization/string.h" @@ -896,7 +895,7 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry if (entry["as_hex"] || (entry["prunable"] && entry["pruned"])) { if (entry["as_hex"]) { - CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["as_hex"]).get(), false, "Failed to parse tx data"); + CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["as_hex"].get()), false, "Failed to parse tx data"); bd = oxenc::from_hex(*entry.["as_hex"]); } else { CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()) && oxenc::is_hex(entry["prunable"].get()), false, "Failed to parse tx data"); @@ -8173,7 +8172,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ return result; } - const bool full = mnode_info["contributors"].size() >= MAX_NUMBER_OF_CONTRIBUTORS; + const bool full = mnode_info["contributors"].size() >= beldex::MAX_NUMBER_OF_CONTRIBUTORS; if (full && !is_preexisting_contributor) { result.status = stake_result_status::master_node_contributors_maxed; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 5c5918e9596..1f566f035ac 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -819,7 +819,7 @@ namespace tools auto get_master_nodes(std::vector const &pubkeys) const { return m_node_rpc_proxy.get_master_nodes(pubkeys); } auto get_master_node_blacklisted_key_images() const { return m_node_rpc_proxy.get_master_node_blacklisted_key_images(); } auto bns_owners_to_names(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_owners_to_names(request); } - auto bns_names_to_owners(cryptonote::rpc::BNS_NAMES_TO_OWNERS::request const &request) const { return m_node_rpc_proxy.bns_names_to_owners(request); } + auto bns_names_to_owners(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_names_to_owners(request); } auto resolve(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_resolve(request); } nlohmann::json list_current_stakes(); struct bns_detail From 64be9507302fc1095ad5edeb27e626f627eb5112 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 30 Apr 2025 10:43:15 +0530 Subject: [PATCH 108/182] rpc_common changes --- .../epee/storages/portable_storage_from_bin.h | 4 +- src/blockchain_db/CMakeLists.txt | 2 + src/cryptonote_basic/miner.cpp | 18 +----- src/cryptonote_basic/miner.h | 1 - src/daemon/rpc_command_executor.cpp | 55 ++++++++++++------- src/rpc/core_rpc_server.cpp | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 7 ++- src/rpc/core_rpc_server_commands_defs.h | 2 +- src/wallet/CMakeLists.txt | 4 +- src/wallet/wallet2.cpp | 32 +++++------ 10 files changed, 62 insertions(+), 65 deletions(-) diff --git a/contrib/epee/include/epee/storages/portable_storage_from_bin.h b/contrib/epee/include/epee/storages/portable_storage_from_bin.h index 26f39e663c2..72c43cd398a 100755 --- a/contrib/epee/include/epee/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/epee/storages/portable_storage_from_bin.h @@ -158,7 +158,7 @@ namespace epee case SERIALIZE_TYPE_TAG: return read_ae(); case SERIALIZE_TYPE_TAG
: return read_ae
(); //case SERIALIZE_TYPE_ARRAY: return read_ae(); // nested arrays not supported - default: CHECK_AND_ASSERT_THROW_MES(false, "unknown entry_type code = " << (int)type); + default: { CHECK_AND_ASSERT_THROW_MES(false, "unknown entry_type code = " << (int)type); return {}; } } } @@ -211,7 +211,7 @@ namespace epee case SERIALIZE_TYPE_TAG: return read_se(); case SERIALIZE_TYPE_TAG
: return read_se
(); //case SERIALIZE_TYPE_ARRAY: return read_se(); // nested arrays not supported - default: CHECK_AND_ASSERT_THROW_MES(false, "unknown entry_type code = " << (int)ent_type); + default: { CHECK_AND_ASSERT_THROW_MES(false, "unknown entry_type code = " << (int)ent_type); return {}; } } } inline diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index e833a139bfb..20d972d4dc4 100755 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -33,6 +33,8 @@ add_library(blockchain_db ) target_link_libraries(blockchain_db + PUBLIC + nlohmann_json::nlohmann_json PRIVATE common ringct diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index d4d1f3b56ac..2f5b3f29f9f 100755 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -56,6 +56,7 @@ namespace cryptonote namespace { + const command_line::arg_descriptor arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; const command_line::arg_descriptor arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; const command_line::arg_descriptor arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; } @@ -116,26 +117,12 @@ namespace cryptonote return true; }); - m_update_hashrate_interval.do_call([&](){ - update_hashrate(); - return true; - }); - return true; } //----------------------------------------------------------------------------------------------------- - void miner::update_hashrate() - { - std::unique_lock lock{m_hashrate_mutex}; - auto hashes = m_hashes.exchange(0); - using dseconds = std::chrono::duration; - if (m_last_hr_update && is_mining()) - m_current_hash_rate = hashes / dseconds{std::chrono::steady_clock::now() - *m_last_hr_update}.count(); - m_last_hr_update = std::chrono::steady_clock::now(); - } - //----------------------------------------------------------------------------------------------------- void miner::init_options(boost::program_options::options_description& desc) { + command_line::add_arg(desc, arg_extra_messages); command_line::add_arg(desc, arg_start_mining); command_line::add_arg(desc, arg_mining_threads); } @@ -212,7 +199,6 @@ namespace cryptonote double miner::get_speed() const { if (is_mining()) { - std::unique_lock lock{m_hashrate_mutex}; return m_current_hash_rate; } return 0.0; diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index 3d291d4eea8..90b3608cdd1 100755 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -87,7 +87,6 @@ namespace cryptonote private: bool worker_thread(uint32_t index, bool slow_mining = false); bool request_block_template(); - void update_hashrate(); struct miner_config { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b7718f0b635..310a66bd58b 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -149,7 +149,7 @@ namespace { << "long term weight: " << header.long_term_weight << "\n" << "num txes: " << header.num_txes << "\n" << "reward: " << cryptonote::print_money(header.reward) << "\n" - << "miner reward: " << cryptonote::print_money(header.miner_reward) << "\n" + << "coinbase_payouts: " << cryptonote::print_money(header.coinbase_payouts) << "\n" << "master node winner: " << header.master_node_winner << "\n" << "miner tx hash: " << header.miner_tx_hash; } @@ -532,7 +532,7 @@ bool rpc_command_executor::show_status() { // str << " was used"; // } - auto hf_version = hfinfo["version"].get(); + auto hf_version = hfinfo["version"].get(); if (hf_version < cryptonote::feature::POS && !has_mining_info) str << ", mining info unavailable"; if (has_mining_info && !mining_busy && mining_active) @@ -625,6 +625,19 @@ bool rpc_command_executor::mining_status() { return true; } +static const char *get_address_type_name(epee::net_utils::address_type address_type) +{ + switch (address_type) + { + default: + case epee::net_utils::address_type::invalid: return "invalid"; + case epee::net_utils::address_type::ipv4: return "IPv4"; + case epee::net_utils::address_type::ipv6: return "IPv6"; + case epee::net_utils::address_type::i2p: return "I2P"; + case epee::net_utils::address_type::tor: return "Tor"; + } +} + bool rpc_command_executor::print_connections() { auto maybe_conns = try_running([this] { return invoke(); }, "Failed to retrieve connection info"); if (!maybe_conns) @@ -645,7 +658,7 @@ bool rpc_command_executor::print_connections() { address += tools::int_to_string(info["port"].get()); tools::msg_writer() << fmt::format(row_fmt, address, - info["address_type"].get(), + get_address_type_name(info["address_type"].get()), info["peer_id"].get(), fmt::format("{}({}/{})", info["recv_count"].get(), tools::friendly_duration(1ms * info["recv_idle_ms"].get()), @@ -1967,13 +1980,13 @@ static uint64_t get_amount_to_make_portions(uint64_t amount, uint64_t portions) return resultlo; } -static uint64_t get_actual_amount(uint64_t amount, uint64_t portions) -{ - uint64_t lo, hi, resulthi, resultlo; - lo = mul128(amount, portions, &hi); - div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); - return resultlo; -} +// static uint64_t get_actual_amount(uint64_t amount, uint64_t portions) +// { +// uint64_t lo, hi, resulthi, resultlo; +// lo = mul128(amount, portions, &hi); +// div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); +// return resultlo; +// } bool rpc_command_executor::prepare_registration(bool force_registration) { @@ -2018,7 +2031,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) } uint64_t block_height = std::max(info["height"].get(), info["target_height"].get()); - uint8_t hf_version = hfinfo["version"].get(); + auto hf_version = hfinfo["version"].get(); #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) cryptonote::network_type const nettype = cryptonote::FAKECHAIN; #else @@ -2038,18 +2051,18 @@ bool rpc_command_executor::prepare_registration(bool force_registration) uint64_t const now = time(nullptr); auto const& header = *maybe_header; - if (now >= header.timestamp) + + const auto now = std::chrono::system_clock::now(); + const auto block_ts = std::chrono::system_clock::from_time_t(header.timestamp); + + if (now - block_ts >= 10min) { - uint64_t delta = now - header.timestamp; - if (delta > (60 * 60)) - { - tools::fail_msg_writer() << "The last block this Master Node knows about was at least " << get_human_time_ago(header.timestamp, now) - << "\nYour node is possibly desynced from the network or still syncing to the network." - << "\n\nRegistering this node may result in a deregistration due to being out of date with the network\n"; - } + tools::fail_msg_writer() << "The last block this Master Node knows about was at least " << get_human_time_ago(now - block_ts) + << "\nYour node is possibly desynced from the network or still syncing to the network." + << "\n\nRegistering this node may result in a deregistration due to being out of date with the network\n"; } - if (block_height >= header.height) + if (auto synced_height = header.height; block_height >= synced_height) { uint64_t delta = block_height - header.height; if (delta > 15) @@ -2066,7 +2079,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) // anything less than DUST will be added to operator stake const uint64_t DUST = beldex::MAX_NUMBER_OF_CONTRIBUTORS; - std::cout << "\n BELDEX MASTER NODE REGISTRATION "<< std::endl; + std::cout << "\n BELDEX MASTER NODE REGISTRATION "<< std::endl; //check std::cout << "Current staking requirement: " << cryptonote::print_money(staking_requirement) << " " << cryptonote::get_unit() << std::endl; enum struct register_step diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 79a2edb96af..84ef3d6f761 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1524,7 +1524,7 @@ namespace cryptonote::rpc { response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height); response.reward = get_block_reward(blk); if(blk.nonce != 0) - response.miner_reward = blk.miner_tx.vout[0].amount; + response.coinbase_payouts = blk.miner_tx.vout[0].amount; response.block_size = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height); response.num_txes = blk.tx_hashes.size(); if (fill_pow_hash) diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 3957f53ae6b..397b8b7a334 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -76,17 +76,18 @@ void to_json(nlohmann::json& j, const block_header_response& h) {"difficulty", h.difficulty}, {"cumulative_difficulty", h.cumulative_difficulty}, {"reward", h.reward}, - {"miner_reward", h.miner_reward}, + {"coinbase_payouts", h.coinbase_payouts}, {"block_size", h.block_size}, {"block_weight", h.block_weight}, {"num_txes", h.num_txes}, - {"pow_hash", h.pow_hash ? *h.pow_hash : nullptr}, {"long_term_weight", h.long_term_weight}, {"miner_tx_hash", h.miner_tx_hash}, {"miner_tx_hash", h.miner_tx_hash}, {"tx_hashes", h.tx_hashes}, {"master_node_winner", h.master_node_winner}, }; + if (h.pow_hash) + j["pow_hash"] = *h.pow_hash; }; void from_json(const nlohmann::json& j, block_header_response& h) @@ -103,7 +104,7 @@ void from_json(const nlohmann::json& j, block_header_response& h) j.at("difficulty").get_to(h.difficulty); j.at("cumulative_difficulty").get_to(h.cumulative_difficulty); j.at("reward").get_to(h.reward); - j.at("miner_reward").get_to(h.miner_reward); + j.at("coinbase_payouts").get_to(h.coinbase_payouts); j.at("block_size").get_to(h.block_size); j.at("block_weight").get_to(h.block_weight); j.at("num_txes").get_to(h.num_txes); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1c2a9a1765d..992ead1a0f4 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -639,7 +639,7 @@ namespace cryptonote::rpc { difficulty_type difficulty; // The strength of the Beldex network based on mining power. difficulty_type cumulative_difficulty; // The cumulative strength of the Beldex network based on mining power. uint64_t reward; // The amount of new generated in this block and rewarded to the miner, foundation and master Nodes. Note: 1 BELDEX = 1e9 atomic units. - uint64_t miner_reward; // The amount of new generated in this block and rewarded to the miner. Note: 1 BELDEX = 1e9 atomic units. + uint64_t coinbase_payouts; // The amount of BDX paid out in this block. Note: 1 BELDEX = 1e9 atomic units. uint64_t block_size; // The block size in bytes. uint64_t block_weight; // The block weight in bytes. uint64_t num_txes; // Number of transactions in the block, not counting the coinbase tx. diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 5a151f7c6f7..1460dc3fa33 100755 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -41,7 +41,7 @@ target_link_libraries(wallet PUBLIC multisig rpc_commands - rpc_server_base + rpc_common cryptonote_core mnemonics net @@ -64,7 +64,7 @@ beldex_add_executable(wallet_rpc_server "beldex-wallet-rpc" target_link_libraries(wallet_rpc_server PRIVATE - rpc_server_base + rpc_common wallet daemonizer Boost::program_options diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index fb3a44761e7..899825da633 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -894,15 +894,14 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry // easy case if we have the whole tx if (entry["as_hex"] || (entry["prunable"] && entry["pruned"])) { - if (entry["as_hex"]) { - CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["as_hex"].get()), false, "Failed to parse tx data"); - bd = oxenc::from_hex(*entry.["as_hex"]); - } else { - CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()) && oxenc::is_hex(entry["prunable"].get()), false, "Failed to parse tx data"); - bd.reserve(oxenc::from_hex_size(entry["pruned"]->size() + entry["prunable"]->size())); - oxenc::from_hex(entry["pruned"]->begin(), entry["pruned"]->end(), std::back_inserter(bd)); - oxenc::from_hex(entry["prunable"]->begin(), entry["prunable"]->end(), std::back_inserter(bd)); - } + std::string hex_blob; + if (entry["as_hex"]) + hex_blob = entry["as_hex"].get(); + else + hex_blob = entry["pruned"].get() + entry["prunable"].get(); + + CHECK_AND_ASSERT_MES(oxenc::is_hex(hex_blob), false, "Invalid tx data"); + bd = oxenc::from_hex(hex_blob); CHECK_AND_ASSERT_MES(cryptonote::parse_and_validate_tx_from_blob(bd, tx), false, "Invalid tx data"); tx_hash = cryptonote::get_transaction_hash(tx); // if the hash was given, check it matches @@ -915,8 +914,8 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry { crypto::hash ph; CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["prunable_hash"].get(), ph), false, "Failed to parse prunable hash"); - CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()), false, "Failed to parse pruned data"); - bd = oxenc::from_hex(*entry.pruned_as_hex); + CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()), false, "Invalid pruned tx entry"); + bd = oxenc::from_hex(entry["pruned"].get()); CHECK_AND_ASSERT_MES(parse_and_validate_tx_base_from_blob(bd, tx), false, "Invalid base tx data"); // only v2 txes can calculate their txid after pruned if (bd[0] > 1) @@ -8658,7 +8657,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; return result; } - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(nettype(), node_info["registration_height"], curr_height); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(nettype(), node_info["registration_height"].get(), curr_height); result.msg.append("You will continue receiving rewards until the master node expires at the estimated height: "); result.msg.append(std::to_string(unlock_height)); result.msg.append(" (about "); @@ -12376,12 +12375,9 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s crypto::hash tx_prefix_hash{}; const auto& res_tx = res["txs"].front(); - THROW_WALLET_EXCEPTION_IF(!oxenc::is_hex(*res_tx.pruned_as_hex) || !oxenc::is_hex(res_tx.prunable_as_hex.value_or(""s)),error::wallet_internal_error, "Failed to parse transaction from daemon"); - std::string tx_data; - tx_data.reserve(oxenc::from_hex_size(res_tx.pruned_as_hex->size() + (res_tx.prunable_as_hex ? res_tx.prunable_as_hex->size() : 0))); - oxenc::from_hex(res_tx.pruned_as_hex->begin(), res_tx.pruned_as_hex->end(), std::back_inserter(tx_data)); - if (res_tx.prunable_as_hex) - oxenc::from_hex(res_tx.prunable_as_hex->begin(), res_tx.prunable_as_hex->end(), std::back_inserter(tx_data)); + std::string tx_blob_hex = res_tx["pruned"].get() + (res_tx["prunable"] ? res_tx["prunable"].get() : ""s); + THROW_WALLET_EXCEPTION_IF(not oxenc::is_hex(tx_blob_hex), error::wallet_internal_error, "Failed to parse transaction from daemon"); + tx_data = oxenc::from_hex(tx_blob_hex); THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "Failed to validate transaction from daemon"); THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, From 0a70e2daba62bfbb337c1086c3896d1880f99945 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Wed, 30 Apr 2025 16:40:15 +0530 Subject: [PATCH 109/182] Fixes compilation errors --- CMakeLists.txt | 2 +- README.md | 2 +- .../keyvalue_serialization_overloads.h | 12 +++-- src/crypto/keccak.c | 6 +-- src/cryptonote_basic/difficulty.cpp | 54 ++----------------- src/cryptonote_core/blockchain.cpp | 23 ++++---- src/simplewallet/simplewallet.cpp | 22 +++++++- src/wallet/api/wallet.cpp | 20 +++---- src/wallet/node_rpc_proxy.cpp | 12 ++++- src/wallet/node_rpc_proxy.h | 10 ++-- src/wallet/wallet2.cpp | 6 +-- src/wallet/wallet2.h | 3 +- 12 files changed, 82 insertions(+), 90 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd48ca8ccad..30115cf92c2 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -857,7 +857,7 @@ if(BUILD_STATIC_DEPS) # sqlite3 target already set up else() add_library(sqlite3 INTERFACE) - pkg_check_modules(SQLITE3 REQUIRED sqlite3 IMPORTED_TARGET) + pkg_check_modules(SQLITE3 REQUIRED sqlite3>=3.24.0 IMPORTED_TARGET) message(STATUS "Found sqlite3 ${SQLITE3_VERSION}") target_link_libraries(sqlite3 INTERFACE PkgConfig::SQLITE3) endif() diff --git a/README.md b/README.md index 4c05fe2d3ac..1fb366e0f5a 100755 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ library archives (`.a`). | pkg-config | any | NO | `pkg-config` | `base-devel` | `pkgconf` | NO | | | Boost | 1.65 | NO | `libboost-all-dev`[2] | `boost` | `boost-devel` | NO | C++ libraries | | libzmq | 4.3.0 | YES | `libzmq3-dev` | `zeromq` | `zeromq-devel` | NO | ZeroMQ library | -| sqlite3 | ? | YES | `libsqlite3-dev` | `sqlite` | `sqlite-devel` | NO | Beldex Name System | +| sqlite3 | 3.24.0 | YES | `libsqlite3-dev` | `sqlite` | `sqlite-devel` | NO | Beldex Name System | | libsodium | 1.0.9 | YES | `libsodium-dev` | `libsodium` | `libsodium-devel` | NO | cryptography | | libcurl | 4.0 | NO | `curl` | `curl-devel` | NO | HTTP | RPC | | libuv (Win) | any | NO | (Windows only) | -- | -- | NO | RPC event loop | diff --git a/contrib/epee/include/epee/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/epee/serialization/keyvalue_serialization_overloads.h index 4660339a209..c4df26cf76c 100755 --- a/contrib/epee/include/epee/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/epee/serialization/keyvalue_serialization_overloads.h @@ -163,11 +163,13 @@ namespace epee { static_assert(Size > 0, "cannot deserialize empty std::array"); size_t next_i = 0; - for (auto [it, end] = stg.template converting_array_range(pname, parent_section); it != end; ++it) { - CHECK_AND_ASSERT_MES(next_i < array.size(), false, "too many values to deserialize into fixed size std::array"); - array[next_i++] = *it; - } - CHECK_AND_ASSERT_MES(next_i == array.size(), false, "not enough values to deserialize into fixed size std::array"); + try { + for (auto [it, end] = stg.template converting_array_range(pname, parent_section); it != end; ++it) { + CHECK_AND_ASSERT_MES(next_i < array.size(), false, "too many values to deserialize into fixed size std::array"); + array[next_i++] = *it; + } + CHECK_AND_ASSERT_MES(next_i == array.size(), false, "not enough values to deserialize into fixed size std::array"); + } catch (const std::out_of_range&) { return false; } return true; } //-------------------------------------------------------------------------------------------------------------------- diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index 3d7d8d0f280..17cfd406685 100755 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -110,7 +110,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) for (i = 0; i < rsizw; i++) { uint64_t ina; memcpy(&ina, in + i * 8, 8); - st[i] ^= swap64le(ina); + st[i] ^= SWAP64LE(ina); } keccakf(st, KECCAK_ROUNDS); } @@ -128,7 +128,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) temp[rsiz - 1] |= 0x80; for (i = 0; i < rsizw; i++) - st[i] ^= swap64le(((uint64_t *) temp)[i]); + st[i] ^= SWAP64LE(((uint64_t *) temp)[i]); keccakf(st, KECCAK_ROUNDS); @@ -151,7 +151,7 @@ void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) #define IS_ALIGNED_64(p) (0 == (7 & ((const char*)(p) - (const char*)0))) #define KECCAK_PROCESS_BLOCK(st, block) { \ for (int i_ = 0; i_ < KECCAK_WORDS; i_++){ \ - ((st))[i_] ^= swap64le(((block))[i_]); \ + ((st))[i_] ^= SWAP64LE(((block))[i_]); \ }; \ keccakf(st, KECCAK_ROUNDS); } diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 0eeb3986a2d..552ea6ac772 100755 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -47,52 +47,6 @@ namespace cryptonote { using std::uint64_t; using std::vector; -#if defined(__x86_64__) - static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { - low = mul128(a, b, &high); - } - -#else - - static inline void mul(uint64_t a, uint64_t b, uint64_t &low, uint64_t &high) { - // __int128 isn't part of the standard, so the previous function wasn't portable. mul128() in Windows is fine, - // but this portable function should be used elsewhere. Credit for this function goes to latexi95. - - uint64_t aLow = a & 0xFFFFFFFF; - uint64_t aHigh = a >> 32; - uint64_t bLow = b & 0xFFFFFFFF; - uint64_t bHigh = b >> 32; - - uint64_t res = aLow * bLow; - uint64_t lowRes1 = res & 0xFFFFFFFF; - uint64_t carry = res >> 32; - - res = aHigh * bLow + carry; - uint64_t highResHigh1 = res >> 32; - uint64_t highResLow1 = res & 0xFFFFFFFF; - - res = aLow * bHigh; - uint64_t lowRes2 = res & 0xFFFFFFFF; - carry = res >> 32; - - res = aHigh * bHigh + carry; - uint64_t highResHigh2 = res >> 32; - uint64_t highResLow2 = res & 0xFFFFFFFF; - - //Addition - - uint64_t r = highResLow1 + lowRes2; - carry = r >> 32; - low = (r << 32) | lowRes1; - r = highResHigh1 + highResLow2 + carry; - uint64_t d3 = r & 0xFFFFFFFF; - carry = r >> 32; - r = highResHigh2 + carry; - high = d3 | (r << 32); - } - -#endif - static inline bool cadd(uint64_t a, uint64_t b) { return a + b < a; } @@ -104,15 +58,15 @@ namespace cryptonote { bool check_hash(const crypto::hash &hash, difficulty_type difficulty) { uint64_t low, high, top, cur; // First check the highest word, this will most likely fail for a random hash. - mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high); + top = mul128(SWAP64LE(((const uint64_t *) &hash)[3]), difficulty, &high); if (high != 0) { return false; } - mul(swap64le(((const uint64_t *) &hash)[0]), difficulty, low, cur); - mul(swap64le(((const uint64_t *) &hash)[1]), difficulty, low, high); + low = mul128(SWAP64LE(((const uint64_t *) &hash)[0]), difficulty, &cur); + low = mul128(SWAP64LE(((const uint64_t *) &hash)[1]), difficulty, &high); bool carry = cadd(cur, low); cur = high; - mul(swap64le(((const uint64_t *) &hash)[2]), difficulty, low, high); + low = mul128(SWAP64LE(((const uint64_t *) &hash)[2]), difficulty, &high); carry = cadc(cur, low, carry); carry = cadc(high, top, carry); return !carry; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9fab0c1eafa..0abb1cc303f 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "common/rules.h" #include "common/hex.h" @@ -302,7 +303,7 @@ uint64_t Blockchain::get_current_blockchain_height(bool lock) const bool Blockchain::load_missing_blocks_into_beldex_subsystems() { uint64_t const mnl_height = std::max(hard_fork_begins(m_nettype, hf::hf9_master_nodes).value_or(0), m_master_node_list.height() + 1); - uint64_t const bns_height = std::max(hard_fork_begins(m_nettype, hf::hf18_bns).value_or(0), m_bns_db.height() + 1); + uint64_t const bns_height = std::max(hard_fork_begins(m_nettype, hf::hf18_bns).value_or(0), m_bns_db.height() + 1); uint64_t const end_height = m_db->height(); uint64_t const start_height = std::min(end_height, std::min(bns_height, mnl_height)); @@ -312,22 +313,26 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() MGINFO("Loading blocks into beldex subsystems, scanning blockchain from height: " << start_height << " to: " << end_height << " (mnl: " << mnl_height << ", bns: " << bns_height << ")"); using clock = std::chrono::steady_clock; - using work_time = std::chrono::duration; - constexpr int64_t BLOCK_COUNT = 1000; + using dseconds = std::chrono::duration; + int64_t constexpr BLOCK_COUNT = 1000; auto work_start = clock::now(); auto scan_start = work_start; - work_time bns_duration{}, mnl_duration{}, bns_iteration_duration{}, mnl_iteration_duration{}; + dseconds bns_duration{}, mnl_duration{}, bns_iteration_duration{}, mnl_iteration_duration{}; for (int64_t block_count = total_blocks, index = 0; block_count > 0; block_count -= BLOCK_COUNT, index++) { - if (index > 0 && (index % 10 == 0)) + auto duration = dseconds{clock::now() - work_start}; + if (duration >= 10s) { m_master_node_list.store(); - auto duration = work_time{clock::now() - work_start}; - MGINFO("... scanning height " << start_height + (index * BLOCK_COUNT) << " (" << duration.count() << "s) (mnl: " << mnl_iteration_duration.count() << "s; bns: " << bns_iteration_duration.count() << "s)"); + MGINFO(fmt::format("... scanning height {} ({:.3f}s) (mnl: {:.3f}s, bns: {:.3f}s)", + start_height + (index * BLOCK_COUNT), + duration.count(), + mnl_iteration_duration.count(), + bns_iteration_duration.count())); #ifdef ENABLE_SYSTEMD // Tell systemd that we're doing something so that it should let us continue starting up // (giving us 120s until we have to send the next notification): @@ -392,8 +397,8 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() if (total_blocks > 1) { - auto duration = work_time{clock::now() - scan_start}; - MGINFO("Done recalculating beldex subsystems (" << duration.count() << "s) (mnl: " << mnl_duration.count() << "s; bns: " << bns_duration.count() << "s)"); + MGINFO(fmt::format("Done recalculating beldex subsystems in {:.2f}s ({:.2f}s mnl; {:.2f}s bns)", + dseconds{clock::now() - scan_start}.count(), mnl_duration.count(), bns_duration.count())); } if (total_blocks > 0) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 78e524c9fcb..13271c90df7 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -37,6 +37,7 @@ #include "common/string_util.h" #include "beldex_economy.h" +#include #include #ifdef _WIN32 #define __STDC_FORMAT_MACROS // NOTE(beldex): Explicitly define the PRIu64 macro on Mingw @@ -5083,6 +5084,22 @@ bool simple_wallet::show_balance_unlocked(bool detailed) << tr("unlocked balance: ") << print_money(unlocked_balance) << unlock_time_message << extra; std::map balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account, false); std::map>> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account, false); + + if (m_current_subaddress_account == 0) { // Only the primary account can stake and earn rewards, currently + if (auto stakes = m_wallet->get_staked_master_nodes(); !stakes.empty()) { + auto my_addr = m_wallet->get_address_as_str(); + uint64_t total_staked = 0, stakes_unlocking = 0; + for (auto& stake : stakes) + for (auto& contr : stake["contributors"]) + if (contr["address"].get() == my_addr) + { + total_staked += contr["amount"].get(); + if (stake["requested_unlock_height"].get() > 0) + stakes_unlocking += contr["amount"].get(); + } + success_msg_writer() << fmt::format(tr("Total staked: {}, {} unlocking"), print_money(total_staked), print_money(stakes_unlocking)); + } + } if (!detailed || balance_per_subaddress.empty()) return true; success_msg_writer() << tr("Balance per address:"); @@ -9570,17 +9587,18 @@ void simple_wallet::print_accounts(const std::string& tag) success_msg_writer() << tr("Accounts with tag: ") << tag; success_msg_writer() << tr("Tag's description: ") << account_tags.first.find(tag)->second; } - success_msg_writer() << fmt::format(tr(" {:>15} {:>21} {:>21} {:>21}"), "Account", "Balance", "Unlocked balance", "Label"); + success_msg_writer() << fmt::format(tr(" {:>15} {:>21} {:>21} {:>21}"), "Address", "Balance", "Unlocked balance", "Label"); uint64_t total_balance = 0, total_unlocked_balance = 0; for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) { + std::string address_str = m_wallet->get_subaddress_as_str({account_index, 0}).substr(0, 6); if (account_tags.second[account_index] != tag) continue; success_msg_writer() << boost::format(tr(" %c%8u %6s %21s %21s %21s")) % (m_current_subaddress_account == account_index ? '*' : ' ') % account_index - % m_wallet->get_subaddress_as_str({account_index, 0}).substr(0, 6) + % address_str % print_money(m_wallet->balance(account_index, false)) % print_money(m_wallet->unlocked_balance(account_index, false,NULL,NULL)) % m_wallet->get_subaddress_label({account_index, 0}); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 8261326aad7..6f9af7a1195 100755 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1095,21 +1095,21 @@ std::vector* WalletImpl::listCurrentStakes() const { std::vector* stakes = new std::vector; - auto response = wallet()->list_current_stakes(); + auto response = wallet()->get_staked_master_nodes(); auto address = mainAddress(); - for (rpc::GET_MASTER_NODES::response::entry const &node_info : response) + for (const auto& node_info : response) { - for (const auto& contributor : node_info.contributors) + for (const auto& contributor : node_info["contributors"]) { - if(contributor.address == address){ + if(contributor["address"] == address){ auto &info = stakes->emplace_back(); - info.mn_pubkey = node_info.master_node_pubkey; - info.stake = contributor.amount; - if(node_info.requested_unlock_height !=0) - info.unlock_height = node_info.requested_unlock_height; - info.decommissioned = !node_info.active && node_info.funded; - info.awaiting = !node_info.funded; + info.mn_pubkey = node_info["master_node_pubkey"]; + info.stake = contributor["amount"]; + if(node_info["requested_unlock_height"] !=0) + info.unlock_height = node_info["requested_unlock_height"]; + info.decommissioned = !node_info["active"] && node_info["funded"]; + info.awaiting = !node_info["funded"]; } } diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7dcb2529533..6f872eddc20 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -257,8 +257,18 @@ bool NodeRPCProxy::update_all_master_nodes_cache(uint64_t height) const { if (m_offline) return false; + nlohmann::json req{}; + req["fields"] = nlohmann::json{}; + req["fields"]["active"] = true; + req["fields"]["contributors"] = true; + req["fields"]["funded"] = true; + req["fields"]["registration_height"] = true; + req["fields"]["requested_unlock_height"] = true; + req["fields"]["master_node_pubkey"] = true; + req["fields"]["staking_requirement"] = true; + req["fields"]["total_reserved"] = true; try { - auto res = m_http_client.json_rpc("get_master_nodes", {}); + auto res = m_http_client.json_rpc("get_master_nodes", req); m_all_master_nodes_cached_height = height; m_all_master_nodes = std::move(res["master_node_states"]); } catch (...) { return false; } diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 96ef189ba29..40572762a82 100755 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -56,10 +56,12 @@ class NodeRPCProxy bool get_dynamic_base_fee_estimate(uint64_t grace_blocks, cryptonote::byte_and_output_fees &fees) const; bool get_fee_quantization_mask(uint64_t &fee_quantization_mask) const; std::optional get_hardfork_version() const; - - std::pair get_master_nodes(std::vector pubkeys) const; - std::pair get_all_master_nodes() const; - std::pair get_contributed_master_nodes(const std::string& contributor) const; + + // Note that this master node responses only fills out fields that are used in wallet code, not + // the full master node records. (see node_rpc_proxy.cpp for the precise list). + std::pair get_master_nodes(std::vector pubkeys) const; + std::pair get_all_master_nodes() const; + std::pair get_contributed_master_nodes(const std::string& contributor) const; std::pair get_master_node_blacklisted_key_images() const; std::pair bns_owners_to_names(nlohmann::json const &request) const; std::pair bns_names_to_owners(nlohmann::json const &request) const; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 899825da633..b07ef7e8f52 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13324,10 +13324,10 @@ uint64_t wallet2::get_approximate_blockchain_height() const return approx_blockchain_height; } -nlohmann::json wallet2::list_current_stakes() +nlohmann::json wallet2::get_staked_master_nodes() { - auto [success, master_node_states] = this->get_all_master_nodes(); - return master_node_states; + auto [success, contributed_nodes] = m_node_rpc_proxy.get_contributed_master_nodes(get_address_as_str()); + return std::move(contributed_nodes); } void wallet2::set_bns_cache_record(wallet2::bns_detail detail) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 1f566f035ac..3a6a22269df 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -818,10 +818,11 @@ namespace tools auto get_all_master_nodes() const { return m_node_rpc_proxy.get_all_master_nodes(); } auto get_master_nodes(std::vector const &pubkeys) const { return m_node_rpc_proxy.get_master_nodes(pubkeys); } auto get_master_node_blacklisted_key_images() const { return m_node_rpc_proxy.get_master_node_blacklisted_key_images(); } + // List of service nodes this wallet has a stake in: + nlohmann::json get_staked_master_nodes(); auto bns_owners_to_names(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_owners_to_names(request); } auto bns_names_to_owners(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_names_to_owners(request); } auto resolve(nlohmann::json const &request) const { return m_node_rpc_proxy.bns_resolve(request); } - nlohmann::json list_current_stakes(); struct bns_detail { std::string name; From 6cddb9232901da32f85a9c5b36564e74c0eab36c Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 10:53:37 +0530 Subject: [PATCH 110/182] RPC: build fix --- CMakeLists.txt | 1 - external/uWebSockets | 2 +- src/checkpoints/checkpoints.cpp | 3 +- src/checkpoints/checkpoints.h | 5 +- src/common/pruning.h | 5 +- .../cryptonote_format_utils.cpp | 8 +++ .../cryptonote_format_utils.h | 8 ++- src/cryptonote_basic/miner.cpp | 2 - src/cryptonote_core/blockchain.cpp | 4 +- src/cryptonote_core/cryptonote_core.cpp | 6 ++- src/cryptonote_core/cryptonote_core.h | 2 +- src/cryptonote_core/master_node_rules.cpp | 2 +- src/daemon/command_parser_executor.h | 6 +++ src/daemon/rpc_command_executor.cpp | 52 +++++++++---------- src/rpc/core_rpc_server.cpp | 5 +- src/rpc/core_rpc_server_command_parser.cpp | 6 +++ src/rpc/core_rpc_server_command_parser.h | 4 +- src/rpc/core_rpc_server_commands_defs.cpp | 2 +- src/rpc/lmq_server.cpp | 8 +-- src/simplewallet/simplewallet.cpp | 2 +- src/wallet/wallet2.cpp | 24 ++++----- 21 files changed, 90 insertions(+), 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30115cf92c2..0ec101e8045 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -571,7 +571,6 @@ else() # if those don't work for your compiler, single it out where appropriate if(CMAKE_BUILD_TYPE STREQUAL "Release" AND NOT OPENBSD) - set(C_SECURITY_FLAGS "${C_SECURITY_FLAGS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1") set(C_SECURITY_FLAGS "${C_SECURITY_FLAGS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1") set(CXX_SECURITY_FLAGS "${CXX_SECURITY_FLAGS} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1") endif() diff --git a/external/uWebSockets b/external/uWebSockets index eea4b7e0f6e..c445faa3812 160000 --- a/external/uWebSockets +++ b/external/uWebSockets @@ -1 +1 @@ -Subproject commit eea4b7e0f6e907646d34909e32f415c2a7dea385 +Subproject commit c445faa38125bf782eed3fec97f83b4733c7fb91 diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 23d0d6a77a9..1faa12d9017 100755 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -322,7 +322,8 @@ namespace cryptonote for (size_t i = 0; i < beldex::array_count(HARDCODED_MAINNET_CHECKPOINTS); ++i) { height_to_hash const &checkpoint = HARDCODED_MAINNET_CHECKPOINTS[i]; - ADD_CHECKPOINT(checkpoint.height, checkpoint.hash); + bool added = add_checkpoint(checkpoint.height, checkpoint.hash); + CHECK_AND_ASSERT(added, false); } } #endif diff --git a/src/checkpoints/checkpoints.h b/src/checkpoints/checkpoints.h index 066487d373a..d19e30ea5d2 100755 --- a/src/checkpoints/checkpoints.h +++ b/src/checkpoints/checkpoints.h @@ -38,11 +38,10 @@ #include "cryptonote_basic/cryptonote_basic_impl.h" #include "common/fs.h" -#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false); -#define JSON_HASH_FILE_NAME "checkpoints.json" - namespace cryptonote { + constexpr std::string_view JSON_HASH_FILE_NAME = "checkpoints.json"sv; + enum struct checkpoint_type { hardcoded, diff --git a/src/common/pruning.h b/src/common/pruning.h index 1e234bbd0d9..8e558d6751e 100755 --- a/src/common/pruning.h +++ b/src/common/pruning.h @@ -41,7 +41,10 @@ namespace tools return (pruning_seed >> PRUNING_SEED_LOG_STRIPES_SHIFT) & PRUNING_SEED_LOG_STRIPES_MASK; } inline uint32_t get_pruning_stripe(uint32_t pruning_seed) { - if (pruning_seed == 0) return 0; return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK); + if (pruning_seed == 0) + return 0; + + return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK); } uint32_t make_pruning_seed(uint32_t stripe, uint32_t log_stripes); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 9a17659ecee..38af1e118bc 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1116,6 +1116,14 @@ namespace cryptonote return s; } //--------------------------------------------------------------- + std::string format_money(uint64_t amount, bool strip_zeros) + { + auto value = print_money(amount, strip_zeros); + value += ' '; + value += get_unit(); + return value; + } + //--------------------------------------------------------------- std::string print_tx_verification_context(tx_verification_context const &tvc, transaction const *tx) { std::ostringstream os; diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index a2b95f68a18..6ae77967351 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -219,8 +219,12 @@ namespace cryptonote uint64_t get_block_height(const block& b); std::vector relative_output_offsets_to_absolute(const std::vector& off); std::vector absolute_output_offsets_to_relative(const std::vector& off); - inline constexpr std::string_view get_unit() { return "BDX"sv; } - std::string print_money(uint64_t amount, bool trim_insignificant = false); + constexpr std::string_view get_unit() { return "BDX"sv; } + // Returns a monetary value with a decimal point; optionally strips insignificant trailing 0s. + std::string print_money(uint64_t amount, bool strip_zeros = false); + // Returns a formatted monetary value including the unit, e.g. "1.234567 BDX"; strips + // insignificant trailing 0s by default (unlike the above) but can be overridden to not do that. + std::string format_money(uint64_t amount, bool strip_zeros = true); std::string print_tx_verification_context (tx_verification_context const &tvc, transaction const *tx = nullptr); std::string print_vote_verification_context(vote_verification_context const &vvc, master_nodes::quorum_vote_t const *vote = nullptr); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 2f5b3f29f9f..0ce2ee6c308 100755 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -56,7 +56,6 @@ namespace cryptonote namespace { - const command_line::arg_descriptor arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; const command_line::arg_descriptor arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; const command_line::arg_descriptor arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; } @@ -122,7 +121,6 @@ namespace cryptonote //----------------------------------------------------------------------------------------------------- void miner::init_options(boost::program_options::options_description& desc) { - command_line::add_arg(desc, arg_extra_messages); command_line::add_arg(desc, arg_start_mining); command_line::add_arg(desc, arg_mining_threads); } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 0abb1cc303f..c9cee347442 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1342,7 +1342,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl } } - if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version < hf::hf20_bulletproof_plusplus) + if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version > hf::hf20_bulletproof_plusplus) { if (version >= hf::hf17_POS && reward_parts.governance_paid == 0) { @@ -1372,7 +1372,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl // TODO(beldex): eliminate all floating point math in reward calculations. uint64_t max_base_reward = reward_parts.base_miner + reward_parts.governance_paid + reward_parts.master_node_total + 1; uint64_t max_money_in_use = max_base_reward + reward_parts.miner_fee; - if (money_in_use > max_money_in_use && version < hf::hf20_bulletproof_plusplus) + if (money_in_use > max_money_in_use && version > hf::hf20_bulletproof_plusplus) { MERROR_VER("coinbase transaction spends too much money (" << print_money(money_in_use) << "). Maximum block reward is " << print_money(max_money_in_use) << " (= " << print_money(max_base_reward) << " base + " << print_money(reward_parts.miner_fee) << " fees)"); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 914f637629c..14000e941e1 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -2294,8 +2294,10 @@ namespace cryptonote { std::vector const states = get_master_node_list_state({ m_master_keys.pub }); - // wait one block before starting uptime proofs. - if (!states.empty() && (states[0].info->registration_height + 1) < get_current_blockchain_height()) + // wait one block before starting uptime proofs (but not on testnet/devnet, where we sometimes + // have mass registrations/deregistrations where the waiting causes problems). + uint64_t delay_blocks = m_nettype == network_type::MAINNET ? 1 : 0; + if (!states.empty() && (states[0].info->registration_height + delay_blocks) < get_current_blockchain_height()) { m_check_uptime_proof_interval.do_call([this]() { // This timer is not perfectly precise and can leak seconds slightly, so send the uptime diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 420b682b034..29eaa2b373a 100755 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -987,7 +987,7 @@ namespace cryptonote /// Time point at which the storage server and belnet last pinged us std::atomic m_last_storage_server_ping, m_last_belnet_ping; - std::atomic m_storage_https_port, m_storage_omq_port; + std::atomic m_storage_https_port{0}, m_storage_omq_port{0}; uint32_t mn_public_ip() const { return m_mn_public_ip; } uint16_t storage_https_port() const { return m_storage_https_port; } diff --git a/src/cryptonote_core/master_node_rules.cpp b/src/cryptonote_core/master_node_rules.cpp index 13825cfff0d..7ccc8ca0538 100755 --- a/src/cryptonote_core/master_node_rules.cpp +++ b/src/cryptonote_core/master_node_rules.cpp @@ -54,7 +54,7 @@ crypto::hash generate_request_stake_unlock_hash(uint32_t nonce) return result; } -uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t curr_height,hf version) +uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t curr_height, hf version) { uint64_t blocks_to_lock = staking_num_lock_blocks(nettype,version); uint64_t result = curr_height + (blocks_to_lock / 2); diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 23c44f254d3..9f9803fcc89 100755 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -42,6 +42,12 @@ class command_parser_executor final private: rpc_command_executor m_executor; public: + + /// Creators a command parser; arguments are forwarded to the rpc_command_executor constructor + template + command_parser_executor(T&&... args) + : m_executor{std::forward(args)...} + {} bool print_checkpoints(const std::vector& args); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 310a66bd58b..2ce60370988 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -154,9 +154,11 @@ namespace { << "miner tx hash: " << header.miner_tx_hash; } - std::string get_human_time_ago(std::chrono::seconds ago, bool abbreviate = false) + template + std::string get_human_time_ago(std::chrono::duration ago_dur, bool abbreviate = false) { - if (ago == 0s) + auto ago = std::chrono::duration_cast(ago_dur); + if (ago == 0s) return "now"; auto dt = ago > 0s ? ago : -ago; std::string s; @@ -170,17 +172,19 @@ namespace { s = fmt::format("{:.1f} days", static_cast(dt.count()) / 86400); if (abbreviate) { if (ago < 0s) - return s + " (in fut.)"; - return s; + s += " (in fut.)"; + } else { + s += ' '; + s += (ago < 0s ? "in the future" : "ago"); } - return s + " " + (ago < 0s ? "in the future" : "ago"); + return s; } std::string get_human_time_ago(std::time_t t, std::time_t now, bool abbreviate = false) { return get_human_time_ago(std::chrono::seconds{now - t}, abbreviate); } - bool print_peer(std::string_view prefix, const json& peer, bool pruned_only, bool publicrpc_only) + bool print_peer(std::string_view prefix, const json& peer, bool pruned_only) { auto pruning_seed = peer.value("pruning_seed", 0); if (pruned_only && pruning_seed == 0) @@ -212,7 +216,6 @@ rpc_command_executor::rpc_command_executor( std::string http_url, const std::optional& login): m_rpc{std::in_place_type, http_url} { - m_rpc_client.emplace(remote_url); if (login) std::get(m_rpc).set_auth(login->username, std::string{login->password.password().view()}); } @@ -491,7 +494,6 @@ bool rpc_command_executor::show_status() { auto maybe_mns = try_running([&] { return invoke(json{{"master_node_pubkeys", json::array({my_mn_key})}}); }, "Failed to retrieve master node info"); if (maybe_mns) { - { if (auto it = maybe_mns->find("master_node_states"); it != maybe_mns->end() && it->is_array() && it->size() > 0) { auto& state = it->front(); my_mn_registered = true; @@ -518,7 +520,7 @@ bool rpc_command_executor::show_status() { if (net == "testnet") str << " ON TESTNET"; else if (net == "devnet") str << " ON DEVNET"; - if (ires.height < net_height) + if (height < net_height) str << ", syncing"; // if (info.value("was_bootstrap_ever_used", false)) @@ -542,7 +544,7 @@ bool rpc_command_executor::show_status() { str << ", net hash " << get_mining_speed(info["difficulty"].get() / info["target"].get()); str << ", v" << info["version"].get(); - str << "(net v" << +hf_version << ')'; + str << "(net v" << static_cast(hf_version) << ')'; auto earliest = hfinfo.value("earliest_height", uint64_t{0}); if (earliest) print_fork_extra_info(str, earliest, net_height, 1s * info["target"].get()); @@ -802,7 +804,7 @@ bool rpc_command_executor::print_height() { return false; } -bool rpc_command_executor::print_block_by_hash(const crypto::hash& block_hash, bool include_hex) { req.fill_pow_hash = true; +bool rpc_command_executor::print_block_by_hash(const crypto::hash& block_hash, bool include_hex) { auto maybe_block = try_running([this, &block_hash] { return invoke(json{ {"hash", tools::type_to_hex(block_hash)}, @@ -874,11 +876,11 @@ bool rpc_command_executor::print_transaction(const crypto::hash& transaction_has std::optional t; if (include_metadata || include_json) { - if (oxenmq::is_hex(pruned_hex) && oxenmq::is_hex(prunable_hex)) + if (oxenc::is_hex(pruned_hex) && oxenc::is_hex(prunable_hex)) { - std::string blob = oxenmq::from_hex(pruned_hex); + std::string blob = oxenc::from_hex(pruned_hex); if (!prunable_hex.empty()) - blob += oxenmq::from_hex(prunable_hex); + blob += oxenc::from_hex(prunable_hex); bool parsed = pruned ? cryptonote::parse_and_validate_tx_base_from_blob(blob, t.emplace()) @@ -1117,7 +1119,7 @@ bool rpc_command_executor::stop_daemon() return invoke_simple("Couldn't stop daemon", "Stop signal sent"); } -bool rpc_command_executor::get_limit(bool up, bool down) +bool rpc_command_executor::get_limit() { auto maybe_limit = try_running([this] { return invoke(); }, "Failed to retrieve current traffic limits"); if (!maybe_limit) @@ -1582,7 +1584,7 @@ void print_votes(std::ostream& o, const json& elem, const std::string& key, EPri } } -static void append_printable_master_node_list_entry(cryptonote::network_type nettype, bool detailed_view, uint64_t blockchain_height, uint64_t entry_index, GET_MASTER_NODES::response::entry const &entry, std::string &buffer) +static void append_printable_master_node_list_entry(cryptonote::network_type nettype, bool detailed_view, uint64_t blockchain_height, uint64_t entry_index, const json& entry, std::string& buffer) { const char indent1[] = " "; const char indent2[] = " "; @@ -1663,7 +1665,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net auto ed_pk = entry.value("pubkey_ed25519", ""sv); stream << indent2 << "Auxiliary Public Keys:\n" << indent3 << (ed_pk.empty() ? "(not yet received)"sv : ed_pk) << " (Ed25519)\n" - << indent3 << (ed_pk.empty() ? "(not yet received)"s : oxenmq::to_base32z(oxenmq::from_hex(ed_pk)) + ".mnode") << " (Belnet)\n" + << indent3 << (ed_pk.empty() ? "(not yet received)"s : oxenc::to_base32z(oxenc::from_hex(ed_pk)) + ".mnode") << " (Belnet)\n" << indent3 << entry.value("pubkey_x25519", "(not yet received)"sv) << " (X25519)\n"; } // @@ -1980,13 +1982,13 @@ static uint64_t get_amount_to_make_portions(uint64_t amount, uint64_t portions) return resultlo; } -// static uint64_t get_actual_amount(uint64_t amount, uint64_t portions) -// { -// uint64_t lo, hi, resulthi, resultlo; -// lo = mul128(amount, portions, &hi); -// div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); -// return resultlo; -// } +static uint64_t get_actual_amount(uint64_t amount, uint64_t portions) +{ + uint64_t lo, hi, resulthi, resultlo; + lo = mul128(amount, portions, &hi); + div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo); + return resultlo; +} bool rpc_command_executor::prepare_registration(bool force_registration) { @@ -2048,8 +2050,6 @@ bool rpc_command_executor::prepare_registration(bool force_registration) if (!maybe_header) return false; - uint64_t const now = time(nullptr); - auto const& header = *maybe_header; const auto now = std::chrono::system_clock::now(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 84ef3d6f761..9fd38a4cf10 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -687,7 +687,7 @@ namespace cryptonote::rpc { void operator()(const tx_extra_burn& x) { set("burn_amount", x.amount); } void operator()(const tx_extra_master_node_winner& x) { set("mn_winner", x.m_master_node_key); } void operator()(const tx_extra_master_node_pubkey& x) { set("mn_pubkey", x.m_master_node_key); } - void operator()(const tx_extra_security_signature& x) { set("security_sig", x.m_security_signature); } + void operator()(const tx_extra_security_signature& x) { set("security_sig", tools::type_to_hex(x.m_security_signature)); } void operator()(const tx_extra_master_node_register& x) { json reservations{}; for (size_t i = 0; i < x.m_portions.size(); i++) @@ -2669,7 +2669,6 @@ namespace cryptonote::rpc { invoke(req_old, context); get_master_node_registration_cmd.response["status"] = req_old.response["status"]; get_master_node_registration_cmd.response["registration_cmd"] = req_old.response["registration_cmd"]; - return res; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2966,7 +2965,7 @@ namespace cryptonote::rpc { success(significant); status = STATUS_OK; } - return res; + return status; } } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index f853862a7ac..6b1a9cff652 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -337,4 +337,10 @@ namespace cryptonote::rpc { get_values(in, "operator_cut", cmd.request.operator_cut); get_values(in, "staking_requirement", cmd.request.staking_requirement); } + + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { + get_values(in, "args", cmd.request.args); + get_values(in, "make_friendly", cmd.request.make_friendly); + get_values(in, "staking_requirement", cmd.request.staking_requirement); +} } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index d58d6bfb2bd..ebc2921a71b 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -8,7 +8,7 @@ namespace cryptonote::rpc { using rpc_input = std::variant; - inline void parse_request(NO_ARGS&, rpc_input) {} + inline void parse_request(NO_ARGS&, rpc_input) {}; void parse_request(BNS_RESOLVE& bns, rpc_input in); void parse_request(GET_MASTER_NODES& mns, rpc_input in); @@ -21,7 +21,6 @@ namespace cryptonote::rpc { void parse_request(STOP_DAEMON& stop_daemon, rpc_input in); void parse_request(SAVE_BC& save_bc, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); - void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); void parse_request(GET_TRANSACTIONS& get, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); void parse_request(SET_LIMIT& limit, rpc_input in); @@ -43,6 +42,7 @@ namespace cryptonote::rpc { void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 397b8b7a334..22dc71016d6 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -456,7 +456,7 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE(difficulty) // KV_SERIALIZE(cumulative_difficulty) // KV_SERIALIZE(reward) -// KV_SERIALIZE(miner_reward) +// KV_SERIALIZE(coinbase_payouts) // KV_SERIALIZE(block_size) // KV_SERIALIZE_OPT(block_weight, (uint64_t)0) // KV_SERIALIZE(num_txes) diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index 1947eb35b3d..1834ce842ef 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -208,7 +208,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog try { auto result = std::visit([](auto&& v) -> std::string { using T = decltype(v); - if constexpr (std::is_same_v) + if constexpr (std::is_same_v) return bt_serialize(std::move(v)); else if constexpr (std::is_same_v) return v.dump(); @@ -503,8 +503,8 @@ void omq_rpc::on_mempool_sub_request(oxenmq::Message& m) } mempool_sub_type sub_type; - if (m.data[0] == "blink"sv) - sub_type = mempool_sub_type::blink; + if (m.data[0] == "flash"sv) + sub_type = mempool_sub_type::flash; else if (m.data[0] == "all"sv) sub_type = mempool_sub_type::all; else { @@ -525,7 +525,7 @@ void omq_rpc::on_mempool_sub_request(oxenmq::Message& m) } result.first->second.type = sub_type; } - MDEBUG("New " << (sub_type == mempool_sub_type::blink ? "blink" : "all") << " mempool subscription request from conn " << m.conn << " @ " << m.remote); + MDEBUG("New " << (sub_type == mempool_sub_type::flash ? "flash" : "all") << " mempool subscription request from conn " << m.conn << " @ " << m.remote); m.send_reply("OK"); } } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 13271c90df7..bc5c5c79631 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6390,7 +6390,7 @@ bool simple_wallet::query_locked_stakes(bool print_result, bool print_key_images if (entry["amount"] > 0) { // version >= master_nodes::key_image_blacklist_entry::version_1_serialize_amount - msg_buf.append(fmt::format(" Total Locked : {}\n", cryptonote::print_money(entry["amount"]))); + msg_buf.append(fmt::format(" Total Locked : {}\n", cryptonote::format_money(entry["amount"]))); } msg_buf.append("\n"); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b07ef7e8f52..f0c5ebb6db2 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8143,7 +8143,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ } uint64_t max_contrib_total = mnode_info["staking_requirement"].get() - mnode_info["total_reserved"].get(); - uint64_t min_contrib_total = master_nodes::get_min_node_contribution(hf_version, mnode_info["staking_requirement"], mnode_info["total_reserved"], total_existing_contributions); + uint64_t min_contrib_total = master_nodes::get_min_node_contribution(*hf_version, mnode_info["staking_requirement"], mnode_info["total_reserved"], total_existing_contributions); bool is_preexisting_contributor = false; for (const auto& contributor : mnode_info["contributors"]) @@ -8577,7 +8577,6 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry } cryptonote::account_public_address const primary_address = get_address(); - rpc::master_node_contributor const *p_contributor = nullptr; nlohmann::json contributions{}; bool found = false; auto const& node_info = response[0]; @@ -8591,7 +8590,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry found = true; contributions = contributor["locked_contributions"]; - p_contributor = &contributor; + break; } if (!found) @@ -8604,15 +8603,6 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry std::string error_msg; uint64_t cur_height = get_daemon_blockchain_height(error_msg); - if(version >= hf::hf18_bns) - { - if(((p_contributor->amount) < master_nodes::SMALL_CONTRIBUTOR_THRESHOLD * beldex::COIN ) && ((cur_height - node_info.registration_height) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)) - { - result.msg = tr("you can't give the unlock command! you have to wait upto ") + std::to_string(node_info.registration_height + master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER - cur_height) + " Blocks or "+ std::to_string((node_info.registration_height + master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER - cur_height)/ (master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER / 30)) + " days approx"; - result.success = false; - return result; - } - } cryptonote::tx_extra_tx_key_image_unlock unlock = {}; { uint64_t curr_height = 0; @@ -8640,6 +8630,13 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry return result; } + if((contribution["amount"] < master_nodes::SMALL_CONTRIBUTOR_THRESHOLD * beldex::COIN ) && ((cur_height - node_info["registration_height"].get()) < master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER)) + { + result.msg = tr("you can't give the unlock command! you have to wait upto ") + std::to_string(node_info["registration_height"].get() + master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER - cur_height) + " Blocks or "+ std::to_string((node_info["registration_height"].get() + master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER - cur_height)/ (master_nodes::SMALL_CONTRIBUTOR_UNLOCK_TIMER / 30)) + " days approx"; + result.success = false; + return result; + } + result.msg.append("You are requesting to unlock a stake of: "); result.msg.append(cryptonote::print_money(contribution["amount"])); result.msg.append(" Beldex from the master node network.\nThis will schedule the master node: "); @@ -8657,7 +8654,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; return result; } - uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(nettype(), node_info["registration_height"].get(), curr_height); + uint64_t unlock_height = master_nodes::get_locked_key_image_unlock_height(nettype(), curr_height, *hf_version); result.msg.append("You will continue receiving rewards until the master node expires at the estimated height: "); result.msg.append(std::to_string(unlock_height)); result.msg.append(" (about "); @@ -12372,6 +12369,7 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s cryptonote::transaction tx; crypto::hash tx_hash{}; + std::string tx_data; crypto::hash tx_prefix_hash{}; const auto& res_tx = res["txs"].front(); From d5d72fa35ec51b804a87d90cb29d2a95f6fde9fb Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 11:46:59 +0530 Subject: [PATCH 111/182] print_locked_stakes fix --- CMakeLists.txt | 2 +- src/common/pruning.h | 3 +- src/cryptonote_core/master_node_list.cpp | 2 +- src/daemon/rpc_command_executor.cpp | 4 +- src/simplewallet/simplewallet.cpp | 352 ++++++++++++++++------- 5 files changed, 249 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ec101e8045..afcce7f9c75 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -560,7 +560,7 @@ else() endif() endif() set(C_WARNINGS "-Waggregate-return -Wnested-externs -Wold-style-definition -Wstrict-prototypes") - set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers") + set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers -Wno-stringop-overflow -Werror=sign-compare") option(COVERAGE "Enable profiling for test coverage report" 0) diff --git a/src/common/pruning.h b/src/common/pruning.h index 8e558d6751e..ae431102564 100755 --- a/src/common/pruning.h +++ b/src/common/pruning.h @@ -42,8 +42,7 @@ namespace tools } inline uint32_t get_pruning_stripe(uint32_t pruning_seed) { if (pruning_seed == 0) - return 0; - + return 0; return 1 + ((pruning_seed >> PRUNING_SEED_STRIPE_SHIFT) & PRUNING_SEED_STRIPE_MASK); } diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index 17f820a73b7..f498378c43f 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -2906,7 +2906,7 @@ namespace master_nodes bool master_node_list::handle_uptime_proof(cryptonote::NOTIFY_UPTIME_PROOF::request const &proof, bool &my_uptime_proof_confirmation, crypto::x25519_public_key &x25519_pkey) { auto vers = get_network_version_revision(m_blockchain.nettype(), m_blockchain.get_current_blockchain_height()); - if (vers >= std::make_pair(hf::hf17_POS, 1)) + if (vers >= std::make_pair(hf::hf17_POS, uint8_t{1})) REJECT_PROOF("Old format (non-bt) proofs are not acceptable from v17+1 onwards"); auto& netconf = get_config(m_blockchain.nettype()); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2ce60370988..0daba3ec902 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -871,7 +871,7 @@ bool rpc_command_executor::print_transaction(const crypto::hash& transaction_has else tools::success_msg_writer() << "Found in blockchain at height " << tx["block_height"].get() << (pruned ? " (pruned)" : ""); - auto pruned_hex = tx["pruned"].get(); // Always included with req.split=true + auto pruned_hex = tx["pruned"].get(); // Always included with req.split=true std::optional t; if (include_metadata || include_json) @@ -1104,7 +1104,7 @@ bool rpc_command_executor::start_mining(const cryptonote::account_public_address if (!try_running([this, &args] { return invoke(args); }, "Unable to start mining")) return false; - tools::success_msg_writer() + tools::success_msg_writer() << fmt::format("Mining started with {} thread(s).", std::max(num_threads, 1)) << (num_blocks ? fmt::format(" Will stop after {} blocks", num_blocks) : ""); return true; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index bc5c5c79631..99744c6458d 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6280,135 +6280,271 @@ bool simple_wallet::request_stake_unlock(const std::vector &args_) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::query_locked_stakes(bool print_result, bool print_key_images) +bool simple_wallet::query_locked_stakes(bool print_details, bool print_key_images) { if (!try_connect_to_daemon()) return false; bool has_locked_stakes = false; - std::string msg_buf; - { - using namespace cryptonote; - auto response = m_wallet->list_current_stakes(); + std::string msg; + auto my_addr = m_wallet->get_address_as_str(); + + auto response = m_wallet->get_staked_master_nodes(); - for (const auto& node_info : response) + // From the old RPC GET_MASTER_NODES::response::entry, but only the + // fields used below. + struct master_node_contribution + { + std::string key_image; + uint64_t amount; + }; + struct master_node_contributor + { + uint64_t amount; // total locked contributions in atomic BDX + std::string address; + std::vector locked_contributions; + }; + struct mn_entry + { + std::string master_node_pubkey; + uint64_t requested_unlock_height; + std::vector contributors; + uint64_t staking_requirement; + }; + std::vector mns; + for (const auto& node_info : response) + { + mn_entry entry; + for (const auto& contributor : node_info["contributors"]) { - bool only_once = true; - for (const auto& contributor : node_info["contributors"]) + master_node_contributor a; + for (const auto& contribution : contributor["locked_contributions"]) { - std::unordered_set printed_addresses; - for (size_t i = 0; i < contributor["locked_contributions"].size(); ++i) - { - const auto& contribution = contributor["locked_contributions"][i]; - has_locked_stakes = true; - - if (!print_result) - continue; - auto required = cryptonote::print_money(node_info["staking_requirement"]); - msg_buf.reserve(512); - std::string walletaddress = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); - if (only_once) - { - if((node_info["contributors"].size() - 1)==0) - msg_buf.append(fmt::format(fg(fmt::color::sky_blue) | fmt::emphasis::bold,"Master Node :{}\n",node_info["master_node_pubkey"])); - else - msg_buf.append(fmt::format(fg(fmt::color::sky_blue) | fmt::emphasis::bold,"Master Node :{} ({} {})\n",node_info["master_node_pubkey"],(node_info["contributors"].size() - 1),(node_info["contributors"].size()-1)==0 ? "": ((node_info["contributors"].size()-1) > 1 ? "Contributions" : "Contribution"))); - - if (node_info["requested_unlock_height"] != master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) - { - msg_buf.append(fmt::format("Unlock Height :{}\n",std::to_string(node_info["requested_unlock_height"]))); - } - if(walletaddress == contributor["address"]) - { - msg_buf.append(fmt::format("Operator's Contribution :{} of {} BDX required\n",cryptonote::print_money(contributor["amount"]),required)); - } - else - { - msg_buf.append(fmt::format("Operator's Contribution :{} of {} BDX required ({})\n",cryptonote::print_money(contributor["amount"]),required,contributor["address"])); - } - printed_addresses.insert(contributor["address"]); - } - if(!only_once && printed_addresses.find(contributor["address"]) == printed_addresses.end()) - { - msg_buf.append(fmt::format(" Total_Contributions:{} ({})\n",cryptonote::print_money(contributor["amount"]),(walletaddress == contributor["address"])? "YOURS" : contributor["address"])); - printed_addresses.insert(contributor["address"]); - } - only_once = false; - msg_buf.append(fmt::format(" ● BDX :{}\n",cryptonote::print_money(contribution["amount"]))); - if(print_key_images) - { - msg_buf.append(fmt::format(" Key Image :{}\n",contribution["key_image"])); - } - msg_buf.append("\n"); - } + master_node_contribution b; + b.key_image = contribution["key_image"].get(); + b.amount = contribution["amount"].get(); + a.locked_contributions.push_back(std::move(b)); } + a.address = contributor["address"].get(); + a.amount = contributor["amount"].get(); + entry.contributors.push_back(std::move(a)); } + entry.master_node_pubkey = node_info["master_node_pubkey"].get(); + entry.requested_unlock_height = node_info["requested_unlock_height"].get(); + entry.staking_requirement = node_info["staking_requirement"].get(); + mns.push_back(std::move(entry)); } + // Sort the list by pubkey, and partition into unlocking and not-unlocking: + std::stable_sort(mns.begin(), mns.end(), [](const auto& a, const auto& b) { + return a.master_node_pubkey < b.master_node_pubkey; }); + std::stable_partition(mns.begin(), mns.end(), [](const auto& a) { + return a.requested_unlock_height != master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT; }); + + for (auto& node_info : mns) { - auto [success, response] = m_wallet->get_master_node_blacklisted_key_images(); - if (!success) - { - fail_msg_writer() << "Connection to daemon failed when retrieving blacklisted key images"; - return has_locked_stakes; - } + auto& contributors = node_info.contributors; + // Filter out any contributor rows without any actual contributions (i.e. from unfilled reserved + // contributions): + contributors.erase( + std::remove_if(contributors.begin(), contributors.end(), + [](const auto& c) { return c.amount == 0; }), + contributors.end()); - bool once_only = true; - bool first = true; - crypto::key_image key_image; - for (const auto& entry : response) - { - if (!tools::hex_to_type(entry["key_image"], key_image)) - { - fail_msg_writer() << tr("Failed to parse hex representation of key image: ") << entry["key_image"]; - continue; - } + // Reorder contributors to put this wallet's contribution(s) first: + std::stable_partition(contributors.begin(), contributors.end(), + [&my_addr](const auto& x) { return x.address == my_addr; }); - if (!m_wallet->contains_key_image(key_image)) - continue; + if (contributors.empty() || contributors[0].address != my_addr) + continue; // We filtered out ourself + auto& me = contributors.front(); - if (first) - first = false; - else - msg_buf += "\n"; + has_locked_stakes = true; + if (!print_details) + continue; - has_locked_stakes = true; - if (!print_result) - continue; + uint64_t total = 0; + for (const auto& c : contributors) + total += c.amount; + + // Formatting: first 1-2 lines of general info: + // + // Master Node: abcdef123456... + // Unlock Height: 1234567 (omitted if not unlocking) + // + // If there are other contributors then we print a total line such as: + // + // Total Contributions: 10000 BDX of 10000 BDX required + // + // For our own contribution, when we have a single contribution, we use one of: + // + // Your Contribution: 5000 BDX (Key image: abcdef123...) + // Your Contribution: 5000 BDX of 10000 BDX required (Key image: abcdef123...) + // + // (the second one applies if we are the only contributor so far). + // + // If we made multiple contributions then: + // + // Your Contributions: 5000 BDX in 2 contributions: + // Your Contributions: 5000 BDX of 10000 BDX required in 2 contributions: + // + // (the second one if we are the only contributor so far). + // + // This is followed by the individual contributions: + // + // ‣ 4000.5 BDX (Key image: abcdef123...) + // ‣ 999.5 BDX (Key image: 789cba456...) + // + // If there are other contributors then we also print: + // + // Other contributions: 10000 BDX from 2 contributors: + // • 1234.565 BDX (bxU7YGUcPJffbaF5p8NLC3VidwJyHSdMaGmSxTBV645v33CmLq2ZvMqBdY9AVB2z8uhbHPCZSuZbv68hE6NBXBc51Gg9MGUGr) + // Key image 123456789... + // • 8765.435 BDX (bxTpop5RZdwE39iBvoP5xpJVoMpYPUwQpef9zS2tLL8yVgbppBbtGnzZxzkSp53Coi88wbsTHiokr7k8MQU94mGF1zzERqELK) + // ‣ 7530 BDX (Key image: 23456789a...) + // ‣ 1235.435 BDX (Key image: 3456789ab...) + // + // If we aren't showing key images then all the key image details get omitted. + + msg += fmt::format("Master Node: {}\n", node_info.master_node_pubkey); + if (node_info.requested_unlock_height != master_nodes::KEY_IMAGE_AWAITING_UNLOCK_HEIGHT) + msg += fmt::format("Unlock height: {}\n", node_info.requested_unlock_height); + + bool just_me = contributors.size() == 1; + + auto required = fmt::format(" of {} required", cryptonote::format_money(node_info.staking_requirement)); + if (!just_me) { + msg += fmt::format("Total Contributions: {}{}\n", cryptonote::format_money(total), required); + required.clear(); + } + + auto my_total = me.amount; + if (me.locked_contributions.size() == 1) + msg += "Your Contribution: "; + else + { + msg += fmt::format("Your Contributions: {}{} in {} contributions:\n ‣ ", + cryptonote::format_money(my_total), + required, + me.locked_contributions.size()); + required.clear(); + } - msg_buf.reserve(512); - if (once_only) - { - msg_buf.append("Blacklisted Stakes\n"); - once_only = false; - } - msg_buf.append(fmt::format(" Unlock Height : {}\n", std::to_string(entry["unlock_height"]))); - if(print_key_images) + for (size_t i = 0; i < me.locked_contributions.size(); i++) + { + auto& c = me.locked_contributions[i]; + if (i > 0) msg += " ‣ "; + msg += cryptonote::format_money(c.amount); + if (!required.empty()) { - msg_buf.append(fmt::format(" Key Image : {}\n", entry["key_image"])); + msg += required; + required.clear(); } - if (entry["amount"] > 0) + if (print_key_images) + msg += fmt::format(" (Key image: {})", c.key_image); + msg += '\n'; + } + + if (contributors.size() > 1) + { + msg += fmt::format("Other Contributions: {} from {} contributor{}:\n", + cryptonote::format_money(total - my_total), + contributors.size() - 1, + contributors.size() == 2 ? "" : "s"); + for (size_t i = 1; i < contributors.size(); i++) { - // version >= master_nodes::key_image_blacklist_entry::version_1_serialize_amount - msg_buf.append(fmt::format(" Total Locked : {}\n", cryptonote::format_money(entry["amount"]))); + const auto& contributor = contributors[i]; + const auto& locked = contributor.locked_contributions; + msg += fmt::format(" • {} ({})\n", + cryptonote::format_money(contributor.amount), contributor.address); + if (locked.size() == 1) + { + if (print_key_images) + msg += fmt::format(" Key image: {}\n", locked[0].key_image); + } + else + { + for (auto& c : locked) + { + msg += " ‣ "; + msg += cryptonote::format_money(c.amount); + if (print_key_images) + msg += fmt::format(" (Key image: {})\n", c.key_image); + else + msg += '\n'; + } + } } - msg_buf.append("\n"); - } + msg += "\n"; + } + + // Find blacklisted key images (i.e. locked contributions from deregistered SNs) that belong to + // this wallet. If there are any, output will be: + // + // Locked Stakes due to Master Node Deregistration: + // ‣ 234.567 BDX (Unlock height 1234567; Key image: abcfed999...) + // ‣ 5000 BDX (Unlock height 123333; Key image: cbcfef989...) + // + // where the "; Key image: ..." part is omitted if not printing key images. + + auto [success, bl] = m_wallet->get_master_node_blacklisted_key_images(); + if (!success) + { + fail_msg_writer() << "Connection to daemon failed when retrieving blacklisted key images"; + return has_locked_stakes; + } + struct blacklisted_images + { + std::string key_image; + uint64_t unlock_height; + uint64_t amount; + }; + std::vector blacklisted; + for (const auto& b : bl) + { + blacklisted.push_back({b["key_image"].get(), + b["unlock_height"].get(), + b["amount"].get()}); } - if (print_result) + // Filter out key images that aren't ours: + blacklisted.erase(std::remove_if(blacklisted.begin(), blacklisted.end(), + [this](const auto& black) { + if (crypto::key_image ki; tools::hex_to_type(black.key_image, ki)) + return !m_wallet->contains_key_image(ki); + fail_msg_writer() << "Failed to parse key image hex: " << black.key_image; + return true; + }), + blacklisted.end()); + + if (!blacklisted.empty()) { - if (has_locked_stakes) + has_locked_stakes = true; + if (print_details) { - tools::msg_writer() << msg_buf; - } - else - { - tools::msg_writer() << "No locked stakes known for this wallet on the network"; + msg += "Locked Stakes due to Master Node Deregistration:\n"; + + // Sort by unlock time (earliest first): + std::stable_sort(blacklisted.begin(), blacklisted.end(), + [](const auto& a, const auto& b) { return a.unlock_height < b.unlock_height; }); + + for (const auto& black : blacklisted) + { + msg += fmt::format(" • {} (Unlock height {}", cryptonote::format_money(black.amount), black.unlock_height); + if (print_key_images) + msg += fmt::format("; Key image: {})\n", black.key_image); + else + msg += ")\n"; + } } } + if (msg.empty() && print_details) + msg = "No locked stakes known for this wallet on the network"; + if (!msg.empty()) + tools::msg_writer() << msg; + return has_locked_stakes; } //---------------------------------------------------------------------------------------------------- @@ -7106,28 +7242,28 @@ bool simple_wallet::bns_lookup(std::vector args) int last_index = -1; for (auto const &mapping : response) { - auto enc_bchat_hex = mapping["encrypted_bchat_value"]; + auto enc_bchat_hex = mapping["encrypted_bchat_value"].get(); if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_bchat_hex) || enc_bchat_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } - auto enc_wallet_hex = mapping["encrypted_wallet_value"]; + auto enc_wallet_hex = mapping["encrypted_wallet_value"].get(); if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_wallet_hex) || enc_wallet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } - auto enc_belnet_hex = mapping["encrypted_belnet_value"]; + auto enc_belnet_hex = mapping["encrypted_belnet_value"].get(); if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_belnet_hex) || enc_belnet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; } - auto enc_eth_hex = mapping["encrypted_eth_addr_value"]; + auto enc_eth_hex = mapping["encrypted_eth_addr_value"].get(); if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_eth_hex) || enc_eth_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) { fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; @@ -7334,7 +7470,7 @@ bool simple_wallet::bns_by_owner(const std::vector& args) { bns::mapping_value mv; const auto type = bns::mapping_type::eth_addr; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_eth_addr_value"]), &mv) + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_eth_addr_value"].get()), &mv) && mv.decrypt(name, type)) value_eth = mv.to_readable_value(nettype, type); } @@ -7354,12 +7490,12 @@ bool simple_wallet::bns_by_owner(const std::vector& args) << "\n Value ethAddress : " << value_eth; writer << "\n Owner : " << entry["owner"]; - if (entry["backup_owner"]) writer - << "\n Backup owner : " << *entry["backup_owner"]; + if (auto got = entry.find("backup_owner"); got != entry.end()) writer + << "\n Backup owner: " << entry["backup_owner"]; writer << "\n Last updated height : " << entry["update_height"]; - if (entry["expiration_height"]) writer - << "\n Expiration height : " << *entry["expiration_height"]; + if (auto got = entry.find("expiration_height"); got != entry.end()) writer + << "\n Expiration height: " << entry["expiration_height"]; writer << "\n Encrypted bchat value : " << (entry["encrypted_bchat_value"].empty() ? "(none)" : entry["encrypted_bchat_value"]); writer From 08b73981035244d2d41736de9f8c5ac7c6bbdc4b Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Fri, 2 May 2025 12:25:22 +0530 Subject: [PATCH 112/182] build fix --- CMakeLists.txt | 2 +- src/daemon/rpc_command_executor.cpp | 2 +- src/wallet/wallet2.cpp | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afcce7f9c75..b9243fc70f1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -560,7 +560,7 @@ else() endif() endif() set(C_WARNINGS "-Waggregate-return -Wnested-externs -Wold-style-definition -Wstrict-prototypes") - set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers -Wno-stringop-overflow -Werror=sign-compare") + set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers -Wno-stringop-overflow") option(COVERAGE "Enable profiling for test coverage report" 0) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 0daba3ec902..96336b18017 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2223,7 +2223,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) } long additional_contributors = strtol(input.c_str(), NULL, 10 /*base 10*/); - if (additional_contributors < 1 || additional_contributors > (beldex::MAX_NUMBER_OF_CONTRIBUTORS - 1)) + if (additional_contributors < 1 || additional_contributors > static_cast(beldex::MAX_NUMBER_OF_CONTRIBUTORS - 1)) { std::cout << "Invalid value. Should be between [1-" << (beldex::MAX_NUMBER_OF_CONTRIBUTORS - 1) << "]" << std::endl; continue; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f0c5ebb6db2..a078710e423 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8599,7 +8599,6 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry return result; } - auto version = m_node_rpc_proxy.get_hardfork_version(); std::string error_msg; uint64_t cur_height = get_daemon_blockchain_height(error_msg); From 927cabd7b003aeb803c1f03e32f63e13f4a92e18 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 13:31:13 +0530 Subject: [PATCH 113/182] move blacklisted key images to completed and enable BNS_OWNERS_TO_NAMES --- CMakeLists.txt | 2 +- src/rpc/common/param_parser.hpp | 89 +++++++++++++++++----- src/rpc/core_rpc_server.cpp | 1 - src/rpc/core_rpc_server_command_parser.cpp | 7 +- src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 8 +- 6 files changed, 80 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9243fc70f1..0ec101e8045 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -560,7 +560,7 @@ else() endif() endif() set(C_WARNINGS "-Waggregate-return -Wnested-externs -Wold-style-definition -Wstrict-prototypes") - set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers -Wno-stringop-overflow") + set(CXX_WARNINGS "-Wno-reorder -Wno-missing-field-initializers") option(COVERAGE "Enable profiling for test coverage report" 0) diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index 4db565c3335..dbf08651c38 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -1,16 +1,21 @@ #pragma once -#include "oxenc/bt_serialize.h" +#include "rpc/common/rpc_binary.h" #include +#include #include #include +#include #include #include namespace cryptonote::rpc { using nlohmann::json; - using rpc_input = std::variant; + using oxenc::bt_dict_consumer; + using oxenc::bt_list_consumer; + using rpc_input = std::variant; + using json_range = std::pair; // Checks that key names are given in ascending order template @@ -43,15 +48,46 @@ namespace cryptonote::rpc { template constexpr bool is_std_optional> = true; - using oxenc::bt_dict_consumer; + // Wrapper around a reference for get_values that adds special handling to act as if the value was + // not given at all if the value is given as an empty string. This sucks, but is necessary for + // backwards compatibility (especially with wallet2 clients). + // + // Usage: + // + // std::string x; + // get_values(input, + // "x", ignore_empty_string{x}, + // // ... + // ); + template + struct ignore_empty_string { + T& value; + ignore_empty_string(T& ref) : value{ref} {} - using json_range = std::pair; + bool should_ignore(bt_dict_consumer& d) { + if (d.is_string()) { + auto d2{d}; // Copy because we want to leave d intact + if (d2.consume_string_view().empty()) + return true; + } + return false; + } + + bool should_ignore(json_range& it_range) { + auto& e = *it_range.first; + return (e.is_string() && e.get().empty()); + } + }; + template + constexpr bool is_ignore_empty_string_wrapper = false; + template + constexpr bool is_ignore_empty_string_wrapper> = true; // Advances the dict consumer to the first element >= the given name. Returns true if found, // false if it advanced beyond the requested name. This is exactly the same as // `d.skip_until(name)`, but is here so we can also overload an equivalent function for json // iteration. - inline bool skip_until(oxenc::bt_dict_consumer& d, std::string_view name) { + inline bool skip_until(bt_dict_consumer& d, std::string_view name) { return d.skip_until(name); } // Equivalent to the above but for a json object iterator. @@ -78,13 +114,13 @@ namespace cryptonote::rpc { template constexpr bool is_tuple_like> = true; template - void load_tuple_values(oxenc::bt_list_consumer&, TupleLike&, std::index_sequence); + void load_tuple_values(bt_list_consumer&, TupleLike&, std::index_sequence); // Consumes the next value from the dict consumer into `val` template - || std::is_same_v, + std::is_same_v + || std::is_same_v, int> = 0> void load_value(BTConsumer& c, T& val) { if constexpr (std::is_integral_v) @@ -156,27 +192,38 @@ namespace cryptonote::rpc { } template - void load_tuple_values(oxenc::bt_list_consumer& c, TupleLike& val, std::index_sequence) { + void load_tuple_values(bt_list_consumer& c, TupleLike& val, std::index_sequence) { (load_value(c, std::get(val)), ...); } + // Takes a json object iterator or bt_dict_consumer and loads the current value at the iterator. + // This calls itself recursively, if needed, to unwrap optional/required/ignore_empty_string + // wrappers. + template + void load_curr_value(In& in, T& val) { + if constexpr (is_required_wrapper) { + load_curr_value(in, val.value); + } else if constexpr (is_ignore_empty_string_wrapper) { + if (!val.should_ignore(in)) + load_curr_value(in, val.value); + } else if constexpr (is_std_optional) { + load_curr_value(in, val.emplace()); + } else { + load_value(in, val); + } + } + // Gets the next value from a json object iterator or bt_dict_consumer. Leaves the iterator at // the next value, i.e. found + 1 if found, or the next greater value if not found. (NB: // nlohmann::json objects are backed by an *ordered* map and so both nlohmann iterators and // bt_dict_consumer behave analogously here). template - void get_next_value(In& in, std::string_view name, T& val) { + void get_next_value(In& in, [[maybe_unused]]std::string_view name, T& val) { if constexpr (std::is_same_v) ; - else if (skip_until(in, name)) { - if constexpr (is_required_wrapper) - return load_value(in, val.value); - else if constexpr (is_std_optional) - return load_value(in, val.emplace()); - else - return load_value(in, val); - } - if constexpr (is_required_wrapper) + else if (skip_until(in, name)) + load_curr_value(in, val); + else if constexpr (is_required_wrapper) throw std::runtime_error{"Required key '" + std::string{name} + "' not found"}; } @@ -201,7 +248,7 @@ namespace cryptonote::rpc { { if (in.front() == 'd') { - oxenc::bt_dict_consumer d{in}; + bt_dict_consumer d{in}; get_values(d, name, val, std::forward(more)...); } else @@ -222,4 +269,4 @@ namespace cryptonote::rpc { } } } -} // namespace cryptonote::rpc \ No newline at end of file +} // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9fd38a4cf10..b217b58bb6a 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -79,7 +79,6 @@ namespace cryptonote::rpc { using nlohmann::json; - using beldex::json_to_bt; namespace { template diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 6b1a9cff652..37e06290b04 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -42,7 +42,7 @@ namespace cryptonote::rpc { get_values(in, "limit", mns.request.limit, - "poll_block_hash", mns.request.poll_block_hash, + "poll_block_hash", ignore_empty_string{mns.request.poll_block_hash}, "master_node_pubkeys", mns.request.master_node_pubkeys); } @@ -331,6 +331,11 @@ namespace cryptonote::rpc { get_values(in, "recent_cutoff", get_output_histogram.request.recent_cutoff); } + void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in) { + get_values(in, "entries", bns_owners_to_names.request.entries); + get_values(in, "include_expired", bns_owners_to_names.request.include_expired); + } + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in) { get_values(in, "contributor_addresses", cmd.request.contributor_addresses); get_values(in, "contributor_amounts", cmd.request.contributor_amounts); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index ebc2921a71b..98d6ed35e54 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -54,4 +54,5 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); void parse_request(GET_BLOCK& get_block, rpc_input in); void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); + void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 992ead1a0f4..494d6843e84 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1960,7 +1960,7 @@ namespace cryptonote::rpc { /// - \p key_image The key image of the transaction that is blacklisted on the network. /// - \p unlock_height The height at which the key image is removed from the blacklist and becomes spendable. /// - \p amount The total amount of locked Beldex in atomic units in this blacklisted stake. - struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC + struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : NO_ARGS { static constexpr auto names() { return NAMES("get_master_node_blacklisted_key_images"); } }; @@ -2327,7 +2327,9 @@ namespace cryptonote::rpc { TEST_TRIGGER_UPTIME_PROOF, REPORT_PEER_STATUS, GET_MN_STATE_CHANGES, - FLUSH_CACHE + FLUSH_CACHE, + GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, + BNS_OWNERS_TO_NAMES >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2347,11 +2349,9 @@ namespace cryptonote::rpc { GET_MASTER_KEYS, GET_MASTER_PRIVKEYS, GET_STAKING_REQUIREMENT, - GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, GET_CHECKPOINTS, BNS_NAMES_TO_OWNERS, BNS_LOOKUP, - BNS_OWNERS_TO_NAMES, BNS_VALUE_DECRYPT >; From 93e181a27dc3b5eb09b2e7393899202cb3dc9f91 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 14:49:18 +0530 Subject: [PATCH 114/182] Fix: get_master_keys/get_master_privkeys arg parsing --- src/rpc/core_rpc_server.cpp | 41 ++++++++---- src/rpc/core_rpc_server_command_parser.h | 65 +++++++++---------- src/rpc/core_rpc_server_commands_defs.h | 79 ++++++++++++------------ 3 files changed, 98 insertions(+), 87 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b217b58bb6a..b7ca94e7f10 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2783,19 +2783,38 @@ namespace cryptonote::rpc { auto& netconf = m_core.get_net_config(); // FIXME: accessing proofs one-by-one like this is kind of gross. m_core.get_master_node_list().access_proof(mn_info.pubkey, [&](const auto& proof) { - if (proof.proof->public_ip != 0) + if (m_core.master_node() && m_core.get_master_keys().pub == mn_info.pubkey) { + // When returning our own info we always want to return the most current data because the + // data from the MN list could be stale (it only gets updated when we get verification of + // acceptance of our proof from the network). The rest of the network might not get the + // updated data until the next proof, but local callers like SS and Belnet want it updated + // immediately. set_if_requested(reqed, entry, - "master_node_version", proof.proof->version, - "belnet_version", proof.proof->belnet_version, - "storage_server_version", proof.proof->storage_server_version, - "public_ip", epee::string_tools::get_ip_string_from_int32(proof.proof->public_ip), - "storage_port", proof.proof->storage_https_port, - "storage_lmq_port", proof.proof->storage_omq_port, - "quorumnet_port", proof.proof->qnet_port); - if (proof.proof->pubkey_ed25519) + "master_node_version", BELDEX_VERSION, + "belnet_version", m_core.belnet_version, + "storage_server_version", m_core.ss_version, + "public_ip", epee::string_tools::get_ip_string_from_int32(m_core.mn_public_ip()), + "storage_port", m_core.storage_https_port(), + "storage_lmq_port", m_core.storage_omq_port(), + "quorumnet_port", m_core.quorumnet_port()); set_if_requested(reqed, binary, - "pubkey_ed25519", proof.proof->pubkey_ed25519, - "pubkey_x25519", proof.pubkey_x25519); + "pubkey_ed25519", m_core.get_master_keys().pub_ed25519, + "pubkey_x25519", m_core.get_master_keys().pub_x25519); + } else { + if (proof.proof->public_ip != 0) + set_if_requested(reqed, entry, + "master_node_version", proof.proof->version, + "belnet_version", proof.proof->belnet_version, + "storage_server_version", proof.proof->storage_server_version, + "public_ip", epee::string_tools::get_ip_string_from_int32(proof.proof->public_ip), + "storage_port", proof.proof->storage_https_port, + "storage_lmq_port", proof.proof->storage_omq_port, + "quorumnet_port", proof.proof->qnet_port); + if (proof.proof->pubkey_ed25519) + set_if_requested(reqed, binary, + "pubkey_ed25519", proof.proof->pubkey_ed25519, + "pubkey_x25519", proof.pubkey_x25519); + } auto system_now = std::chrono::system_clock::now(); auto steady_now = std::chrono::steady_clock::now(); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 98d6ed35e54..47107c44b4f 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -10,49 +10,42 @@ namespace cryptonote::rpc { inline void parse_request(NO_ARGS&, rpc_input) {}; - void parse_request(BNS_RESOLVE& bns, rpc_input in); - void parse_request(GET_MASTER_NODES& mns, rpc_input in); - void parse_request(START_MINING& start_mining, rpc_input in); - void parse_request(STOP_MINING& stop_mining, rpc_input in); - void parse_request(MINING_STATUS& mining_status, rpc_input in); - void parse_request(GET_TRANSACTION_POOL_STATS& get_transaction_pool_stats, rpc_input in); - void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in); - void parse_request(GET_BLOCK_COUNT& getblockcount, rpc_input in); - void parse_request(STOP_DAEMON& stop_daemon, rpc_input in); - void parse_request(SAVE_BC& save_bc, rpc_input in); + void parse_request(BANNED& banned, rpc_input in); + void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); + void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); + void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in); + void parse_request(GET_BLOCK& get_block, rpc_input in); + void parse_request(GET_BLOCK_HASH& bh, rpc_input in); + void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); + void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); + void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); + void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in); + void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); + void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); + void parse_request(GET_PEER_LIST& bh, rpc_input in); + void parse_request(GET_MASTER_NODES& sns, rpc_input in); + // void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); + // void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); + void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); + void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); void parse_request(GET_TRANSACTIONS& get, rpc_input in); + void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); - void parse_request(SET_LIMIT& limit, rpc_input in); + void parse_request(IN_PEERS& in_peers, rpc_input in); void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); - void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); - void parse_request(GET_BLOCK_HASH& bh, rpc_input in); - void parse_request(GET_PEER_LIST& bh, rpc_input in); - void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); - void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); - void parse_request(BANNED& banned, rpc_input in); - void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); - void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in); - void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in); + void parse_request(BELNET_PING& belnet_ping, rpc_input in); + void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in); + void parse_request(BNS_RESOLVE& bns, rpc_input in); void parse_request(OUT_PEERS& out_peers, rpc_input in); - void parse_request(IN_PEERS& in_peers, rpc_input in); void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); - void parse_request(BELNET_PING& belnet_ping, rpc_input in); - void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); - void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); - void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); - void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); - void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); - void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); - void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(SETBANS& set_bans, rpc_input in); - void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); - void parse_request(RELAY_TX& relay_tx, rpc_input in); - void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); - void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); - void parse_request(GET_BLOCK& get_block, rpc_input in); - void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); - void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in); + void parse_request(SET_LIMIT& limit, rpc_input in); + void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); + void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); + void parse_request(START_MINING& start_mining, rpc_input in); + void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); + void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 494d6843e84..3b0cd5d7b25 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1596,7 +1596,7 @@ namespace cryptonote::rpc { /// - \p master_node_pubkey The queried daemon's master node public key. Will be empty if not running as a master node. /// - \p master_node_ed25519_pubkey The daemon's ed25519 auxiliary public key. /// - \p master_node_x25519_pubkey The daemon's x25519 auxiliary public key. - struct GET_MASTER_KEYS : RPC_COMMAND + struct GET_MASTER_KEYS : NO_ARGS { static constexpr auto names() { return NAMES("get_master_keys", "get_master_node_key"); } }; @@ -1614,7 +1614,7 @@ namespace cryptonote::rpc { /// - \p master_node_privkey The queried daemon's master node private key. Will be empty if not running as a master node. /// - \p master_node_ed25519_privkey The daemon's ed25519 private key (note that this is in sodium's format, which consists of the private and public keys concatenated together) /// - \p master_node_x25519_privkey The daemon's x25519 private key. - struct GET_MASTER_PRIVKEYS : RPC_COMMAND + struct GET_MASTER_PRIVKEYS : NO_ARGS { static constexpr auto names() { return NAMES("get_master_privkeys", "get_master_node_privkey"); } }; @@ -2284,52 +2284,52 @@ namespace cryptonote::rpc { /// ::response. The ::request has to be unique (for overload resolution); /// ::response does not. using core_rpc_types = tools::type_list< + BANNED, + FLUSH_CACHE, + FLUSH_TRANSACTION_POOL, + GET_BASE_FEE_ESTIMATE, + GET_BLOCK_COUNT, + GET_BLOCK_HASH, + GET_COINBASE_TX_SUM, GET_CONNECTIONS, GET_HEIGHT, GET_INFO, - BNS_RESOLVE, - GET_OUTPUTS, GET_LIMIT, - SET_LIMIT, - HARD_FORK_INFO, - START_MINING, - STOP_MINING, - SAVE_BC, - STOP_DAEMON, - SYNC_INFO, - GET_BLOCK_COUNT, - MINING_STATUS, - GET_TRANSACTION_POOL_HASHES, - GET_TRANSACTION_POOL_STATS, - GET_TRANSACTIONS, - IS_KEY_IMAGE_SPENT, + GET_OUTPUTS, + GET_PEER_LIST, + GET_MASTER_KEYS, GET_MASTER_NODES, + GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, GET_MASTER_NODE_STATUS, - SUBMIT_TRANSACTION, - GET_BLOCK_HASH, - GET_PEER_LIST, - GET_MASTER_NODE_REGISTRATION_CMD_RAW, - GET_MASTER_NODE_REGISTRATION_CMD, - SET_LOG_LEVEL, - SET_LOG_CATEGORIES, - BANNED, - FLUSH_TRANSACTION_POOL, + GET_MASTER_PRIVKEYS, + GET_MN_STATE_CHANGES, + GET_TRANSACTIONS, + GET_TRANSACTION_POOL_HASHES, + GET_TRANSACTION_POOL_STATS, GET_VERSION, - GET_COINBASE_TX_SUM, - GET_BASE_FEE_ESTIMATE, - OUT_PEERS, + HARD_FORK_INFO, IN_PEERS, - POP_BLOCKS, - STORAGE_SERVER_PING, + IS_KEY_IMAGE_SPENT, BELNET_PING, + MINING_STATUS, + BNS_OWNERS_TO_NAMES, + BNS_RESOLVE, + OUT_PEERS, + POP_BLOCKS, PRUNE_BLOCKCHAIN, - TEST_TRIGGER_P2P_RESYNC, - TEST_TRIGGER_UPTIME_PROOF, REPORT_PEER_STATUS, - GET_MN_STATE_CHANGES, - FLUSH_CACHE, - GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, - BNS_OWNERS_TO_NAMES + SAVE_BC, + SET_LIMIT, + SET_LOG_CATEGORIES, + SET_LOG_LEVEL, + START_MINING, + STOP_DAEMON, + STOP_MINING, + STORAGE_SERVER_PING, + SUBMIT_TRANSACTION, + SYNC_INFO, + TEST_TRIGGER_P2P_RESYNC, + TEST_TRIGGER_UPTIME_PROOF >; using FIXME_old_rpc_types = tools::type_list< GET_NET_STATS, @@ -2338,7 +2338,6 @@ namespace cryptonote::rpc { GET_BLOCK_HEADER_BY_HEIGHT, GET_BLOCK, GET_BLOCK_HEADERS_RANGE, - // SET_BOOTSTRAP_DAEMON, GETBANS, SETBANS, GET_OUTPUT_HISTOGRAM, @@ -2346,8 +2345,8 @@ namespace cryptonote::rpc { RELAY_TX, GET_OUTPUT_DISTRIBUTION, GET_QUORUM_STATE, - GET_MASTER_KEYS, - GET_MASTER_PRIVKEYS, + GET_MASTER_NODE_REGISTRATION_CMD_RAW, + GET_MASTER_NODE_REGISTRATION_CMD, GET_STAKING_REQUIREMENT, GET_CHECKPOINTS, BNS_NAMES_TO_OWNERS, From c94803cf321b63fd85e7cbca19c286514319e8d2 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 15:36:27 +0530 Subject: [PATCH 115/182] Activate various RPC endpoints --- src/daemon/command_parser_executor.cpp | 14 +- src/daemon/rpc_command_executor.cpp | 4 +- src/rpc/core_rpc_server.cpp | 16 +- src/rpc/core_rpc_server.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 186 ++++++++++++--------- src/rpc/core_rpc_server_command_parser.h | 4 +- src/rpc/core_rpc_server_commands_defs.cpp | 8 +- src/rpc/core_rpc_server_commands_defs.h | 48 +++--- 8 files changed, 161 insertions(+), 123 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 57a4b7e5506..0c50bd687f3 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -42,13 +42,23 @@ namespace daemonize { +template +constexpr bool is_std_optional = false; +template +inline constexpr bool is_std_optional> = true; + // Consumes an argument from the given list, if present, parsing it into `var`. // Returns false upon parse failure, true otherwise. template -static bool parse_if_present(std::forward_list &list, T &var, const char *name) +static bool parse_if_present(std::forward_list& list, T& var, const char* name) { if (list.empty()) return true; - if (epee::string_tools::get_xtype_from_string(var, list.front())) + bool good = false; + if constexpr (is_std_optional) + good = epee::string_tools::get_xtype_from_string(var.emplace(), list.front()); + else + good = epee::string_tools::get_xtype_from_string(var, list.front()); + if (good) { list.pop_front(); return true; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 96336b18017..5eb4d1e1c11 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1175,7 +1175,7 @@ bool rpc_command_executor::in_peers(bool set, uint32_t limit) bool rpc_command_executor::print_bans() { - auto maybe_bans = try_running([this] { return invoke(); }, "Failed to retrieve ban list"); + auto maybe_bans = try_running([this] { return invoke(); }, "Failed to retrieve ban list"); if (!maybe_bans) return false; auto bans = *maybe_bans; @@ -1195,7 +1195,7 @@ bool rpc_command_executor::print_bans() bool rpc_command_executor::ban(const std::string &address, time_t seconds, bool clear_ban) { - auto maybe_banned = try_running([this, &address, seconds, clear_ban] { return invoke(json{{"host", std::move(address)}, {"ip", 0}, {"seconds", seconds}, {"ban", !clear_ban}}); }, clear_ban ? "Failed to clear ban" : "Failed to set ban"); + auto maybe_banned = try_running([this, &address, seconds, clear_ban] { return invoke(json{{"host", std::move(address)}, {"ip", 0}, {"seconds", seconds}, {"ban", !clear_ban}}); }, clear_ban ? "Failed to clear ban" : "Failed to set ban"); if (!maybe_banned) return false; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b7ca94e7f10..666d10ae113 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1838,7 +1838,7 @@ namespace cryptonote::rpc { hfinfo.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GETBANS& get_bans, rpc_context context) + void core_rpc_server::invoke(GET_BANS& get_bans, rpc_context context) { PERF_TIMER(on_get_bans); @@ -1897,7 +1897,7 @@ namespace cryptonote::rpc { banned.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(SETBANS& set_bans, rpc_context context) + void core_rpc_server::invoke(SET_BANS& set_bans, rpc_context context) { PERF_TIMER(on_set_bans); @@ -3076,16 +3076,8 @@ namespace cryptonote::rpc { std::vector blocks; const auto& db = m_core.get_blockchain_storage(); - const uint64_t current_height = db.get_current_blockchain_height(); - - uint64_t end_height; - uint64_t start_height = get_mn_state_changes.request.start_height; - if (get_mn_state_changes.request.end_height == GET_MN_STATE_CHANGES::HEIGHT_SENTINEL_VALUE) { - // current height is the block being mined, so exclude it from the results - end_height = current_height - 1; - } else { - end_height = get_mn_state_changes.request.end_height; - } + auto start_height = get_mn_state_changes.request.start_height; + auto end_height = get_mn_state_changes.request.end_height.value_or(db.get_current_blockchain_height() - 1); if (end_height < start_height) throw rpc_error{ERROR_WRONG_PARAM, "The provided end_height needs to be higher than start_height"}; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 592894154a9..15c6e0d1df6 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -175,8 +175,8 @@ namespace cryptonote::rpc { void invoke(FLUSH_CACHE& flush_cache, rpc_context context); void invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context); void invoke(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_context context); - void invoke(GETBANS& get_bans, rpc_context context); - void invoke(SETBANS& set_bans, rpc_context context); + void invoke(GET_BANS& get_bans, rpc_context context); + void invoke(SET_BANS& set_bans, rpc_context context); void invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context); void invoke(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_context context); void invoke(GET_MASTER_KEYS& get_master_keys, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 37e06290b04..8d8071e9fd2 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -13,7 +13,9 @@ namespace cryptonote::rpc { void parse_request(GET_MASTER_NODES& mns, rpc_input in) { // Remember: key access must be in sorted order (even across get_values() calls). - get_values(in, "active_only", mns.request.active_only); + get_values(in, + "active_only", mns.request.active_only); + bool fields_dict = false; if (auto* json_in = std::get_if(&in)) { // Deprecated {"field":true, "field2":true, ...} handling: @@ -32,7 +34,9 @@ namespace cryptonote::rpc { } if (!fields_dict) { std::vector fields; - get_values(in, "fields", fields); + get_values(in, + "fields", fields); + for (const auto& f : fields) mns.request.fields.emplace(f); // If the only thing given is "all" then just clear it (as a small optimization): @@ -91,13 +95,15 @@ namespace cryptonote::rpc { } void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in) { - get_values(in, "include_unrelayed", pstats.request.include_unrelayed); + get_values(in, + "include_unrelayed", pstats.request.include_unrelayed); } void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in) { get_values(in, "height", hfinfo.request.height, "version", hfinfo.request.version); + if (hfinfo.request.height && hfinfo.request.version) throw std::runtime_error{"Error: at most one of 'height'" + std::to_string(hfinfo.request.height) + "/" + std::to_string(hfinfo.request.version) + " and 'version' may be specified"}; } @@ -110,12 +116,12 @@ namespace cryptonote::rpc { std::optional data; get_values(in, - "data", data, - "memory_pool", get.request.memory_pool, - "prune", get.request.prune, - "split", get.request.split, - "tx_extra", get.request.tx_extra, - "tx_hashes", get.request.tx_hashes); + "data", data, + "memory_pool", get.request.memory_pool, + "prune", get.request.prune, + "split", get.request.split, + "tx_extra", get.request.tx_extra, + "tx_hashes", get.request.tx_hashes); if (data) get.request.data = *data; @@ -130,6 +136,7 @@ namespace cryptonote::rpc { get_values(in, "limit_down", limit.request.limit_down, "limit_up", limit.request.limit_up); + if (limit.request.limit_down < -1) throw std::domain_error{"limit_down must be >= -1"}; if (limit.request.limit_down < -1) @@ -137,7 +144,8 @@ namespace cryptonote::rpc { } void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in){ - get_values(in, "key_images", spent.request.key_images); + get_values(in, + "key_images", spent.request.key_images); } void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in) { @@ -202,150 +210,178 @@ namespace cryptonote::rpc { } void parse_request(GET_PEER_LIST& pl, rpc_input in) { - get_values(in, "public_only", pl.request.public_only); + get_values(in, + "public_only", pl.request.public_only); } void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in) { - get_values(in, "level", set_log_level.request.level); + get_values(in, + "level", set_log_level.request.level); } void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in) { - get_values(in, "categories", set_log_categories.request.categories); + get_values(in, + "categories", set_log_categories.request.categories); } void parse_request(BANNED& banned, rpc_input in) { - get_values(in, "address", banned.request.address); + get_values(in, + "address", banned.request.address); } void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in) { - get_values(in, "txids", flush_transaction_pool.request.txids); + get_values(in, + "txids", flush_transaction_pool.request.txids); } void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in) { - get_values(in, "height", get_coinbase_tx_sum.request.height); - get_values(in, "count", get_coinbase_tx_sum.request.count); + get_values(in, + "height", get_coinbase_tx_sum.request.height, + "count", get_coinbase_tx_sum.request.count); } void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in) { - get_values(in, "grace_blocks", get_base_fee_estimate.request.grace_blocks); + get_values(in, + "grace_blocks", get_base_fee_estimate.request.grace_blocks); } void parse_request(OUT_PEERS& out_peers, rpc_input in){ - get_values(in, "set", out_peers.request.set); - get_values(in, "out_peers", out_peers.request.out_peers); + get_values(in, + "set", out_peers.request.set, + "out_peers", out_peers.request.out_peers); } void parse_request(IN_PEERS& in_peers, rpc_input in){ - get_values(in, "set", in_peers.request.set); - get_values(in, "in_peers", in_peers.request.in_peers); + get_values(in, + "set", in_peers.request.set, + "in_peers", in_peers.request.in_peers); } void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ - get_values(in, "nblocks", pop_blocks.request.nblocks); + get_values(in, + "nblocks", pop_blocks.request.nblocks); } void parse_request(BELNET_PING& belnet_ping, rpc_input in){ - get_values(in, "version", belnet_ping.request.version); - get_values(in, "pubkey_ed25519", belnet_ping.request.pubkey_ed25519); + get_values(in, + "version", belnet_ping.request.version, + "pubkey_ed25519", belnet_ping.request.pubkey_ed25519); } void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ - get_values(in, "version", storage_server_ping.request.version); - get_values(in, "https_port", storage_server_ping.request.https_port); - get_values(in, "omq_port", storage_server_ping.request.omq_port); - get_values(in, "pubkey_ed25519", storage_server_ping.request.pubkey_ed25519); + get_values(in, + "version", storage_server_ping.request.version, + "https_port", storage_server_ping.request.https_port, + "omq_port", storage_server_ping.request.omq_port, + "pubkey_ed25519", storage_server_ping.request.pubkey_ed25519); } void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ - get_values(in, "check", prune_blockchain.request.check); + get_values(in, + "check", prune_blockchain.request.check); } void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in) { - get_values(in, "type", report_peer_status.request.type); - get_values(in, "pubkey", report_peer_status.request.pubkey); - get_values(in, "passed", report_peer_status.request.passed); + get_values(in, + "type", report_peer_status.request.type, + "pubkey", report_peer_status.request.pubkey, + "passed", report_peer_status.request.passed); } void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in) { - get_values(in, "start_height", get_mn_state_changes.request.start_height); - get_values(in, "end_height", get_mn_state_changes.request.end_height); + get_values(in, + "start_height", get_mn_state_changes.request.start_height, + "end_height", required{get_mn_state_changes.request.end_height}); } void parse_request(FLUSH_CACHE& flush_cache, rpc_input in) { - get_values(in, "bad_txs", flush_cache.request.bad_txs); - get_values(in, "bad_blocks", flush_cache.request.bad_blocks); + get_values(in, + "bad_txs", flush_cache.request.bad_txs, + "bad_blocks", flush_cache.request.bad_blocks); } void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in) { - get_values(in, "fill_pow_hash", get_last_block_header.request.fill_pow_hash); - get_values(in, "get_tx_hashes", get_last_block_header.request.get_tx_hashes); + get_values(in, + "fill_pow_hash", get_last_block_header.request.fill_pow_hash, + "get_tx_hashes", get_last_block_header.request.get_tx_hashes); } void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in) { - get_values(in, "hash", get_block_header_by_hash.request.hash); - get_values(in, "hashes", get_block_header_by_hash.request.hashes); - get_values(in, "fill_pow_hash", get_block_header_by_hash.request.fill_pow_hash); - get_values(in, "get_tx_hashes", get_block_header_by_hash.request.get_tx_hashes); + get_values(in, + "hash", get_block_header_by_hash.request.hash, + "hashes", get_block_header_by_hash.request.hashes, + "fill_pow_hash", get_block_header_by_hash.request.fill_pow_hash, + "get_tx_hashes", get_block_header_by_hash.request.get_tx_hashes); } - void parse_request(SETBANS& set_bans, rpc_input in) { - get_values(in, "host", set_bans.request.host); - get_values(in, "ip", set_bans.request.ip); - get_values(in, "seconds", set_bans.request.seconds); - get_values(in, "ban", set_bans.request.ban); + void parse_request(SET_BANS& set_bans, rpc_input in) { + get_values(in, + "host", set_bans.request.host, + "ip", set_bans.request.ip, + "seconds", set_bans.request.seconds, + "ban", set_bans.request.ban); } void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in) { - get_values(in, "height", get_staking_requirement.request.height); + get_values(in, + "height", get_staking_requirement.request.height); } void parse_request(RELAY_TX& relay_tx, rpc_input in){ - get_values(in, "txids", relay_tx.request.txids); + get_values(in, + "txids", relay_tx.request.txids); } void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { - get_values(in, "start_height", get_block_headers_range.request.start_height); - get_values(in, "end_height", get_block_headers_range.request.end_height); - get_values(in, "fill_pow_hash", get_block_headers_range.request.fill_pow_hash); - get_values(in, "get_tx_hashes", get_block_headers_range.request.get_tx_hashes); + get_values(in, + "start_height", get_block_headers_range.request.start_height, + "end_height", get_block_headers_range.request.end_height, + "fill_pow_hash", get_block_headers_range.request.fill_pow_hash, + "get_tx_hashes", get_block_headers_range.request.get_tx_hashes); } void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in) { - get_values(in, "height", get_block_header_by_height.request.height); - get_values(in, "heights", get_block_header_by_height.request.heights); - get_values(in, "fill_pow_hash", get_block_header_by_height.request.fill_pow_hash); - get_values(in, "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes); + get_values(in, + "height", get_block_header_by_height.request.height, + "heights", get_block_header_by_height.request.heights, + "fill_pow_hash", get_block_header_by_height.request.fill_pow_hash, + "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes); } void parse_request(GET_BLOCK& get_block, rpc_input in) { - get_values(in, "hash", get_block.request.hash); - get_values(in, "height", get_block.request.height); - get_values(in, "fill_pow_hash", get_block.request.fill_pow_hash); + get_values(in, + "hash", get_block.request.hash, + "height", get_block.request.height, + "fill_pow_hash", get_block.request.fill_pow_hash); } void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in) { - get_values(in, "amounts", get_output_histogram.request.amounts); - get_values(in, "min_count", get_output_histogram.request.min_count); - get_values(in, "max_count", get_output_histogram.request.max_count); - get_values(in, "unlocked", get_output_histogram.request.unlocked); - get_values(in, "recent_cutoff", get_output_histogram.request.recent_cutoff); + get_values(in, + "amounts", get_output_histogram.request.amounts, + "min_count", get_output_histogram.request.min_count, + "max_count", get_output_histogram.request.max_count, + "unlocked", get_output_histogram.request.unlocked, + "recent_cutoff", get_output_histogram.request.recent_cutoff); } void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in) { - get_values(in, "entries", bns_owners_to_names.request.entries); - get_values(in, "include_expired", bns_owners_to_names.request.include_expired); + get_values(in, + "entries", bns_owners_to_names.request.entries, + "include_expired", bns_owners_to_names.request.include_expired); } void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in) { - get_values(in, "contributor_addresses", cmd.request.contributor_addresses); - get_values(in, "contributor_amounts", cmd.request.contributor_amounts); - get_values(in, "operator_cut", cmd.request.operator_cut); - get_values(in, "staking_requirement", cmd.request.staking_requirement); + get_values(in, + "contributor_addresses", cmd.request.contributor_addresses, + "contributor_amounts", cmd.request.contributor_amounts, + "operator_cut", cmd.request.operator_cut, + "staking_requirement", cmd.request.staking_requirement); } void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { - get_values(in, "args", cmd.request.args); - get_values(in, "make_friendly", cmd.request.make_friendly); - get_values(in, "staking_requirement", cmd.request.staking_requirement); + get_values(in, + "args", cmd.request.args, + "make_friendly", cmd.request.make_friendly, + "staking_requirement", cmd.request.staking_requirement); } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 47107c44b4f..57777f0b406 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -26,7 +26,7 @@ namespace cryptonote::rpc { void parse_request(GET_PEER_LIST& bh, rpc_input in); void parse_request(GET_MASTER_NODES& sns, rpc_input in); // void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); - // void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); void parse_request(GET_TRANSACTIONS& get, rpc_input in); @@ -41,7 +41,7 @@ namespace cryptonote::rpc { void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); - void parse_request(SETBANS& set_bans, rpc_input in); + void parse_request(SET_BANS& set_bans, rpc_input in); void parse_request(SET_LIMIT& limit, rpc_input in); void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 22dc71016d6..b3ad583c948 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -741,20 +741,20 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::ban) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BANS::ban) // KV_SERIALIZE(host) // KV_SERIALIZE(ip) // KV_SERIALIZE(seconds) // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(GETBANS::response) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BANS::response) // KV_SERIALIZE(status) // KV_SERIALIZE(bans) // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::ban) +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_BANS::ban) // KV_SERIALIZE(host) // KV_SERIALIZE(ip) // KV_SERIALIZE(ban) @@ -762,7 +762,7 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(SETBANS::request) +// KV_SERIALIZE_MAP_CODE_BEGIN(SET_BANS::request) // KV_SERIALIZE(bans) // KV_SERIALIZE_MAP_CODE_END() diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 3b0cd5d7b25..da3e13c1619 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1132,7 +1132,7 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p bans List of banned nodes - struct GETBANS : RPC_COMMAND + struct GET_BANS : RPC_COMMAND { static constexpr auto names() { return NAMES("get_bans"); } }; @@ -1156,7 +1156,7 @@ namespace cryptonote::rpc { /// Output values available from a restricted/admin RPC endpoint: /// /// - \p status General RPC status string. `"OK"` means everything looks good. - struct SETBANS : RPC_COMMAND + struct SET_BANS : RPC_COMMAND { static constexpr auto names() { return NAMES("set_bans"); } @@ -1335,7 +1335,7 @@ namespace cryptonote::rpc { /// - \p difficulty The cumulative difficulty of all blocks in the alternative chain. /// - \p block_hashes List containing hex block hashes /// - \p main_chain_parent_block - struct GET_ALTERNATE_CHAINS : RPC_COMMAND + struct GET_ALTERNATE_CHAINS : NO_ARGS { static constexpr auto names() { return NAMES("get_alternative_chains"); } @@ -1992,14 +1992,15 @@ namespace cryptonote::rpc { } request; }; - /// Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. + /// Query recent master node state changes processed on the blockchain. /// /// Inputs: /// - /// - \p start_height The starting block's height. - /// - \p end_height The ending block's height. + /// - \p start_height Returns counts starting from this block height. Required. + /// - \p end_height Optional: returns count ending at this block height; if omitted, counts to the + /// current height. /// - /// Output values available from a public RPC endpoint: + /// Output values available from a private/admin RPC endpoint: /// /// - \p status Generic RPC error code. "OK" is the success value. /// - \p untrusted If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. @@ -2010,15 +2011,14 @@ namespace cryptonote::rpc { /// - \p total_unlock /// - \p start_height /// - \p end_height - struct GET_MN_STATE_CHANGES : PUBLIC + struct GET_MN_STATE_CHANGES : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_nodes_state_changes"); } - static constexpr uint64_t HEIGHT_SENTINEL_VALUE = std::numeric_limits::max() - 1; struct request_parameters { uint64_t start_height; - uint64_t end_height; // Optional: If omitted, the tally runs until the current block + std::optional end_height; } request; }; @@ -2287,22 +2287,35 @@ namespace cryptonote::rpc { BANNED, FLUSH_CACHE, FLUSH_TRANSACTION_POOL, + GET_ALTERNATE_CHAINS, + GET_BANS, GET_BASE_FEE_ESTIMATE, + GET_BLOCK, GET_BLOCK_COUNT, GET_BLOCK_HASH, + GET_BLOCK_HEADERS_RANGE, + GET_BLOCK_HEADER_BY_HASH, + GET_BLOCK_HEADER_BY_HEIGHT, + GET_CHECKPOINTS, GET_COINBASE_TX_SUM, GET_CONNECTIONS, GET_HEIGHT, GET_INFO, + GET_LAST_BLOCK_HEADER, GET_LIMIT, + GET_NET_STATS, GET_OUTPUTS, + GET_OUTPUT_HISTOGRAM, GET_PEER_LIST, + GET_QUORUM_STATE, GET_MASTER_KEYS, GET_MASTER_NODES, GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, + GET_MASTER_NODE_REGISTRATION_CMD_RAW, GET_MASTER_NODE_STATUS, GET_MASTER_PRIVKEYS, GET_MN_STATE_CHANGES, + GET_STAKING_REQUIREMENT, GET_TRANSACTIONS, GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_STATS, @@ -2319,6 +2332,7 @@ namespace cryptonote::rpc { PRUNE_BLOCKCHAIN, REPORT_PEER_STATUS, SAVE_BC, + SET_BANS, SET_LIMIT, SET_LOG_CATEGORIES, SET_LOG_LEVEL, @@ -2332,23 +2346,9 @@ namespace cryptonote::rpc { TEST_TRIGGER_UPTIME_PROOF >; using FIXME_old_rpc_types = tools::type_list< - GET_NET_STATS, - GET_LAST_BLOCK_HEADER, - GET_BLOCK_HEADER_BY_HASH, - GET_BLOCK_HEADER_BY_HEIGHT, - GET_BLOCK, - GET_BLOCK_HEADERS_RANGE, - GETBANS, - SETBANS, - GET_OUTPUT_HISTOGRAM, - GET_ALTERNATE_CHAINS, RELAY_TX, GET_OUTPUT_DISTRIBUTION, - GET_QUORUM_STATE, - GET_MASTER_NODE_REGISTRATION_CMD_RAW, GET_MASTER_NODE_REGISTRATION_CMD, - GET_STAKING_REQUIREMENT, - GET_CHECKPOINTS, BNS_NAMES_TO_OWNERS, BNS_LOOKUP, BNS_VALUE_DECRYPT From c13672e2ed804e6e25b7dc9fc2717320f8a3694c Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 15:47:16 +0530 Subject: [PATCH 116/182] stringop-overflow warnings under GCC12 and re-add GET_TRANSACTION_POOL --- CMakeLists.txt | 6 ++++++ src/rpc/core_rpc_server_command_parser.cpp | 9 +++++++++ src/rpc/core_rpc_server_commands_defs.h | 16 ++++++++++++++++ src/rpc/lmq_server.cpp | 1 + 4 files changed, 32 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ec101e8045..6b8b0986b05 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -605,6 +605,12 @@ else() add_cxx_flag_if_supported(-fstack-clash-protection CXX_SECURITY_FLAGS) endif() + # GCC 12's stringop-overflow warnings are really broken, with tons and tons of false positives all + # over the place (not just in our code, but also in its own stdlibc++ code). + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 12 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) + set(CXX_WARNINGS "${CXX_WARNINGS} -Wno-stringop-overflow") + endif() + # Removed in GCC 9.1 (or before ?), but still accepted, so spams the output if (NOT CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_VERSION VERSION_LESS 9.1) add_c_flag_if_supported(-mmitigate-rop C_SECURITY_FLAGS) diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 8d8071e9fd2..175471dd079 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -132,6 +132,15 @@ namespace cryptonote::rpc { throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } + void parse_request(GET_TRANSACTION_POOL& get, rpc_input in) { + // Deprecated wrapper; GET_TRANSACTION_POOL is a no-member subclass of GET_TRANSACTIONS; it + // works identically, except that we force `memory_pool` to true. + parse_request(static_cast(get), std::move(in)); + if (!get.request.tx_hashes.empty()) + throw std::runtime_error{"Error: 'get_transaction_pool' does not support specifying 'tx_hashes'"}; + get.request.memory_pool = true; + } + void parse_request(SET_LIMIT& limit, rpc_input in) { get_values(in, "limit_down", limit.request.limit_down, diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index da3e13c1619..ce380409a20 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -295,7 +295,22 @@ namespace cryptonote::rpc { /// Like `split`, but also omits the prunable part of transactions from the response details. bool prune = false; } request; + }; + /// DEPRECATED. This endpoint is for backwards compatibility for old clients obtaining + /// transactions in the transaction pool. The replacement is to use `get_transactions` with + /// `"memory_pool": true`. + /// + /// Inputs: + /// + /// - Takes all the same inputs as get_transactions, except for `memory_pool` and `tx_hashes`. + /// + /// Outputs: + /// + /// - Same as get_transactions with `"memory_pool": true`. + struct GET_TRANSACTION_POOL : GET_TRANSACTIONS + { + static constexpr auto names() { return NAMES("get_transaction_pool"); } }; /// Queries whether outputs have been spent using the key image associated with the output. @@ -2317,6 +2332,7 @@ namespace cryptonote::rpc { GET_MN_STATE_CHANGES, GET_STAKING_REQUIREMENT, GET_TRANSACTIONS, + GET_TRANSACTION_POOL, GET_TRANSACTION_POOL_HASHES, GET_TRANSACTION_POOL_STATS, GET_VERSION, diff --git a/src/rpc/lmq_server.cpp b/src/rpc/lmq_server.cpp index 1834ce842ef..d0c9ff44525 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -227,6 +227,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // number instead of a JSON object. If you want to find some, `grep number2 epee` (for // real). MINFO("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); + MDEBUG("Bad request body:" << m.data.empty() ? "(empty)" : m.data[0]); m.send_reply(LMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); return; } catch (const rpc_error& e) { From a57e9c75479aefcf9c5f60d1abc67cf3b09e1285 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Fri, 2 May 2025 16:48:24 +0530 Subject: [PATCH 117/182] More RPC fixes --- src/daemon/command_parser_executor.cpp | 13 +- src/daemon/rpc_command_executor.cpp | 46 +++-- src/daemon/rpc_command_executor.h | 6 +- src/rpc/core_rpc_server.cpp | 207 +++++++++++---------- src/rpc/core_rpc_server_command_parser.cpp | 32 +++- src/rpc/core_rpc_server_command_parser.h | 2 + src/rpc/core_rpc_server_commands_defs.h | 20 +- 7 files changed, 183 insertions(+), 143 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 0c50bd687f3..032a37acd84 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -68,10 +68,9 @@ static bool parse_if_present(std::forward_list& list, T& var, const return false; } -bool command_parser_executor::print_checkpoints(const std::vector &args) +bool command_parser_executor::print_checkpoints(const std::vector& args) { - uint64_t start_height = cryptonote::rpc::GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE; - uint64_t end_height = cryptonote::rpc::GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE; + std::optional start_height, end_height; std::forward_list args_list(args.begin(), args.end()); bool print_json = !args_list.empty() && args_list.front() == "+json"; @@ -99,7 +98,7 @@ bool command_parser_executor::print_checkpoints(const std::vector & bool command_parser_executor::print_mn_state_changes(const std::vector &args) { uint64_t start_height; - uint64_t end_height = cryptonote::rpc::GET_MN_STATE_CHANGES::HEIGHT_SENTINEL_VALUE; + std::optional end_height; if (args.empty()) { std::cout << "Missing first argument start_height" << std::endl; @@ -113,8 +112,6 @@ bool command_parser_executor::print_mn_state_changes(const std::vector& args) { - uint64_t start_height = cryptonote::rpc::GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE; - uint64_t end_height = cryptonote::rpc::GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE; + std::optional start_height; + std::optional end_height; std::forward_list args_list(args.begin(), args.end()); if (!parse_if_present(args_list, start_height, "start height")) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 5eb4d1e1c11..2ed0cee3060 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -277,22 +277,24 @@ json rpc_command_executor::invoke( return result; } -bool rpc_command_executor::print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json) +bool rpc_command_executor::print_checkpoints(std::optional start_height, std::optional end_height, bool print_json) { uint32_t count; - if (start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE && - end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) - { + if (!start_height && !end_height) count = GET_CHECKPOINTS::NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT; - } - else if (start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE || - end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) - { + else if (!start_height || !end_height) count = 1; - } - // Otherwise, neither heights are set to HEIGHT_SENTINEL_VALUE, so get all the checkpoints between start and end + // Otherwise, both start/end are set so get all the checkpoints between start and end - auto maybe_checkpoints = try_running([&] { return invoke(json{{"start_height", start_height}, {"end_height", end_height}, {"count", count}}); }, "Failed to query blockchain checkpoints"); + auto maybe_checkpoints = try_running([&] { + json params{ + {"count", count} + }; + if (start_height) + params["start_height"] = *start_height; + if (end_height) + params["end_height"] = *end_height; + return invoke(std::move(params)); }, "Failed to query blockchain checkpoints"); if (!maybe_checkpoints) return false; @@ -326,9 +328,13 @@ bool rpc_command_executor::print_checkpoints(uint64_t start_height, uint64_t end return true; } -bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, uint64_t end_height) +bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, std::optional end_height) { - auto maybe_mn_state = try_running([&] { return invoke(json{{"start_height", start_height}, {"end_height", end_height}}); }, "Failed to query master nodes state changes"); + auto maybe_sn_state = try_running([&] { + json params{{"start_height", start_height}}; + if (end_height) + params["end_height"] = *end_height; + return invoke(std::move(params)); }, "Failed to query service node state changes"); if (!maybe_mn_state) return false; @@ -747,14 +753,16 @@ bool rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint return true; } -bool rpc_command_executor::print_quorum_state(uint64_t start_height, uint64_t end_height) +bool rpc_command_executor::print_quorum_state(std::optional start_height, std::optional end_height) { auto maybe_quorums = try_running([this, start_height, end_height] { - return invoke(json{ - {"start_height", start_height}, - {"end_height", end_height}, - {"quorum_type", GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE}}); - }, "Failed to retrieve quorum state"); + json params; + if (start_height) + params["start_height"] = *start_height; + if (end_height) + params["end_height"] = *end_height; + return invoke(std::move(params)); + }, "Failed to retrieve quorum state"); if (!maybe_quorums) return false; diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 1e8e9b0641a..cb87076e28b 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -149,9 +149,9 @@ class rpc_command_executor final { return invoke(params)["response"]; } - bool print_checkpoints(uint64_t start_height, uint64_t end_height, bool print_json); + bool print_checkpoints(std::optional start_height, std::optional end_height, bool print_json); - bool print_mn_state_changes(uint64_t start_height, uint64_t end_height); + bool print_mn_state_changes(uint64_t start_height, std::optional end_height); bool print_peer_list(bool white = true, bool gray = true, size_t limit = 0, bool pruned_only = false); @@ -167,7 +167,7 @@ class rpc_command_executor final { bool print_blockchain_info(int64_t start_block_index, uint64_t end_block_index); - bool print_quorum_state(uint64_t start_height, uint64_t end_height); + bool print_quorum_state(std::optional start_height, std::optional end_height); bool set_log_level(int8_t level); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 666d10ae113..05e12dd76c6 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -47,6 +47,7 @@ #include "cryptonote_config.h" #include "cryptonote_core/beldex_name_system.h" #include "cryptonote_core/pos.h" +#include "cryptonote_core/service_node_rules.h" #include "beldex_economy.h" #include "epee/string_tools.h" #include "core_rpc_server.h" @@ -1506,7 +1507,14 @@ namespace cryptonote::rpc { return reward; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash, bool get_tx_hashes) + void core_rpc_server::fill_block_header_response( + const block& blk, + bool orphan_status, + uint64_t height, + const crypto::hash& hash, + block_header_response& response, + bool fill_pow_hash, + bool get_tx_hashes) { PERF_TIMER(fill_block_header_response); response.major_version = static_cast(blk.major_version); @@ -1527,10 +1535,19 @@ namespace cryptonote::rpc { response.block_size = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height); response.num_txes = blk.tx_hashes.size(); if (fill_pow_hash) - response.pow_hash = tools::type_to_hex(get_block_longhash_w_blockchain(m_core.get_nettype(), &(m_core.get_blockchain_storage()), blk, height, 0)); + response.pow_hash = tools::type_to_hex( + get_block_longhash_w_blockchain( + m_core.get_nettype(), + &m_core.get_blockchain_storage(), + blk, + height, + 0)); response.long_term_weight = m_core.get_blockchain_storage().get_db().get_block_long_term_weight(height); response.miner_tx_hash = tools::type_to_hex(cryptonote::get_transaction_hash(blk.miner_tx)); - response.master_node_winner = tools::type_to_hex(cryptonote::get_master_node_winner_from_tx_extra(blk.miner_tx.extra)); + response.master_node_winner = + tools::type_to_hex(blk.service_node_winner_key) == "" + ? tools::type_to_hex(cryptonote::get_service_node_winner_from_tx_extra(blk.miner_tx.extra)) + : tools::type_to_hex(blk.service_node_winner_key); if (get_tx_hashes) { response.tx_hashes.reserve(blk.tx_hashes.size()); @@ -1698,8 +1715,8 @@ namespace cryptonote::rpc { uint64_t block_height = var::get(blk.miner_tx.vin.front()).height; if (block_height != h) throw rpc_error{ERROR_INTERNAL, "Internal error: coinbase transaction in the block has the wrong height"}; - headers.push_back(block_header_response()); - fill_block_header_response(blk, false, block_height, get_block_hash(blk), headers.back(), get_block_headers_range.request.fill_pow_hash && context.admin, get_block_headers_range.request.get_tx_hashes); + auto& hdr = headers.emplace_back(); + fill_block_header_response(blk, false, block_height, get_block_hash(blk), hdr, get_block_headers_range.request.fill_pow_hash && context.admin, get_block_headers_range.request.get_tx_hashes); } get_block_headers_range.response["headers"] = headers; get_block_headers_range.response["status"] = STATUS_OK; @@ -2447,24 +2464,18 @@ namespace cryptonote::rpc { { PERF_TIMER(on_get_quorum_state); - uint8_t quorum_type = get_quorum_state.request.quorum_type; + const auto& quorum_type = get_quorum_state.request.quorum_type; - if (quorum_type >= tools::enum_count && - quorum_type != GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE) - throw rpc_error{ERROR_WRONG_PARAM, - "Quorum type specifies an invalid value: " + std::to_string(get_quorum_state.request.quorum_type)}; - - auto requested_type = [quorum_type](master_nodes::quorum_type type) { - return quorum_type == GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE || - quorum_type == static_cast(type); + auto is_requested_type = [&quorum_type](master_nodes::quorum_type type) { + return !quorum_type || quorum_type == static_cast(type); }; bool latest = false; uint64_t latest_ob = 0, latest_cp = 0, latest_bl = 0; - uint64_t start = get_quorum_state.request.start_height, end = get_quorum_state.request.end_height; + auto& start = get_quorum_state.request.start_height; + auto& end = get_quorum_state.request.end_height; uint64_t curr_height = m_core.get_blockchain_storage().get_current_blockchain_height(); - if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE && - end == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE) + if (!start && !end) { latest = true; // Our start block for the latest quorum of each type depends on the type being requested: @@ -2474,36 +2485,33 @@ namespace cryptonote::rpc { // POS: current height (i.e. top block height + 1) uint64_t top_height = curr_height - 1; latest_ob = top_height; - latest_cp = std::min(start, top_height - top_height % master_nodes::CHECKPOINT_INTERVAL); - latest_bl = std::min(start, top_height - top_height % master_nodes::FLASH_QUORUM_INTERVAL); - if (requested_type(master_nodes::quorum_type::checkpointing)) - start = std::min(start, latest_cp); - if (requested_type(master_nodes::quorum_type::flash)) - start = std::min(start, latest_bl); + latest_cp = top_height - top_height % master_nodes::CHECKPOINT_INTERVAL; + latest_bl = top_height - top_height % master_nodes::BLINK_QUORUM_INTERVAL; + if (is_requested_type(master_nodes::quorum_type::checkpointing)) + start = latest_cp; + if (is_requested_type(master_nodes::quorum_type::blink)) + start = start ? std::min(*start, latest_bl) : latest_bl; end = curr_height; } - else if (start == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE) - { - start = end; - end = end + 1; - } - else if (end == GET_QUORUM_STATE::HEIGHT_SENTINEL_VALUE) - { - end = start + 1; - } - else - { - if (end > start) end++; - else if (end != 0) end--; - } + else if (!start) + start = (*end)++; + else if (!end) + end = *start + 1; + else if (*end > *start) + ++*end; + else if (end > 0) + --*end; + + if (!start || *start > curr_height) + start = curr_height; - start = std::min(curr_height, start); // We can also provide the POS quorum for the current block being produced, so if asked for // that make a note. - bool add_curr_POS = (latest || end > curr_height) && requested_type(master_nodes::quorum_type::POS); - end = std::min(curr_height, end); + bool add_curr_POS = (latest || end > curr_height) && is_requested_type(master_nodes::quorum_type::POS); + if (!end || *end > curr_height) + end = curr_height; - uint64_t count = (start > end) ? start - end : end - start; + uint64_t count = (*start > *end) ? *start - *end : *end - *start; if (!context.admin && count > GET_QUORUM_STATE::MAX_COUNT) throw rpc_error{ERROR_WRONG_PARAM, "Number of requested quorums greater than the allowed limit: " @@ -2514,53 +2522,48 @@ namespace cryptonote::rpc { std::vector quorums; quorums.reserve(std::min((uint64_t)16, count)); auto net = nettype(); - for (size_t height = start; height != end;) + for (size_t height = *start; height < *end; height++) { auto hf_version = get_network_version(net, height); + auto start_quorum_iterator = static_cast(0); + auto end_quorum_iterator = master_nodes::max_quorum_type_for_hf(hf_version); + + if (quorum_type) { - auto start_quorum_iterator = static_cast(0); - auto end_quorum_iterator = master_nodes::max_quorum_type_for_hf(hf_version); + start_quorum_iterator = static_cast(*quorum_type); + end_quorum_iterator = start_quorum_iterator; + } - if (quorum_type != GET_QUORUM_STATE::ALL_QUORUMS_SENTINEL_VALUE) - { - start_quorum_iterator = static_cast(quorum_type); - end_quorum_iterator = start_quorum_iterator; + for (int quorum_int = (int)start_quorum_iterator; quorum_int <= (int)end_quorum_iterator; quorum_int++) + { + auto type = static_cast(quorum_int); + if (latest) + { // Latest quorum requested, so skip if this is isn't the latest height for *this* quorum type + if (type == master_nodes::quorum_type::obligations && height != latest_ob) continue; + if (type == master_nodes::quorum_type::checkpointing && height != latest_cp) continue; + if (type == master_nodes::quorum_type::blink && height != latest_bl) continue; + if (type == master_nodes::quorum_type::pulse) continue; } - - for (int quorum_int = (int)start_quorum_iterator; quorum_int <= (int)end_quorum_iterator; quorum_int++) + if (std::shared_ptr quorum = m_core.get_quorum(type, height, true /*include_old*/)) { - auto type = static_cast(quorum_int); - if (latest) - { // Latest quorum requested, so skip if this is isn't the latest height for *this* quorum type - if (type == master_nodes::quorum_type::obligations && height != latest_ob) continue; - if (type == master_nodes::quorum_type::checkpointing && height != latest_cp) continue; - if (type == master_nodes::quorum_type::flash && height != latest_bl) continue; - if (type == master_nodes::quorum_type::POS) continue; - } - if (std::shared_ptr quorum = m_core.get_quorum(type, height, true /*include_old*/)) - { - auto& entry = quorums.emplace_back(); - entry.height = height; - entry.quorum_type = static_cast(quorum_int); - entry.quorum.validators = hexify(quorum->validators); - entry.quorum.workers = hexify(quorum->workers); + auto& entry = quorums.emplace_back(); + entry.height = height; + entry.quorum_type = static_cast(quorum_int); + entry.quorum.validators = hexify(quorum->validators); + entry.quorum.workers = hexify(quorum->workers); - at_least_one_succeeded = true; - } + at_least_one_succeeded = true; } } - - if (end >= start) height++; - else height--; } if (auto hf_version = get_network_version(nettype(), curr_height); add_curr_POS && hf_version >= hf::hf17_POS) { - cryptonote::Blockchain const &blockchain = m_core.get_blockchain_storage(); - cryptonote::block_header const &top_header = blockchain.get_db().get_block_header_from_height(curr_height - 1); + const auto& blockchain = m_core.get_blockchain_storage(); + const auto& top_header = blockchain.get_db().get_block_header_from_height(curr_height - 1); - POS::timings next_timings = {}; - uint8_t POS_round = 0; + POS::timings next_timings{}; + uint8_t POS_round = 0; if (POS::get_round_timings(blockchain, curr_height, top_header.timestamp, next_timings) && POS::convert_time_to_round(POS::clock::now(), next_timings.r0_timestamp, &POS_round)) { @@ -3023,48 +3026,52 @@ namespace cryptonote::rpc { return; } //------------------------------------------------------------------------------------------------------------------------------ - static void check_quantity_limit(size_t count, size_t max, char const *container_name = nullptr) + + template + static void check_quantity_limit(T count, T max, const char* container_name = "input") { if (count > max) - { - std::ostringstream err; - err << "Number of requested entries"; - if (container_name) err << " in " << container_name; - err << " greater than the allowed limit: " << max << ", requested: " << count; - throw rpc_error{ERROR_WRONG_PARAM, err.str()}; - } + throw rpc_error{ERROR_WRONG_PARAM, + "Number of requested entries ({}) in {} is greater than the allowed limit ({})"_format( + count, container_name, max)}; + } + + template + static void check_quantity_limit(std::optional count, T max, const char* name = "input") { + if (count) + check_quantity_limit(*count, max, name); } + //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_CHECKPOINTS& get_checkpoints, rpc_context context) { if (!context.admin) check_quantity_limit(get_checkpoints.request.count, GET_CHECKPOINTS::MAX_COUNT); + auto& start = get_checkpoints.request.start_height; + auto& end = get_checkpoints.request.end_height; + auto count = get_checkpoints.request.count.value_or(GET_CHECKPOINTS::NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT); + get_checkpoints.response["status"] = STATUS_OK; - BlockchainDB const &db = m_core.get_blockchain_storage().get_db(); + const auto& db = m_core.get_blockchain_storage().get_db(); std::vector checkpoints; - if (get_checkpoints.request.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE && - get_checkpoints.request.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) - { - checkpoint_t top_checkpoint; - if (db.get_top_checkpoint(top_checkpoint)) - checkpoints = db.get_checkpoints_range(top_checkpoint.height, 0, get_checkpoints.request.count); - } - else if (get_checkpoints.request.start_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) + if (!start && !end) { - checkpoints = db.get_checkpoints_range(get_checkpoints.request.end_height, 0, get_checkpoints.request.count); - } - else if (get_checkpoints.request.end_height == GET_CHECKPOINTS::HEIGHT_SENTINEL_VALUE) - { - checkpoints = db.get_checkpoints_range(get_checkpoints.request.start_height, UINT64_MAX, get_checkpoints.request.count); + if (checkpoint_t top_checkpoint; db.get_top_checkpoint(top_checkpoint)) + checkpoints = db.get_checkpoints_range(top_checkpoint.height, 0, count); } + else if (!start) + checkpoints = db.get_checkpoints_range(*end, 0, count); + else if (!end) + checkpoints = db.get_checkpoints_range(*start, UINT64_MAX, count); else - { - checkpoints = db.get_checkpoints_range(get_checkpoints.request.start_height, get_checkpoints.request.end_height); - } + checkpoints = + context.admin + ? db.get_checkpoints_range(*start, *end) + : db.get_checkpoints_range(*start, *end, GET_CHECKPOINTS::MAX_COUNT); - get_checkpoints.response["checkpoints"] = checkpoints; + get_checkpoints.response["checkpoints"] = std::move(checkpoints); return; } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 175471dd079..282e4205c52 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -379,6 +379,36 @@ namespace cryptonote::rpc { "include_expired", bns_owners_to_names.request.include_expired); } + void parse_request(GET_QUORUM_STATE& qs, rpc_input in) { + + get_values(in, + "end_height", qs.request.end_height, + "quorum_type", qs.request.quorum_type, + "start_height", qs.request.start_height); + + if (qs.request.quorum_type) { + if (*qs.request.quorum_type == 255) // backwards-compat magic value + qs.request.quorum_type = std::nullopt; + else if (*qs.request.quorum_type > tools::enum_count) + throw std::domain_error{ + "Quorum type specifies an invalid value: "_format(*qs.request.quorum_type)}; + } + } + + void parse_request(GET_CHECKPOINTS& getcp, rpc_input in) { + get_values(in, + "count", getcp.request.count, + "end_height", getcp.request.end_height, + "start_height", getcp.request.start_height); + } + + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { + get_values(in, + "args", cmd.request.args, + "make_friendly", cmd.request.make_friendly, + "staking_requirement", cmd.request.staking_requirement); + } + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in) { get_values(in, "contributor_addresses", cmd.request.contributor_addresses, @@ -392,5 +422,5 @@ namespace cryptonote::rpc { "args", cmd.request.args, "make_friendly", cmd.request.make_friendly, "staking_requirement", cmd.request.staking_requirement); -} + } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 57777f0b406..e9130c7aef5 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -19,7 +19,9 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in); void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in); + void parse_request(GET_CHECKPOINTS& getcp, rpc_input in); void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in); + void parse_request(GET_QUORUM_STATE& get_quorum_state, rpc_input in); void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in); void parse_request(GET_OUTPUTS& get_outputs, rpc_input in); void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ce380409a20..2f953b86f4b 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1147,7 +1147,7 @@ namespace cryptonote::rpc { /// /// - \p status General RPC status string. `"OK"` means everything looks good. /// - \p bans List of banned nodes - struct GET_BANS : RPC_COMMAND + struct GET_BANS : NO_ARGS { static constexpr auto names() { return NAMES("get_bans"); } }; @@ -1534,13 +1534,11 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_quorum_state"); } static constexpr size_t MAX_COUNT = 256; - static constexpr uint64_t HEIGHT_SENTINEL_VALUE = UINT64_MAX; - static constexpr uint8_t ALL_QUORUMS_SENTINEL_VALUE = 255; struct request_parameters { - uint64_t start_height; // (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. - uint64_t end_height; // (Optional): End height, omit both start and end height to request the latest quorum - uint8_t quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, 255 = all quorums, default is all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. + std::optional start_height; // (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. + std::optional end_height; // (Optional): End height, omit both start and end height to request the latest quorum + std::optional quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, omitted or 255 = all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. } request; struct quorum_t @@ -1564,7 +1562,6 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p check Instead of running check if the blockchain has already been pruned. /// - \p args (Developer) The list of arguments used in raw registration, i.e. portions /// - \p make_friendly Provide information about how to use the command in the result. /// - \p staking_requirement The staking requirement to become a Master Node the registration command will be generated upon @@ -1996,14 +1993,13 @@ namespace cryptonote::rpc { { static constexpr auto names() { return NAMES("get_checkpoints"); } - static constexpr size_t MAX_COUNT = 256; + static constexpr uint32_t MAX_COUNT = 256; static constexpr uint32_t NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT = 60; - static constexpr uint64_t HEIGHT_SENTINEL_VALUE = std::numeric_limits::max() - 1; struct request_parameters { - uint64_t start_height; // Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. - uint64_t end_height; // Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. - uint32_t count; // Optional: Number of checkpoints to query. + std::optional start_height; // Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. + std::optional end_height; // Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. + std::optional count; // Optional: Number of checkpoints to query. } request; }; From f3cb8f80c0d5dc3dcf09d365bdf409deca402824 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 17:44:14 +0530 Subject: [PATCH 118/182] fix: build --- src/daemon/rpc_command_executor.cpp | 4 ++-- src/rpc/core_rpc_server.cpp | 25 +++++++++++----------- src/rpc/core_rpc_server_command_parser.cpp | 11 ++-------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2ed0cee3060..575e918925d 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -330,11 +330,11 @@ bool rpc_command_executor::print_checkpoints(std::optional start_heigh bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, std::optional end_height) { - auto maybe_sn_state = try_running([&] { + auto maybe_mn_state = try_running([&] { json params{{"start_height", start_height}}; if (end_height) params["end_height"] = *end_height; - return invoke(std::move(params)); }, "Failed to query service node state changes"); + return invoke(std::move(params)); }, "Failed to query service node state changes"); if (!maybe_mn_state) return false; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 05e12dd76c6..61392ef2ad0 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -47,7 +47,7 @@ #include "cryptonote_config.h" #include "cryptonote_core/beldex_name_system.h" #include "cryptonote_core/pos.h" -#include "cryptonote_core/service_node_rules.h" +#include "cryptonote_core/master_node_rules.h" #include "beldex_economy.h" #include "epee/string_tools.h" #include "core_rpc_server.h" @@ -1544,10 +1544,7 @@ namespace cryptonote::rpc { 0)); response.long_term_weight = m_core.get_blockchain_storage().get_db().get_block_long_term_weight(height); response.miner_tx_hash = tools::type_to_hex(cryptonote::get_transaction_hash(blk.miner_tx)); - response.master_node_winner = - tools::type_to_hex(blk.service_node_winner_key) == "" - ? tools::type_to_hex(cryptonote::get_service_node_winner_from_tx_extra(blk.miner_tx.extra)) - : tools::type_to_hex(blk.service_node_winner_key); + response.master_node_winner = tools::type_to_hex(cryptonote::get_master_node_winner_from_tx_extra(blk.miner_tx.extra)); if (get_tx_hashes) { response.tx_hashes.reserve(blk.tx_hashes.size()); @@ -2486,10 +2483,10 @@ namespace cryptonote::rpc { uint64_t top_height = curr_height - 1; latest_ob = top_height; latest_cp = top_height - top_height % master_nodes::CHECKPOINT_INTERVAL; - latest_bl = top_height - top_height % master_nodes::BLINK_QUORUM_INTERVAL; + latest_bl = top_height - top_height % master_nodes::FLASH_QUORUM_INTERVAL; if (is_requested_type(master_nodes::quorum_type::checkpointing)) start = latest_cp; - if (is_requested_type(master_nodes::quorum_type::blink)) + if (is_requested_type(master_nodes::quorum_type::flash)) start = start ? std::min(*start, latest_bl) : latest_bl; end = curr_height; } @@ -2541,8 +2538,8 @@ namespace cryptonote::rpc { { // Latest quorum requested, so skip if this is isn't the latest height for *this* quorum type if (type == master_nodes::quorum_type::obligations && height != latest_ob) continue; if (type == master_nodes::quorum_type::checkpointing && height != latest_cp) continue; - if (type == master_nodes::quorum_type::blink && height != latest_bl) continue; - if (type == master_nodes::quorum_type::pulse) continue; + if (type == master_nodes::quorum_type::flash && height != latest_bl) continue; + if (type == master_nodes::quorum_type::POS) continue; } if (std::shared_ptr quorum = m_core.get_quorum(type, height, true /*include_old*/)) { @@ -3031,9 +3028,13 @@ namespace cryptonote::rpc { static void check_quantity_limit(T count, T max, const char* container_name = "input") { if (count > max) - throw rpc_error{ERROR_WRONG_PARAM, - "Number of requested entries ({}) in {} is greater than the allowed limit ({})"_format( - count, container_name, max)}; + { + std::ostringstream err; + err << "Number of requested entries"; + if (container_name) err << " in " << container_name; + err << " greater than the allowed limit: " << max << ", requested: " << count; + throw rpc_error{ERROR_WRONG_PARAM, err.str()}; + } } template diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 282e4205c52..071ca4b7862 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -389,9 +389,9 @@ namespace cryptonote::rpc { if (qs.request.quorum_type) { if (*qs.request.quorum_type == 255) // backwards-compat magic value qs.request.quorum_type = std::nullopt; - else if (*qs.request.quorum_type > tools::enum_count) + else if (*qs.request.quorum_type > tools::enum_count) throw std::domain_error{ - "Quorum type specifies an invalid value: "_format(*qs.request.quorum_type)}; + "Quorum type specifies an invalid value: " + *qs.request.quorum_type}; } } @@ -416,11 +416,4 @@ namespace cryptonote::rpc { "operator_cut", cmd.request.operator_cut, "staking_requirement", cmd.request.staking_requirement); } - - void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { - get_values(in, - "args", cmd.request.args, - "make_friendly", cmd.request.make_friendly, - "staking_requirement", cmd.request.staking_requirement); - } } \ No newline at end of file From e208e75bb92cda0716ff8ce467c139a0d6db5f06 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 2 May 2025 17:52:21 +0530 Subject: [PATCH 119/182] Allow belnet/ss to send an error ping --- src/rpc/core_rpc_server.cpp | 11 ++++++++++- src/rpc/core_rpc_server_command_parser.cpp | 6 ++++-- src/rpc/core_rpc_server_commands_defs.h | 2 ++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 61392ef2ad0..bf3c8edcbdd 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2958,6 +2958,7 @@ namespace cryptonote::rpc { std::array cur_version, std::array required, std::string_view pubkey_ed25519, + std::string_view error, std::string_view name, std::atomic& update, std::chrono::seconds lifetime, @@ -2965,7 +2966,13 @@ namespace cryptonote::rpc { { std::string our_pubkey_ed25519 = tools::type_to_hex(core.get_master_keys().pub_ed25519); std::string status{}; - if (cur_version < required) { + if (!error.empty()) { + status = fmt::format("Error: {}", error); + MERROR(fmt::format("{0} reported an error: {1}. Check {0} logs for more details.", name, error)); + update = 0; // Reset our last ping time to 0 so that we won't send a ping until we get + // success back again (even if we had an earlier acceptable ping within the + // cutoff time). + else if (cur_version < required) { status = fmt::format("Outdated {}. Current: {}.{}.{}, Required: {}.{}.{}",name, cur_version[0], cur_version[1], cur_version[2], required[0], required[1], required[2]); MERROR(status); } else if (!pubkey_ed25519.empty() && !(pubkey_ed25519.find_first_not_of('0') == std::string_view::npos) // TODO: once belnet & ss are always sending this we can remove this empty bypass @@ -2994,6 +3001,7 @@ namespace cryptonote::rpc { storage_server_ping.response["status"] = handle_ping(m_core, storage_server_ping.request.version, master_nodes::MIN_STORAGE_SERVER_VERSION, storage_server_ping.request.pubkey_ed25519, + storage_server_ping.request.error, "Storage Server", m_core.m_last_storage_server_ping, m_core.get_net_config().UPTIME_PROOF_FREQUENCY, [this, &storage_server_ping](bool significant) { m_core.m_storage_https_port = storage_server_ping.request.https_port; @@ -3009,6 +3017,7 @@ namespace cryptonote::rpc { belnet_ping.response["status"] = handle_ping(m_core, belnet_ping.request.version, master_nodes::MIN_BELNET_VERSION, belnet_ping.request.pubkey_ed25519, + belnet_ping.request.error, "Belnet", m_core.m_last_belnet_ping, m_core.get_net_config().UPTIME_PROOF_FREQUENCY, [this](bool significant) { if (significant) m_core.reset_proof_interval(); }); } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 071ca4b7862..e99898c53c0 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -274,15 +274,17 @@ namespace cryptonote::rpc { void parse_request(BELNET_PING& belnet_ping, rpc_input in){ get_values(in, "version", belnet_ping.request.version, + "error", belnet_ping.request.error, "pubkey_ed25519", belnet_ping.request.pubkey_ed25519); } void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ get_values(in, - "version", storage_server_ping.request.version, + "ed25519_pubkey", storage_server_ping.request.pubkey_ed25519, + "error", storage_server_ping.request.error, "https_port", storage_server_ping.request.https_port, "omq_port", storage_server_ping.request.omq_port, - "pubkey_ed25519", storage_server_ping.request.pubkey_ed25519); + "version", storage_server_ping.request.version); } void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 2f953b86f4b..e1d8a923cac 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1909,6 +1909,7 @@ namespace cryptonote::rpc { uint16_t https_port; // Storage server https port to include in uptime proofs uint16_t omq_port; // Storage Server oxenmq port to include in uptime proofs std::string pubkey_ed25519; // Master node Ed25519 pubkey for verifying that storage server is using the right one + std::string error; // If given and non-empty then this is an error message telling beldexd to *not* submit an uptime proof and to report this error in the logs instead. Beldexd won't send proofs until it gets another ping (without an error). } request; }; @@ -1933,6 +1934,7 @@ namespace cryptonote::rpc { { std::array version; // Belnet version std::string pubkey_ed25519; // Master node Ed25519 pubkey for verifying that belnet is using the right one + std::string error; // If given and non-empty then this is an error message telling oxend to *not* submit an uptime proof and to report this error in the logs instead. Beldexd won't send proofs until it gets another ping (without an error). } request; }; From af7d54ea429d4ef4e9589dc9f7613af8cffeb4d5 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 2 May 2025 18:50:37 +0530 Subject: [PATCH 120/182] Rename LMQ to OMQ and redo json_archiver using nlohmann::json - Renames LMQ -> OMQ in the codebase. (This doesn't rename the `--lmq-...` CLI options because that would break backwards compatibility.) - Rewrites json_archiver to use nlohmann::json instead of manually constructing JSON. This simplifies the logic and allows merging the serialized JSON into RPC results more easily. --- src/common/CMakeLists.txt | 1 + src/cryptonote_basic/cryptonote_basic.h | 13 +- .../cryptonote_format_utils.h | 8 +- src/cryptonote_core/cryptonote_core.cpp | 4 +- src/cryptonote_core/cryptonote_core.h | 4 +- src/cryptonote_core/pos.cpp | 4 +- src/daemon/daemon.cpp | 2 +- src/daemon/daemon.h | 2 +- src/ringct/rctTypes.h | 24 +-- src/rpc/CMakeLists.txt | 2 +- src/rpc/common/CMakeLists.txt | 1 - src/rpc/common/command_decorators.cpp | 4 +- src/rpc/common/command_decorators.h | 10 +- .../{rpc_binary.cpp => json_binary_proxy.cpp} | 4 +- .../{rpc_binary.h => json_binary_proxy.h} | 40 ++-- src/rpc/common/param_parser.hpp | 8 +- src/rpc/core_rpc_server.cpp | 35 +++- src/rpc/core_rpc_server_binary_commands.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 9 +- src/rpc/core_rpc_server_commands_defs.cpp | 4 +- src/rpc/core_rpc_server_commands_defs.h | 12 +- src/rpc/http_server.cpp | 8 +- src/rpc/{lmq_server.cpp => omq_server.cpp} | 43 ++-- src/rpc/{lmq_server.h => omq_server.h} | 2 +- src/serialization/binary_archive.h | 51 ++--- src/serialization/container.h | 3 +- src/serialization/json_archive.h | 194 +++++++----------- src/serialization/json_utils.h | 60 ------ src/serialization/pair.h | 6 +- 29 files changed, 236 insertions(+), 326 deletions(-) rename src/rpc/common/{rpc_binary.cpp => json_binary_proxy.cpp} (96%) rename src/rpc/common/{rpc_binary.h => json_binary_proxy.h} (74%) rename src/rpc/{lmq_server.cpp => omq_server.cpp} (94%) rename src/rpc/{lmq_server.h => omq_server.h} (97%) delete mode 100755 src/serialization/json_utils.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 218bfe8d827..3117b779522 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(common expect.cpp file.cpp i18n.cpp + json_binary_proxy.cpp beldex.cpp notify.cpp password.cpp diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index c5a904288f0..b909511a69b 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -36,7 +36,6 @@ #include "serialization/variant.h" #include "serialization/vector.h" #include "serialization/binary_archive.h" -#include "serialization/json_archive.h" #include "serialization/crypto.h" #include "epee/serialization/keyvalue_serialization.h" // eepe named serialization #include "cryptonote_config.h" @@ -275,16 +274,18 @@ namespace cryptonote set_blob_size_valid(false); } - const unsigned int start_pos = Binary ? ar.streampos() : 0; + unsigned int start_pos = 0; + if constexpr (Binary) + start_pos = ar.streampos(); serialization::value(ar, static_cast(*this)); - if (Binary) + if constexpr (Binary) prefix_size = ar.streampos() - start_pos; if (version == txversion::v1) { - if (Binary) + if constexpr (Binary) unprunable_size = ar.streampos() - start_pos; ar.tag("signatures"); @@ -311,7 +312,7 @@ namespace cryptonote else if (signature_size != signatures[i].size()) throw std::invalid_argument{"Invalid signature size (expected " + std::to_string(signature_size) + ", have " + std::to_string(signatures[i].size()) + ")"}; - value(arr.element(), signatures[i]); + value(ar, signatures[i]); } } else @@ -324,7 +325,7 @@ namespace cryptonote rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); } - if (Binary) + if constexpr (Binary) unprunable_size = ar.streampos() - start_pos; if (!pruned && rct_signatures.type != rct::RCTType::Null) diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 6ae77967351..8318711455a 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -39,6 +39,7 @@ #include "common/meta.h" #include "common/string_util.h" #include "serialization/binary_utils.h" +#include "serialization/json_archive.h" #include namespace epee @@ -319,15 +320,12 @@ namespace cryptonote template std::string obj_to_json_str(T&& obj, bool indent = false) { - std::ostringstream ss; - serialization::json_archiver ar{ss, indent}; try { - serialize(ar, obj); + return serialization::dump_json(obj, indent ? 2 : -1); } catch (const std::exception& e) { LOG_ERROR("obj_to_json_str failed: serialization failed: " << e.what()); - return ""s; } - return ss.str(); + return ""s; } //--------------------------------------------------------------- blobdata block_to_blob(const block& b); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 14000e941e1..55716b4f665 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -981,7 +981,7 @@ namespace cryptonote MGINFO_YELLOW("- x25519: " << tools::type_to_hex(keys.pub_x25519)); } else { // Only print the x25519 version because it's the only thing useful for a non-MN (for - // encrypted LMQ RPC connections). + // encrypted OMQ RPC connections). MGINFO_YELLOW("x25519 public key: " << tools::type_to_hex(keys.pub_x25519)); } @@ -2334,7 +2334,7 @@ namespace cryptonote MGINFO_RED( "Another master node (" << pk << ") is broadcasting the same public IP and ports as this master node (" << epee::string_tools::get_ip_string_from_int32(m_mn_public_ip) << ":" << proof.proof->qnet_port << "[qnet], :" << - proof.proof->storage_https_port << "[SS-HTTP], :" << proof.proof->storage_omq_port << "[SS-LMQ]). " + proof.proof->storage_https_port << "[SS-HTTP], :" << proof.proof->storage_omq_port << "[SS-OMQ]). " "This will lead to deregistration of one or both master nodes if not corrected. " "(Do both master nodes have the correct IP for the master-node-public-ip setting?)"); }); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 29eaa2b373a..ccddda5d495 100755 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1205,8 +1205,8 @@ namespace cryptonote // avoid linking issues (protocol does not link against core). void* m_quorumnet_state = nullptr; - /// Stores x25519 -> access level for LMQ authentication. - /// Not to be modified after the LMQ listener starts. + /// Stores x25519 -> access level for OMQ authentication. + /// Not to be modified after the OMQ listener starts. std::unordered_map m_omq_auth; size_t block_sync_size; diff --git a/src/cryptonote_core/pos.cpp b/src/cryptonote_core/pos.cpp index 3c8c6bf1292..8c8e8133fe4 100755 --- a/src/cryptonote_core/pos.cpp +++ b/src/cryptonote_core/pos.cpp @@ -762,13 +762,13 @@ bool POS::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t b /* POS progresses via a state-machine that is iterated through job submissions - to 1 dedicated POS thread, started by LMQ. + to 1 dedicated POS thread, started by OMQ. Iterating the state-machine is done by a periodic invocation of POS::main(...) and messages received via Quorumnet for POS, which are queued in the thread's job queue. - Using 1 dedicated thread via LMQ avoids any synchronization required in the + Using 1 dedicated thread via OMQ avoids any synchronization required in the user code when implementing POS. Skip control flow graph for textual description of stages. diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index b8ae389b4c8..b64b1aade00 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -43,7 +43,7 @@ #endif #include "rpc/common/rpc_args.h" #include "rpc/http_server.h" -#include "rpc/lmq_server.h" +#include "rpc/omq_server.h" // #include "rpc/bootstrap_daemon.h" #include "cryptonote_protocol/quorumnet.h" #include "cryptonote_core/uptime_proof.h" diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index ed3d8cf8a52..6838c6c4eaa 100755 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -34,7 +34,7 @@ #include "p2p/net_node.h" #include "rpc/core_rpc_server.h" #include "rpc/http_server.h" -#include "rpc/lmq_server.h" +#include "rpc/omq_server.h" #include "blocks/blocks.h" #include "rpc/core_rpc_server.h" diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index aee1c2cea4f..8dd42a6ab44 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -303,7 +303,7 @@ namespace rct { { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& e : pseudoOuts) - value(arr.element(), e); + value(ar, e); } { @@ -311,21 +311,21 @@ namespace rct { if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG)) { for (auto& e : ecdhInfo) { - auto obj = arr.element().begin_object(); + auto obj = ar.begin_object(); if (Archive::is_deserializer) memset(e.amount.bytes, 0, sizeof(e.amount.bytes)); field(ar, "amount", reinterpret_cast(e.amount)); } } else { for (auto& e : ecdhInfo) - value(arr.element(), e); + value(ar, e); } } { auto arr = start_array(ar, "outPk", outPk, outputs); for (auto& e : outPk) - value(arr.element(), e.mask); + value(ar, e.mask); } } }; @@ -356,7 +356,7 @@ namespace rct { auto arr = start_array(ar, "bp", bulletproofs, nbp); for (auto& b : bulletproofs) - value(arr.element(), b); + value(ar, b); if (auto n_max = n_bulletproof_max_amounts(bulletproofs); n_max < outputs) throw std::invalid_argument{"invalid bulletproofs: n_max (" + std::to_string(n_max) + ") < outputs (" + std::to_string(outputs) + ")"}; @@ -365,7 +365,7 @@ namespace rct { { auto arr = start_array(ar, "rangeSigs", rangeSigs, outputs); for (auto& s : rangeSigs) - value(arr.element(), s); + value(ar, s); } if (type == RCTType::CLSAG) @@ -377,11 +377,11 @@ namespace rct { // we save the CLSAGs contents directly, because we want it to save its // arrays without the size prefixes, and the load can't know what size // to expect if it's not in the data - auto obj = arr.element().begin_object(); + auto obj = ar.begin_object(); { auto arr_s = start_array(ar, "s", clsag.s, mixin + 1); for (auto& x : clsag.s) - value(arr_s.element(), x); + value(ar, x); } field(ar, "c1", clsag.c1); field(ar, "D", clsag.D); @@ -402,7 +402,7 @@ namespace rct { auto arr = start_array(ar, "MGs", MGs, mg_elements); for (auto& mg : MGs) { - auto obj = arr.element().begin_object(); + auto obj = ar.begin_object(); // we save the MGs contents directly, because we want it to save its // arrays and matrices without the size prefixes, and the load can't @@ -411,14 +411,14 @@ namespace rct { auto arr_ss = start_array(ar, "ss", mg.ss, mixin + 1); for (auto& ss : mg.ss) { - auto arr_ss2 = arr_ss.element().begin_array(); + auto arr_ss2 = ar.begin_array(); if constexpr (Archive::is_deserializer) ss.resize(mg_ss2_elements); else if (ss.size() != mg_ss2_elements) throw std::invalid_argument{"invalid mg_ss2 size: have " + std::to_string(ss.size()) + ", expected " + std::to_string(mg_ss2_elements)}; for (auto& x : ss) - value(arr_ss2.element(), x); + value(ar, x); } } field(ar, "cc", mg.cc); @@ -430,7 +430,7 @@ namespace rct { { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& o : pseudoOuts) - value(arr.element(), o); + value(ar, o); } } diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 99350f91a1d..6d1296ac5c8 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -42,7 +42,7 @@ add_library(rpc add_library(daemon_rpc_server http_server.cpp - lmq_server.cpp + omq_server.cpp ) add_library(rpc_http_client diff --git a/src/rpc/common/CMakeLists.txt b/src/rpc/common/CMakeLists.txt index c1d6b0cdcfc..ef8ebc2b1ad 100644 --- a/src/rpc/common/CMakeLists.txt +++ b/src/rpc/common/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(rpc_common - rpc_binary.cpp command_decorators.cpp json_bt.cpp rpc_args.cpp diff --git a/src/rpc/common/command_decorators.cpp b/src/rpc/common/command_decorators.cpp index 7d4d1b45662..9bee00c966a 100644 --- a/src/rpc/common/command_decorators.cpp +++ b/src/rpc/common/command_decorators.cpp @@ -5,8 +5,8 @@ namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { bt = true; - response_b64.format = json_binary_proxy::fmt::bt; - response_hex.format = json_binary_proxy::fmt::bt; + response_b64.format = tools::json_binary_proxy::fmt::bt; + response_hex.format = tools::json_binary_proxy::fmt::bt; } } // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/common/command_decorators.h b/src/rpc/common/command_decorators.h index 83ac433fc05..72d8837d1dd 100644 --- a/src/rpc/common/command_decorators.h +++ b/src/rpc/common/command_decorators.h @@ -1,6 +1,6 @@ #pragma once -#include "rpc_binary.h" +#include "common/json_binary_proxy.h" #include @@ -17,7 +17,7 @@ namespace cryptonote::rpc { /// Base class that all RPC commands must inherit from (either directly or via one or more of the /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC - /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be + /// command. For OMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be /// at `whatever`. This base class is also where response objects are stored. struct RPC_COMMAND { private: @@ -58,7 +58,7 @@ namespace cryptonote::rpc { /// Usage: /// std::string data = "abc"; /// rpc.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" - json_binary_proxy response_hex{response, json_binary_proxy::fmt::hex}; + tools::json_binary_proxy response_hex{response, tools::json_binary_proxy::fmt::hex}; /// Proxy object that encodes binary data as base64 for json, leaving it as binary for /// bt-encoded responses. @@ -66,13 +66,13 @@ namespace cryptonote::rpc { /// Usage: /// std::string data = "abc"; /// rpc.response_b64["foo"]["bar"] = data; // json: "YWJj", bt: "abc" - json_binary_proxy response_b64{response, json_binary_proxy::fmt::base64}; + tools::json_binary_proxy response_b64{response, tools::json_binary_proxy::fmt::base64}; }; /// Tag types that are used (via inheritance) to set rpc endpoint properties /// Specifies that the RPC call is public (i.e. available through restricted rpc). If this is - /// *not* inherited from then the command is restricted (i.e. only available to admins). For LMQ, + /// *not* inherited from then the command is restricted (i.e. only available to admins). For OMQ, /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). struct PUBLIC : virtual RPC_COMMAND {}; diff --git a/src/rpc/common/rpc_binary.cpp b/src/rpc/common/json_binary_proxy.cpp similarity index 96% rename from src/rpc/common/rpc_binary.cpp rename to src/rpc/common/json_binary_proxy.cpp index eef636c4737..ac2b1d7fbc3 100644 --- a/src/rpc/common/rpc_binary.cpp +++ b/src/rpc/common/json_binary_proxy.cpp @@ -1,8 +1,8 @@ -#include "rpc_binary.h" +#include "json_binary_proxy.h" #include #include -namespace cryptonote::rpc { +namespace tools { void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) { if (allow_raw && bytes.size() == raw_size) { diff --git a/src/rpc/common/rpc_binary.h b/src/rpc/common/json_binary_proxy.h similarity index 74% rename from src/rpc/common/rpc_binary.h rename to src/rpc/common/json_binary_proxy.h index f202a6bb6b7..42f8ddc8bd8 100644 --- a/src/rpc/common/rpc_binary.h +++ b/src/rpc/common/json_binary_proxy.h @@ -8,37 +8,37 @@ using namespace std::literals; -namespace cryptonote::rpc { +namespace tools { // Binary types that we support for rpc input/output. For json, these must be specified as hex or // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. template - inline constexpr bool is_binary_parameter = false; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; + inline constexpr bool json_is_binary = false; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; template - inline constexpr bool is_binary_container = false; + inline constexpr bool json_is_binary_container = false; template - inline constexpr bool is_binary_container> = is_binary_parameter; + inline constexpr bool json_is_binary_container> = json_is_binary; template - inline constexpr bool is_binary_container> = is_binary_parameter; + inline constexpr bool json_is_binary_container> = json_is_binary; // De-referencing wrappers around the above: - template inline constexpr bool is_binary_parameter = is_binary_parameter; - template inline constexpr bool is_binary_parameter = is_binary_parameter; - template inline constexpr bool is_binary_container = is_binary_container; - template inline constexpr bool is_binary_container = is_binary_container; + template inline constexpr bool json_is_binary = json_is_binary; + template inline constexpr bool json_is_binary = json_is_binary; + template inline constexpr bool json_is_binary_container = json_is_binary_container; + template inline constexpr bool json_is_binary_container = json_is_binary_container; void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw // bytes. - template >> + template >> void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) { load_binary_parameter_impl(bytes, sizeof(T), allow_raw, reinterpret_cast(&val)); } @@ -82,13 +82,13 @@ namespace cryptonote::rpc { /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its /// contents as the binary value. - template , int> = 0> + template , int> = 0> nlohmann::json& operator=(const T& val) { return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; } /// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning /// each one into a new array of binary values. - template , int> = 0> + template , int> = 0> nlohmann::json& operator=(const T& vals) { auto a = nlohmann::json::array(); for (auto& val : vals) @@ -122,14 +122,14 @@ namespace cryptonote::rpc { // invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. namespace nlohmann { template - struct adl_serializer>> { + struct adl_serializer>> { static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); static void to_json(json& j, const T&) { throw std::logic_error{"Internal error: binary types are not directly serializable"}; } static void from_json(const json& j, T& val) { - cryptonote::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); + tools::load_binary_parameter(j.get(), false /*no raw*/, val); } }; } diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index dbf08651c38..da4d61dbe01 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -1,6 +1,6 @@ #pragma once -#include "rpc/common/rpc_binary.h" +#include "common/json_binary_proxy.h" #include #include @@ -127,8 +127,8 @@ namespace cryptonote::rpc { val = c.template consume_integer(); else if constexpr (std::is_same_v || std::is_same_v) val = c.consume_string_view(); - else if constexpr (is_binary_parameter) - load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + else if constexpr (tools::json_is_binary) + tools::load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); else if constexpr (is_expandable_list) { auto lc = c.consume_list_consumer(); val.clear(); @@ -182,7 +182,7 @@ namespace cryptonote::rpc { val = i; } else if constexpr (std::is_same_v || std::is_same_v) { val = e.get(); - } else if constexpr (is_binary_parameter || is_expandable_list || is_tuple_like) { + } else if constexpr (tools::json_is_binary || is_expandable_list || is_tuple_like) { try { e.get_to(val); } catch (const std::exception& e) { throw std::domain_error{"Invalid values in '" + key + "'"}; } } else { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index bf3c8edcbdd..101a3165d29 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -37,6 +37,7 @@ #include #include #include +#include "common/json_binary_proxy.h" #include #include "epee/net/network_throttle.hpp" #include "common/string_util.h" @@ -70,6 +71,7 @@ #include "net/parse.h" #include "crypto/hash.h" #include "p2p/net_node.h" +#include "serialization/json_archive.h" #include "version.h" #include @@ -80,6 +82,8 @@ namespace cryptonote::rpc { using nlohmann::json; + using tools::json_binary_proxy; + namespace { template @@ -660,13 +664,13 @@ namespace cryptonote::rpc { // a single one we want just the value itself; this does that. Returns a reference to the // assigned value (whether as a top-level value or array element). template - json& set(const std::string& key, T&& value, bool binary = is_binary_parameter || is_binary_container) { + json& set(const std::string& key, T&& value, bool binary = tools::json_is_binary_parameter || tools::json_is_binary_container) { auto* x = &entry[key]; if (!x->is_null() && !x->is_array()) x = &(entry[key] = json::array({std::move(*x)})); if (x->is_array()) x = &x->emplace_back(); - if constexpr (is_binary_parameter || is_binary_container || std::is_convertible_v) { + if constexpr (tools::json_is_binary || tools::json_is_binary_container || std::is_convertible_v) { if (binary) return json_binary_proxy{*x, format} = std::forward(value); } @@ -1021,12 +1025,34 @@ namespace cryptonote::rpc { return; } } + std::optional extra; if (get.request.tx_extra) - load_tx_extra_data(e["extra"], tx, nettype(), hf_version, get.is_bt()); + load_tx_extra_data(extra.emplace(), tx, nettype(), hf_version, get.is_bt()); + if (get.request.tx_extra_raw) + e_bin["tx_extra_raw"] = std::string_view{reinterpret_cast(tx.extra.data()), tx.extra.size()}; + + // Clear it because we don't want/care about it in the RPC output (we already got it more + // usefully from the above). + tx.extra.clear(); + + { + serialization::json_archiver ja{ + get.is_bt() ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex}; + serialize(ja, tx); + auto dumped = std::move(ja).json(); + for (const auto& [k, v] : dumped.items()) { + LOG_PRINT_L0("tx details has k= " << k); + } + e.update(dumped); + } + + if (extra) + e["extra"] = std::move(*extra); + else + e.erase("extra"); auto ptx_it = found_in_pool.find(tx_hash); bool in_pool = ptx_it != found_in_pool.end(); - e["in_pool"] = in_pool; auto height = std::numeric_limits::max(); if (uint64_t fee, burned; get_tx_miner_fee(tx, fee, hf_version >= feature::FEE_BURNING, &burned)) { @@ -1037,6 +1063,7 @@ namespace cryptonote::rpc { if (in_pool) { const auto& meta = ptx_it->second.meta; + e["in_pool"] = true; e["weight"] = meta.weight; e["relayed"] = (bool) ptx_it->second.meta.relayed; e["received_timestamp"] = ptx_it->second.meta.receive_time; diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index a79df8d2655..97be1d49206 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -239,8 +239,8 @@ namespace cryptonote::rpc { struct request { bool flashed_txs_only; // Optional: If true only transactions that were sent via flash and approved are queried. - bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using LMQ RPC. - crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using LMQ RPC. + bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using OMQ RPC. + crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using OMQ RPC. KV_MAP_SERIALIZABLE }; diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index e99898c53c0..2c2b85ef416 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -114,20 +114,15 @@ namespace cryptonote::rpc { if (auto it = json_in->find("txs_hashes"); it != json_in->end()) (*json_in)["tx_hashes"] = std::move(*it); - std::optional data; get_values(in, - "data", data, + "data", get.request.data, "memory_pool", get.request.memory_pool, "prune", get.request.prune, "split", get.request.split, "tx_extra", get.request.tx_extra, + "tx_extra_raw", get.request.tx_extra_raw, "tx_hashes", get.request.tx_hashes); - if (data) - get.request.data = *data; - else - get.request.data = !(get.request.prune || get.request.split); - if (get.request.memory_pool && !get.request.tx_hashes.empty()) throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index b3ad583c948..0f1c7e10d35 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -56,8 +56,8 @@ namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { bt = true; - response_b64.format = json_binary_proxy::fmt::bt; - response_hex.format = json_binary_proxy::fmt::bt; + response_b64.format = tools::json_binary_proxy::fmt::bt; + response_hex.format = tools::json_binary_proxy::fmt::bt; } void to_json(nlohmann::json& j, const block_header_response& h) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e1d8a923cac..045aa515d6c 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -45,7 +45,6 @@ // setlocal comments+=:// #include "rpc/common/rpc_version.h" -#include "rpc/common/rpc_binary.h" #include "rpc/common/command_decorators.h" #include "crypto/crypto.h" @@ -175,7 +174,7 @@ namespace cryptonote::rpc { /// - \p double_spend_seen -- set to true if one or more outputs in this mempool transaction /// have already been spent (and thus the tx cannot currently be added to the blockchain). /// - \p data -- Full, unpruned transaction data. For a json request this is hex-encoded; for a - /// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`, + /// bt-encoded request this is raw bytes. This field is omitted if any of `tx_extra`, /// `split`, or `prune` is requested; or if the transaction has been pruned in the database. /// - \p pruned -- The non-prunable part of the transaction, encoded as hex (for json requests). /// Always included if `split` or `prune` are specified; without those options it will be @@ -285,10 +284,13 @@ namespace cryptonote::rpc { bool memory_pool = false; /// If set to true then parse and return tx-extra information bool tx_extra = false; + /// If set to true then include the raw tx-extra information in the tx_extra_raw field. This + /// will be hex-encoded for json, raw bytes for bt-encoded requests. + bool tx_extra_raw = false; /// Controls whether the `data` (or `pruned`, if pruned) field containing raw tx data is - /// included: if explicitly specified then the raw data will be included if true. Otherwise - /// the raw data is included only when neither of `split` nor `prune` are set to true. - bool data = true; + /// included. By default it is not included; you typically want `details` rather than this + /// field. + bool data = false; /// If set to true then always split transactions into non-prunable and prunable parts in the /// response. bool split = false; diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index fca49ac5e62..214145413a1 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -89,7 +89,7 @@ namespace cryptonote::rpc { : m_server{server}, m_restricted{restricted} { // uWS is designed to work from a single thread, which is good (we pull off the requests and - // then stick them into the LMQ job queue to be scheduled along with other jobs). But as a + // then stick them into the OMQ job queue to be scheduled along with other jobs). But as a // consequence, we need to create everything inside that thread. We *also* need to get the // (thread local) event loop pointer back from the thread so that we can shut it down later // (injecting a callback into it is one of the few thread-safe things we can do across threads). @@ -270,7 +270,7 @@ namespace cryptonote::rpc { void invoke_txpool_hashes_bin(std::shared_ptr data); - // Invokes the actual RPC request; this is called (via lokimq) from some random LMQ worker thread, + // Invokes the actual RPC request; this is called (via lokimq) from some random OMQ worker thread, // which means we can't just write our reply; instead we have to post it to the uWS loop. void invoke_rpc(std::shared_ptr dataptr) { @@ -494,7 +494,7 @@ namespace cryptonote::rpc { auto& omq = data->core_rpc.get_core().get_omq(); std::string cat{data->call->is_public ? "rpc" : "admin"}; - std::string cmd{"http:" + data->uri}; // Used for LMQ job logging; prefixed with http: so we can distinguish it + std::string cmd{"http:" + data->uri}; // Used for OMQ job logging; prefixed with http: so we can distinguish it std::string remote{data->request.context.remote}; omq.inject_task(std::move(cat), std::move(cmd), std::move(remote), [data=std::move(data)] { invoke_rpc(std::move(data)); }); }); @@ -560,7 +560,7 @@ namespace cryptonote::rpc { auto& omq = data->core_rpc.get_core().get_omq(); std::string cat{data->call->is_public ? "rpc" : "admin"}; - std::string cmd{"jsonrpc:" + *method}; // Used for LMQ job logging; prefixed with jsonrpc: so we can distinguish it + std::string cmd{"jsonrpc:" + *method}; // Used for OMQ job logging; prefixed with jsonrpc: so we can distinguish it std::string remote{data->request.context.remote}; omq.inject_task(std::move(cat), std::move(cmd), std::move(remote), [data=std::move(data)] { invoke_rpc(std::move(data)); }); }); diff --git a/src/rpc/lmq_server.cpp b/src/rpc/omq_server.cpp similarity index 94% rename from src/rpc/lmq_server.cpp rename to src/rpc/omq_server.cpp index d0c9ff44525..afd3066414b 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/omq_server.cpp @@ -1,12 +1,11 @@ -#include "lmq_server.h" +#include "omq_server.h" #include "rpc/common/param_parser.hpp" #include "cryptonote_config.h" #include "oxenmq/oxenmq.h" #include "oxenc/bt.h" #include -// FIXME: Rename this to omq_server.{h,cpp} #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc" @@ -52,7 +51,7 @@ const command_line::arg_descriptor> arg_omq_local_contr #ifndef _WIN32 const command_line::arg_descriptor arg_omq_umask{ "lmq-umask", - "Sets the umask to apply to any listening ipc:///path/to/sock LMQ sockets, in octal.", + "Sets the umask to apply to any listening ipc:///path/to/sock OMQ sockets, in octal.", "0007"}; #endif @@ -70,20 +69,20 @@ auto as_x_pubkeys(const std::vector& pk_strings) { pks.reserve(pk_strings.size()); for (const auto& pkstr : pk_strings) { if (pkstr.size() != 64 || !oxenc::is_hex(pkstr)) - throw std::runtime_error("Invalid LMQ login pubkey: '" + pkstr + "'; expected 64-char hex pubkey"); + throw std::runtime_error("Invalid OMQ login pubkey: '" + pkstr + "'; expected 64-char hex pubkey"); pks.emplace_back(); oxenc::to_hex(pkstr.begin(), pkstr.end(), reinterpret_cast(&pks.back())); } return pks; } -// LMQ RPC responses consist of [CODE, DATA] for code we (partially) mimic HTTP error codes: 200 +// OMQ RPC responses consist of [CODE, DATA] for code we (partially) mimic HTTP error codes: 200 // means success, anything else means failure. (We don't have codes for Forbidden or Not Found -// because those happen at the LMQ protocol layer). +// because those happen at the OMQ protocol layer). constexpr std::string_view - LMQ_OK{"200"sv}, - LMQ_BAD_REQUEST{"400"sv}, - LMQ_ERROR{"500"sv}; + OMQ_OK{"200"sv}, + OMQ_BAD_REQUEST{"400"sv}, + OMQ_ERROR{"500"sv}; } // end anonymous namespace @@ -111,21 +110,21 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // the quorumnet listener set up in cryptonote_core). for (const auto &addr : command_line::get_arg(vm, arg_omq_public)) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (public unencrypted)"); + MGINFO("OMQ listening on " << addr << " (public unencrypted)"); omq.listen_plain(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::basic); }); } for (const auto &addr : command_line::get_arg(vm, arg_omq_curve_public)) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (public curve)"); + MGINFO("OMQ listening on " << addr << " (public curve)"); omq.listen_curve(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::basic); }); } for (const auto &addr : command_line::get_arg(vm, arg_omq_curve)) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (curve restricted)"); + MGINFO("OMQ listening on " << addr << " (curve restricted)"); omq.listen_curve(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::denied); }); } @@ -147,7 +146,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog } for (const auto &addr : locals) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (unauthenticated local admin)"); + MGINFO("OMQ listening on " << addr << " (unauthenticated local admin)"); omq.listen_plain(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::admin); }); } @@ -195,7 +194,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog omq.add_request_command(cmd.second->is_public ? "rpc" : "admin", cmd.first, [name=std::string_view{cmd.first}, &call=*cmd.second, this](oxenmq::Message& m) { if (m.data.size() > 1) - m.send_reply(LMQ_BAD_REQUEST, "Bad request: RPC commands must have at most one data part " + m.send_reply(OMQ_BAD_REQUEST, "Bad request: RPC commands must have at most one data part " "(received " + std::to_string(m.data.size()) + ")"); rpc_request request{}; @@ -217,7 +216,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog return std::move(v); } }, call.invoke(std::move(request), rpc_)); - m.send_reply(LMQ_OK, std::move(result)); + m.send_reply(OMQ_OK, std::move(result)); return; } catch (const parse_error& e) { // This isn't really WARNable as it's the client fault; log at info level instead. @@ -226,25 +225,25 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // warnings that get generated deep inside epee, for example when passing a string or // number instead of a JSON object. If you want to find some, `grep number2 epee` (for // real). - MINFO("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); + MINFO("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); MDEBUG("Bad request body:" << m.data.empty() ? "(empty)" : m.data[0]); - m.send_reply(LMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); + m.send_reply(OMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); return; } catch (const rpc_error& e) { - MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' failed with: " << e.what()); - m.send_reply(LMQ_ERROR, e.what()); + MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' failed with: " << e.what()); + m.send_reply(OMQ_ERROR, e.what()); return; } catch (const std::exception& e) { - MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " + MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " "raised an exception: " << e.what()); } catch (...) { - MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " + MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " "raised an unknown exception"); } // Don't include the exception message in case it contains something that we don't want go // back to the user. If we want to support it eventually we could add some sort of // `rpc::user_visible_exception` that carries a message to send back to the user. - m.send_reply(LMQ_ERROR, "An exception occured while processing your request"); + m.send_reply(OMQ_ERROR, "An exception occured while processing your request"); }); } diff --git a/src/rpc/lmq_server.h b/src/rpc/omq_server.h similarity index 97% rename from src/rpc/lmq_server.h rename to src/rpc/omq_server.h index 498f1e5e4c4..f21838fe835 100755 --- a/src/rpc/lmq_server.h +++ b/src/rpc/omq_server.h @@ -40,7 +40,7 @@ namespace cryptonote::rpc { void init_omq_options(boost::program_options::options_description& desc); /** - * LMQ RPC server class. This doesn't actually hold the OxenMQ instance--that's in + * OMQ RPC server class. This doesn't actually hold the OxenMQ instance--that's in * cryptonote_core--but it works with it to add RPC endpoints, make it listen on RPC ports, and * handles RPC requests. */ diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 0b205114d3c..726fc2799ef 100755 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -57,27 +57,6 @@ static_assert(-1 == ~0, "Non 2s-complement architecture not supported!"); using binary_variant_tag_type = uint8_t; -// RAII class for `begin_array()`. This particular implementation is a no-op. -template -struct binary_archive_nested_array { - Archive& ar; - - // Call before writing an element to add a delimiter. (For binary_archive this is a no-op). - // Returns the archive itself, allowing you to write: - // - // auto arr = ar.begin_array(); - // for (auto& val : whatever) - // value(arr.element(), val); - // - Archive& element() { return ar; } - ~binary_archive_nested_array() {} // Explicitly empty constructor to silent unused variable warnings -}; - -// Do-nothing object for the RAII `begin_object` interface. -struct binary_archive_nested_object { - ~binary_archive_nested_object() {} // As above. -}; - /* \struct binary_unarchiver * * \brief the deserializer class for a binary archive @@ -132,23 +111,28 @@ class binary_unarchiver : public deserializer throw std::runtime_error{"deserialization of varint failed"}; } + // RAII class for `begin_array()`/`begin_object()`. This particular implementation is a no-op. + struct nested { + ~nested() {}; // Avoids unused variable warnings + }; + // Reads array size into s and returns an RAII object to help delimit and end it. - [[nodiscard]] binary_archive_nested_array begin_array(size_t& s) + [[nodiscard]] nested begin_array(size_t& s) { serialize_varint(s); - return {*this}; + return {}; } // Begins a sizeless array (this requires that the size is provided by some other means). - [[nodiscard]] binary_archive_nested_array begin_array() + [[nodiscard]] nested begin_array() { - return {*this}; + return {}; } // Does nothing. (This is used for tag annotations for archivers such as json) void tag(std::string_view) { } - [[nodiscard]] binary_archive_nested_object begin_object() { return {}; } + [[nodiscard]] nested begin_object() { return {}; } void read_variant_tag(binary_variant_tag_type &t) { serialize_int(t); @@ -232,24 +216,29 @@ class binary_archiver : public serializer tools::write_varint(std::ostreambuf_iterator{stream_}, v); } + // RAII class for `begin_array()`/`begin_object()`. This particular implementation is a no-op. + struct nested { + ~nested() {}; // Avoids unused variable warnings + }; + // Begins an array and returns an RAII object that is used to delimit array elements. For // binary_archiver the size is written when the array begins, and the RAII is a no-op. - [[nodiscard]] binary_archive_nested_array begin_array(size_t& s) + [[nodiscard]] nested begin_array(size_t& s) { serialize_varint(s); - return {*this}; + return {}; } // Begins a sizeless array. (Typically requires that size be stored some other way). - [[nodiscard]] binary_archive_nested_array begin_array() + [[nodiscard]] nested begin_array() { - return {*this}; + return {}; } // Does nothing. (This is used for tag annotations for archivers such as json) void tag(std::string_view) { } - [[nodiscard]] binary_archive_nested_object begin_object() { return {}; } + [[nodiscard]] nested begin_object() { return {}; } void write_variant_tag(binary_variant_tag_type t) { serialize_int(t); } diff --git a/src/serialization/container.h b/src/serialization/container.h index 4d32f903c7b..20851633500 100755 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -101,7 +101,6 @@ void serialize_container(Archive& ar, C& v) static_assert(detail::has_emplace_back || detail::has_value_insert, "Unsupported container type"); for (size_t i = 0; i < cnt; i++) { - arr.element(); if constexpr (detail::has_emplace_back) detail::serialize_container_element(ar, v.emplace_back()); else { @@ -119,7 +118,7 @@ void serialize_container(Archive& ar, C& v) size_t cnt = v.size(); auto arr = ar.begin_array(cnt); for (auto& e : v) - serialize_container_element(arr.element(), e); + serialize_container_element(ar, e); } } // namespace detail diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index ae05435878d..adb68e2d531 100755 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2020, The Beldex Project +// Copyright (c) 2018-2022, The Beldex Project // Copyright (c) 2014-2019, The Monero Project // // All rights reserved. @@ -38,171 +38,133 @@ #include "serialization.h" #include "base.h" +#include "common/json_binary_proxy.h" #include -#include -#include #include -#include +#include +#include +#undef BELDEX_DEFAULT_LOG_CATEGORY +#define BELDEX_DEFAULT_LOG_CATEGORY "json_archieve" namespace serialization { using json_variant_tag_type = std::string_view; /*! \struct json_archiver * - * \brief a archive using the JSON standard + * \brief serialize data to JSON via nlohmann::json * * \detailed there is no deserializing counterpart; we only support JSON serializing here. */ struct json_archiver : public serializer { - using variant_tag_type = std::string_view; + explicit json_archiver(tools::json_binary_proxy::fmt bin_format = tools::json_binary_proxy::fmt::hex) + : bin_format_{bin_format} {} - json_archiver(std::ostream& s, bool indent = false) - : stream_{s}, indent_{indent} - { - exc_restore_ = stream_.exceptions(); - stream_.exceptions(std::istream::badbit | std::istream::failbit | std::istream::eofbit); - } + /// Returns the current nlohmann::json. + const nlohmann::json& json() const& { return top_; } + nlohmann::json&& json() && { return std::move(top_); } - ~json_archiver() { stream_.exceptions(exc_restore_); } + /// Dumps the current nlohmann::json; arguments are forwarded to nlohmann::json::dump() + template + auto dump(T&&... args) const { + return top_.dump(std::forward(args)...); + } + // Sets the tag for the next object value we will write. void tag(std::string_view tag) { - if (!object_begin) - stream_ << (indent_ ? ", "sv : ","sv); - make_indent(); - stream_ << '"' << tag << (indent_ ? "\": "sv : "\":"); - - object_begin = false; + MGINFO("tag : " << tag); + tag_ = tag; } - struct nested_object { + struct nested_value { json_archiver& ar; - ~nested_object() { - --ar.depth_; - ar.make_indent(); - ar.stream_ << '}'; + ~nested_value() { + assert(ar.stack_.size() >= 2); + ar.stack_.pop_back(); } - nested_object(const nested_object&) = delete; - nested_object& operator=(const nested_object&) = delete; - nested_object(nested_object&&) = delete; - nested_object& operator=(nested_object&&) = delete; + nested_value(const nested_value&) = delete; + nested_value& operator=(const nested_value&) = delete; + nested_value(nested_value&&) = delete; + nested_value& operator=(nested_value&&) = delete; }; - [[nodiscard]] nested_object begin_object() + [[nodiscard]] nested_value begin_object() { - stream_ << '{'; - ++depth_; - object_begin = true; - return nested_object{*this}; + stack_.emplace_back(set(nlohmann::json::object())); + return {*this}; } - template - static auto promote_to_printable_integer_type(T v) + [[nodiscard]] nested_value begin_array(size_t s=0) { - // Unary operator '+' performs integral promotion on type T [expr.unary.op]. - // If T is signed or unsigned char, it's promoted to int and printed as number. - return +v; + stack_.emplace_back(set(nlohmann::json::array())); + return {*this}; } template void serialize_int(T v) { - stream_ << std::dec << promote_to_printable_integer_type(v); - } - - void serialize_blob(void *buf, size_t len, std::string_view delimiter="\""sv) { - stream_ << delimiter; - auto* begin = static_cast(buf); - oxenc::to_hex(begin, begin + len, std::ostreambuf_iterator{stream_}); - stream_ << delimiter; - } - - template - void serialize_blobs(const std::vector& blobs, std::string_view delimiter="\""sv) { - serialize_blob(blobs.data(), blobs.size()*sizeof(T), delimiter); + set(v); } template void serialize_varint(T &v) { - stream_ << std::dec << promote_to_printable_integer_type(v); + serialize_int(v); } - struct nested_array { - json_archiver& ar; - int exc_count = std::uncaught_exceptions(); - bool first = true; - - // Call before writing an element to add a delimiter. The first element() call adds no - // delimiter. Returns the archive itself, allowing you to write: - // - // auto arr = ar.begin_array(); - // for (auto& val : whatever) - // value(arr.element(), val); - // - json_archiver& element() { - if (first) first = false; - else ar.delimit_array(); - return ar; - } - - ~nested_array() noexcept(false) { - if (std::uncaught_exceptions() == exc_count) { // Normal destruction - --ar.depth_; - if (ar.inner_array_contents_) - ar.make_indent(); - ar.stream_ << ']'; - } - // else we're destructing during a stack unwind so some other serialization failed, thus don't - // try terminating the array (since it might *also* throw if an IO error occurs). - } - - // Non-copyable, non-moveable - nested_array(const nested_array&) = delete; - nested_array& operator=(const nested_array&) = delete; - nested_array(nested_array&&) = delete; - nested_array& operator=(nested_array&&) = delete; - }; - - // Begins an array and returns an RAII object that is used to delimit array elements and - // terminates the array on destruction. - [[nodiscard]] nested_array begin_array(size_t s=0) - { - inner_array_contents_ = s > 0; - ++depth_; - stream_ << '['; - return {*this}; + void serialize_blob(const void *buf, size_t len) { + nlohmann::json val; + tools::json_binary_proxy{val, bin_format_} = std::string_view{static_cast(buf), len}; + set(std::move(val)); } - void delimit_array() { stream_ << (indent_ ? ", "sv : ","sv); } + template + void serialize_blobs(const std::vector& blobs) { + serialize_blob(blobs.data(), blobs.size()*sizeof(T)); + } void write_variant_tag(std::string_view t) { tag(t); } - // Returns the current position (i.e. stream.tellp()) of the output stream. - unsigned int streampos() { return static_cast(stream_.tellp()); } - private: - static constexpr std::string_view indents{" "}; - void make_indent() - { - if (indent_) - { - stream_ << '\n'; - auto in = 2 * depth_; - for (; in > indents.size(); in -= indents.size()) - stream_ << indents; - stream_ << indents.substr(0, in); + nlohmann::json& curr() { + if (stack_.empty()) + return top_; + else + return stack_.back(); + } + + template + nlohmann::json& set(T&& val) { + auto& c = curr(); + if (stack_.empty()) { + c = std::forward(val); + return c; + } + if (c.is_array()) { + c.push_back(std::forward(val)); + return c.back(); } + return (c[tag_] = std::forward(val)); } - std::ostream& stream_; - std::ios_base::iostate exc_restore_; - bool indent_ = false; - bool object_begin = false; - bool inner_array_contents_ = false; - size_t depth_ = 0; + nlohmann::json top_; + std::vector> stack_{}; + tools::json_binary_proxy::fmt bin_format_; + std::string tag_; }; + +/*! serializes the data in v to a string. Throws on error. +*/ +template +std::string dump_json(T& v, int indent = -1) +{ + json_archiver oar; + serialize(oar, v); + return oar.dump(indent); +} + + } // namespace serialization diff --git a/src/serialization/json_utils.h b/src/serialization/json_utils.h deleted file mode 100755 index 82b4eb26141..00000000000 --- a/src/serialization/json_utils.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2014-2019, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include -#include "json_archive.h" - -namespace serialization { - -/// Subclass of json_archiver that writes to a std::ostringstream and returns the string on -/// demand. -class json_string_archiver : public json_archiver { - std::ostringstream oss; -public: - /// Constructor; takes no arguments. - json_string_archiver() : json_archiver{oss} {} - - /// Returns the string from the std::ostringstream - std::string str() { return oss.str(); } -}; - -/*! serializes the data in v to a string. Throws on error. -*/ -template -std::string dump_json(T& v) -{ - json_string_archiver oar; - serialize(oar, v); - return oar.str(); -} - -} // namespace serialization diff --git a/src/serialization/pair.h b/src/serialization/pair.h index 8f02e97a0e4..f2c08f8daf0 100755 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -58,10 +58,8 @@ void serialize_value(Archive& ar, std::pair& p) if (!Archive::is_serializer && cnt != 2) throw std::runtime_error("Serialization failed: expected pair, found " + std::to_string(cnt) + " values"); - detail::serialize_pair_element(arr.element(), p.first); - detail::serialize_pair_element(arr.element(), p.second); - - ar.end_array(); + detail::serialize_pair_element(ar, p.first); + detail::serialize_pair_element(ar, p.second); } } From 2120bd4999e87392ac91f002baaab81eb86616b1 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Mon, 5 May 2025 10:20:23 +0530 Subject: [PATCH 121/182] Move nlohmann_json dependency to 'common' & Fix get_master_node_blacklisted_key_images --- src/blockchain_db/CMakeLists.txt | 2 -- src/common/CMakeLists.txt | 1 + src/cryptonote_core/CMakeLists.txt | 1 - src/rpc/CMakeLists.txt | 2 -- src/rpc/common/CMakeLists.txt | 1 - src/rpc/core_rpc_server.cpp | 5 +---- src/rpc/core_rpc_server_commands_defs.cpp | 4 ++-- src/rpc/core_rpc_server_commands_defs.h | 6 +++--- src/serialization/json_archive.h | 3 --- 9 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/blockchain_db/CMakeLists.txt b/src/blockchain_db/CMakeLists.txt index 20d972d4dc4..e833a139bfb 100755 --- a/src/blockchain_db/CMakeLists.txt +++ b/src/blockchain_db/CMakeLists.txt @@ -33,8 +33,6 @@ add_library(blockchain_db ) target_link_libraries(blockchain_db - PUBLIC - nlohmann_json::nlohmann_json PRIVATE common ringct diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 3117b779522..265399842a5 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -67,6 +67,7 @@ target_link_libraries(common oxenmq::oxenmq filesystem fmt::fmt + nlohmann_json::nlohmann_json date::date PRIVATE sodium diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index d4246df6a3f..c4f892cccca 100755 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -53,7 +53,6 @@ target_link_libraries(cryptonote_core device checkpoints sqlite3 - nlohmann_json::nlohmann_json PRIVATE Boost::program_options systemd diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 6d1296ac5c8..c7b6c30a71c 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -53,7 +53,6 @@ target_link_libraries(rpc_commands rpc_common common cpr::cpr - nlohmann_json::nlohmann_json PRIVATE cryptonote_protocol extra) @@ -83,6 +82,5 @@ target_link_libraries(rpc_http_client PUBLIC common cpr::cpr - nlohmann_json::nlohmann_json PRIVATE extra) diff --git a/src/rpc/common/CMakeLists.txt b/src/rpc/common/CMakeLists.txt index ef8ebc2b1ad..6e90f577954 100644 --- a/src/rpc/common/CMakeLists.txt +++ b/src/rpc/common/CMakeLists.txt @@ -9,7 +9,6 @@ target_link_libraries(rpc_common PUBLIC common uWebSockets - nlohmann_json::nlohmann_json oxenmq::oxenmq PRIVATE extra) \ No newline at end of file diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 101a3165d29..4071c7c283e 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1041,9 +1041,6 @@ namespace cryptonote::rpc { serialize(ja, tx); auto dumped = std::move(ja).json(); - for (const auto& [k, v] : dumped.items()) { - LOG_PRINT_L0("tx details has k= " << k); - } e.update(dumped); } @@ -1810,7 +1807,7 @@ namespace cryptonote::rpc { get_block.response["block_header"] = header; std::vector tx_hashes; tx_hashes.reserve(blk.tx_hashes.size()); - std::transform(blk.tx_hashes.begin(), blk.tx_hashes.end(), tx_hashes.begin(), [](const auto& x) { return tools::type_to_hex(x); }); + std::transform(blk.tx_hashes.begin(), blk.tx_hashes.end(), std::back_inserter(tx_hashes), [](const auto& x) { return tools::type_to_hex(x); }); get_block.response["tx_hashes"] = tx_hashes; get_block.response["blob"] = oxenc::to_hex(t_serializable_object_to_blob(blk)); get_block.response["json"] = obj_to_json_str(blk); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 0f1c7e10d35..072affa064d 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -108,10 +108,10 @@ void from_json(const nlohmann::json& j, block_header_response& h) j.at("block_size").get_to(h.block_size); j.at("block_weight").get_to(h.block_weight); j.at("num_txes").get_to(h.num_txes); - if (j.at("pow_hash").is_null()) + if (auto it = j.find("pow_hash"); it == j.end() || it->is_null()) h.pow_hash = std::nullopt; else - h.pow_hash = j["pow_hash"].get(); + h.pow_hash = it->get(); j.at("long_term_weight").get_to(h.long_term_weight); j.at("miner_tx_hash").get_to(h.miner_tx_hash); j.at("miner_tx_hash").get_to(h.miner_tx_hash); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 045aa515d6c..7595e33ce6a 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -655,8 +655,8 @@ namespace cryptonote::rpc { std::string hash; // The hash of this block. difficulty_type difficulty; // The strength of the Beldex network based on mining power. difficulty_type cumulative_difficulty; // The cumulative strength of the Beldex network based on mining power. - uint64_t reward; // The amount of new generated in this block and rewarded to the miner, foundation and master Nodes. Note: 1 BELDEX = 1e9 atomic units. - uint64_t coinbase_payouts; // The amount of BDX paid out in this block. Note: 1 BELDEX = 1e9 atomic units. + uint64_t reward; // The amount of new BELDEX (in atomic units) generated in this block and allocated to master nodes and governance. As of Bledex 10 (HF) this is the *earned* amount, but not the *paid* amount which occurs in batches. + uint64_t coinbase_payouts; // The amount of BELDEX paid out in this block. As of Beldex 10 (HF 19), this reflects the current batched amounts being paid from earnings batched over previous blocks, not the amounts *earned* in the current block. uint64_t block_size; // The block size in bytes. uint64_t block_weight; // The block weight in bytes. uint64_t num_txes; // Number of transactions in the block, not counting the coinbase tx. @@ -1976,7 +1976,7 @@ namespace cryptonote::rpc { /// - \p key_image The key image of the transaction that is blacklisted on the network. /// - \p unlock_height The height at which the key image is removed from the blacklist and becomes spendable. /// - \p amount The total amount of locked Beldex in atomic units in this blacklisted stake. - struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : NO_ARGS + struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_master_node_blacklisted_key_images"); } }; diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index adb68e2d531..c8940d6622f 100755 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -44,8 +44,6 @@ #include #include -#undef BELDEX_DEFAULT_LOG_CATEGORY -#define BELDEX_DEFAULT_LOG_CATEGORY "json_archieve" namespace serialization { using json_variant_tag_type = std::string_view; @@ -73,7 +71,6 @@ struct json_archiver : public serializer // Sets the tag for the next object value we will write. void tag(std::string_view tag) { - MGINFO("tag : " << tag); tag_ = tag; } From 43376a23a0724a17c968c51428640964e720dba5 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 5 May 2025 14:37:26 +0530 Subject: [PATCH 122/182] revert Renames LMQ -> OMQ in the codebase. --- src/common/CMakeLists.txt | 1 - src/cryptonote_basic/cryptonote_basic.h | 13 +- .../cryptonote_format_utils.h | 8 +- src/cryptonote_core/cryptonote_core.cpp | 4 +- src/cryptonote_core/cryptonote_core.h | 4 +- src/cryptonote_core/pos.cpp | 4 +- src/daemon/daemon.cpp | 2 +- src/daemon/daemon.h | 2 +- src/ringct/rctTypes.h | 24 +-- src/rpc/CMakeLists.txt | 2 +- src/rpc/common/CMakeLists.txt | 1 + src/rpc/common/command_decorators.cpp | 4 +- src/rpc/common/command_decorators.h | 10 +- src/rpc/common/param_parser.hpp | 8 +- .../{json_binary_proxy.cpp => rpc_binary.cpp} | 4 +- .../{json_binary_proxy.h => rpc_binary.h} | 40 ++-- src/rpc/core_rpc_server.cpp | 34 +--- src/rpc/core_rpc_server_binary_commands.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 9 +- src/rpc/core_rpc_server_commands_defs.cpp | 4 +- src/rpc/core_rpc_server_commands_defs.h | 12 +- src/rpc/http_server.cpp | 8 +- src/rpc/{omq_server.cpp => lmq_server.cpp} | 43 ++-- src/rpc/{omq_server.h => lmq_server.h} | 2 +- src/serialization/binary_archive.h | 51 +++-- src/serialization/container.h | 3 +- src/serialization/json_archive.h | 191 +++++++++++------- src/serialization/json_utils.h | 60 ++++++ src/serialization/pair.h | 6 +- 29 files changed, 327 insertions(+), 231 deletions(-) rename src/rpc/common/{json_binary_proxy.cpp => rpc_binary.cpp} (96%) rename src/rpc/common/{json_binary_proxy.h => rpc_binary.h} (74%) rename src/rpc/{omq_server.cpp => lmq_server.cpp} (94%) rename src/rpc/{omq_server.h => lmq_server.h} (97%) create mode 100755 src/serialization/json_utils.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 265399842a5..b4d4a98b311 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -36,7 +36,6 @@ add_library(common expect.cpp file.cpp i18n.cpp - json_binary_proxy.cpp beldex.cpp notify.cpp password.cpp diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index b909511a69b..c5a904288f0 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -36,6 +36,7 @@ #include "serialization/variant.h" #include "serialization/vector.h" #include "serialization/binary_archive.h" +#include "serialization/json_archive.h" #include "serialization/crypto.h" #include "epee/serialization/keyvalue_serialization.h" // eepe named serialization #include "cryptonote_config.h" @@ -274,18 +275,16 @@ namespace cryptonote set_blob_size_valid(false); } - unsigned int start_pos = 0; - if constexpr (Binary) - start_pos = ar.streampos(); + const unsigned int start_pos = Binary ? ar.streampos() : 0; serialization::value(ar, static_cast(*this)); - if constexpr (Binary) + if (Binary) prefix_size = ar.streampos() - start_pos; if (version == txversion::v1) { - if constexpr (Binary) + if (Binary) unprunable_size = ar.streampos() - start_pos; ar.tag("signatures"); @@ -312,7 +311,7 @@ namespace cryptonote else if (signature_size != signatures[i].size()) throw std::invalid_argument{"Invalid signature size (expected " + std::to_string(signature_size) + ", have " + std::to_string(signatures[i].size()) + ")"}; - value(ar, signatures[i]); + value(arr.element(), signatures[i]); } } else @@ -325,7 +324,7 @@ namespace cryptonote rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); } - if constexpr (Binary) + if (Binary) unprunable_size = ar.streampos() - start_pos; if (!pruned && rct_signatures.type != rct::RCTType::Null) diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 8318711455a..6ae77967351 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -39,7 +39,6 @@ #include "common/meta.h" #include "common/string_util.h" #include "serialization/binary_utils.h" -#include "serialization/json_archive.h" #include namespace epee @@ -320,12 +319,15 @@ namespace cryptonote template std::string obj_to_json_str(T&& obj, bool indent = false) { + std::ostringstream ss; + serialization::json_archiver ar{ss, indent}; try { - return serialization::dump_json(obj, indent ? 2 : -1); + serialize(ar, obj); } catch (const std::exception& e) { LOG_ERROR("obj_to_json_str failed: serialization failed: " << e.what()); + return ""s; } - return ""s; + return ss.str(); } //--------------------------------------------------------------- blobdata block_to_blob(const block& b); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 55716b4f665..14000e941e1 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -981,7 +981,7 @@ namespace cryptonote MGINFO_YELLOW("- x25519: " << tools::type_to_hex(keys.pub_x25519)); } else { // Only print the x25519 version because it's the only thing useful for a non-MN (for - // encrypted OMQ RPC connections). + // encrypted LMQ RPC connections). MGINFO_YELLOW("x25519 public key: " << tools::type_to_hex(keys.pub_x25519)); } @@ -2334,7 +2334,7 @@ namespace cryptonote MGINFO_RED( "Another master node (" << pk << ") is broadcasting the same public IP and ports as this master node (" << epee::string_tools::get_ip_string_from_int32(m_mn_public_ip) << ":" << proof.proof->qnet_port << "[qnet], :" << - proof.proof->storage_https_port << "[SS-HTTP], :" << proof.proof->storage_omq_port << "[SS-OMQ]). " + proof.proof->storage_https_port << "[SS-HTTP], :" << proof.proof->storage_omq_port << "[SS-LMQ]). " "This will lead to deregistration of one or both master nodes if not corrected. " "(Do both master nodes have the correct IP for the master-node-public-ip setting?)"); }); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index ccddda5d495..29eaa2b373a 100755 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1205,8 +1205,8 @@ namespace cryptonote // avoid linking issues (protocol does not link against core). void* m_quorumnet_state = nullptr; - /// Stores x25519 -> access level for OMQ authentication. - /// Not to be modified after the OMQ listener starts. + /// Stores x25519 -> access level for LMQ authentication. + /// Not to be modified after the LMQ listener starts. std::unordered_map m_omq_auth; size_t block_sync_size; diff --git a/src/cryptonote_core/pos.cpp b/src/cryptonote_core/pos.cpp index 8c8e8133fe4..3c8c6bf1292 100755 --- a/src/cryptonote_core/pos.cpp +++ b/src/cryptonote_core/pos.cpp @@ -762,13 +762,13 @@ bool POS::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t b /* POS progresses via a state-machine that is iterated through job submissions - to 1 dedicated POS thread, started by OMQ. + to 1 dedicated POS thread, started by LMQ. Iterating the state-machine is done by a periodic invocation of POS::main(...) and messages received via Quorumnet for POS, which are queued in the thread's job queue. - Using 1 dedicated thread via OMQ avoids any synchronization required in the + Using 1 dedicated thread via LMQ avoids any synchronization required in the user code when implementing POS. Skip control flow graph for textual description of stages. diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index b64b1aade00..b8ae389b4c8 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -43,7 +43,7 @@ #endif #include "rpc/common/rpc_args.h" #include "rpc/http_server.h" -#include "rpc/omq_server.h" +#include "rpc/lmq_server.h" // #include "rpc/bootstrap_daemon.h" #include "cryptonote_protocol/quorumnet.h" #include "cryptonote_core/uptime_proof.h" diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index 6838c6c4eaa..ed3d8cf8a52 100755 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -34,7 +34,7 @@ #include "p2p/net_node.h" #include "rpc/core_rpc_server.h" #include "rpc/http_server.h" -#include "rpc/omq_server.h" +#include "rpc/lmq_server.h" #include "blocks/blocks.h" #include "rpc/core_rpc_server.h" diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 8dd42a6ab44..aee1c2cea4f 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -303,7 +303,7 @@ namespace rct { { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& e : pseudoOuts) - value(ar, e); + value(arr.element(), e); } { @@ -311,21 +311,21 @@ namespace rct { if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG)) { for (auto& e : ecdhInfo) { - auto obj = ar.begin_object(); + auto obj = arr.element().begin_object(); if (Archive::is_deserializer) memset(e.amount.bytes, 0, sizeof(e.amount.bytes)); field(ar, "amount", reinterpret_cast(e.amount)); } } else { for (auto& e : ecdhInfo) - value(ar, e); + value(arr.element(), e); } } { auto arr = start_array(ar, "outPk", outPk, outputs); for (auto& e : outPk) - value(ar, e.mask); + value(arr.element(), e.mask); } } }; @@ -356,7 +356,7 @@ namespace rct { auto arr = start_array(ar, "bp", bulletproofs, nbp); for (auto& b : bulletproofs) - value(ar, b); + value(arr.element(), b); if (auto n_max = n_bulletproof_max_amounts(bulletproofs); n_max < outputs) throw std::invalid_argument{"invalid bulletproofs: n_max (" + std::to_string(n_max) + ") < outputs (" + std::to_string(outputs) + ")"}; @@ -365,7 +365,7 @@ namespace rct { { auto arr = start_array(ar, "rangeSigs", rangeSigs, outputs); for (auto& s : rangeSigs) - value(ar, s); + value(arr.element(), s); } if (type == RCTType::CLSAG) @@ -377,11 +377,11 @@ namespace rct { // we save the CLSAGs contents directly, because we want it to save its // arrays without the size prefixes, and the load can't know what size // to expect if it's not in the data - auto obj = ar.begin_object(); + auto obj = arr.element().begin_object(); { auto arr_s = start_array(ar, "s", clsag.s, mixin + 1); for (auto& x : clsag.s) - value(ar, x); + value(arr_s.element(), x); } field(ar, "c1", clsag.c1); field(ar, "D", clsag.D); @@ -402,7 +402,7 @@ namespace rct { auto arr = start_array(ar, "MGs", MGs, mg_elements); for (auto& mg : MGs) { - auto obj = ar.begin_object(); + auto obj = arr.element().begin_object(); // we save the MGs contents directly, because we want it to save its // arrays and matrices without the size prefixes, and the load can't @@ -411,14 +411,14 @@ namespace rct { auto arr_ss = start_array(ar, "ss", mg.ss, mixin + 1); for (auto& ss : mg.ss) { - auto arr_ss2 = ar.begin_array(); + auto arr_ss2 = arr_ss.element().begin_array(); if constexpr (Archive::is_deserializer) ss.resize(mg_ss2_elements); else if (ss.size() != mg_ss2_elements) throw std::invalid_argument{"invalid mg_ss2 size: have " + std::to_string(ss.size()) + ", expected " + std::to_string(mg_ss2_elements)}; for (auto& x : ss) - value(ar, x); + value(arr_ss2.element(), x); } } field(ar, "cc", mg.cc); @@ -430,7 +430,7 @@ namespace rct { { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& o : pseudoOuts) - value(ar, o); + value(arr.element(), o); } } diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index c7b6c30a71c..1d7b1afb09e 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -42,7 +42,7 @@ add_library(rpc add_library(daemon_rpc_server http_server.cpp - omq_server.cpp + lmq_server.cpp ) add_library(rpc_http_client diff --git a/src/rpc/common/CMakeLists.txt b/src/rpc/common/CMakeLists.txt index 6e90f577954..e15a959f002 100644 --- a/src/rpc/common/CMakeLists.txt +++ b/src/rpc/common/CMakeLists.txt @@ -1,4 +1,5 @@ add_library(rpc_common + rpc_binary.cpp command_decorators.cpp json_bt.cpp rpc_args.cpp diff --git a/src/rpc/common/command_decorators.cpp b/src/rpc/common/command_decorators.cpp index 9bee00c966a..7d4d1b45662 100644 --- a/src/rpc/common/command_decorators.cpp +++ b/src/rpc/common/command_decorators.cpp @@ -5,8 +5,8 @@ namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { bt = true; - response_b64.format = tools::json_binary_proxy::fmt::bt; - response_hex.format = tools::json_binary_proxy::fmt::bt; + response_b64.format = json_binary_proxy::fmt::bt; + response_hex.format = json_binary_proxy::fmt::bt; } } // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/common/command_decorators.h b/src/rpc/common/command_decorators.h index 72d8837d1dd..83ac433fc05 100644 --- a/src/rpc/common/command_decorators.h +++ b/src/rpc/common/command_decorators.h @@ -1,6 +1,6 @@ #pragma once -#include "common/json_binary_proxy.h" +#include "rpc_binary.h" #include @@ -17,7 +17,7 @@ namespace cryptonote::rpc { /// Base class that all RPC commands must inherit from (either directly or via one or more of the /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC - /// command. For OMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be + /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be /// at `whatever`. This base class is also where response objects are stored. struct RPC_COMMAND { private: @@ -58,7 +58,7 @@ namespace cryptonote::rpc { /// Usage: /// std::string data = "abc"; /// rpc.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" - tools::json_binary_proxy response_hex{response, tools::json_binary_proxy::fmt::hex}; + json_binary_proxy response_hex{response, json_binary_proxy::fmt::hex}; /// Proxy object that encodes binary data as base64 for json, leaving it as binary for /// bt-encoded responses. @@ -66,13 +66,13 @@ namespace cryptonote::rpc { /// Usage: /// std::string data = "abc"; /// rpc.response_b64["foo"]["bar"] = data; // json: "YWJj", bt: "abc" - tools::json_binary_proxy response_b64{response, tools::json_binary_proxy::fmt::base64}; + json_binary_proxy response_b64{response, json_binary_proxy::fmt::base64}; }; /// Tag types that are used (via inheritance) to set rpc endpoint properties /// Specifies that the RPC call is public (i.e. available through restricted rpc). If this is - /// *not* inherited from then the command is restricted (i.e. only available to admins). For OMQ, + /// *not* inherited from then the command is restricted (i.e. only available to admins). For LMQ, /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). struct PUBLIC : virtual RPC_COMMAND {}; diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index da4d61dbe01..dbf08651c38 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -1,6 +1,6 @@ #pragma once -#include "common/json_binary_proxy.h" +#include "rpc/common/rpc_binary.h" #include #include @@ -127,8 +127,8 @@ namespace cryptonote::rpc { val = c.template consume_integer(); else if constexpr (std::is_same_v || std::is_same_v) val = c.consume_string_view(); - else if constexpr (tools::json_is_binary) - tools::load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + else if constexpr (is_binary_parameter) + load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); else if constexpr (is_expandable_list) { auto lc = c.consume_list_consumer(); val.clear(); @@ -182,7 +182,7 @@ namespace cryptonote::rpc { val = i; } else if constexpr (std::is_same_v || std::is_same_v) { val = e.get(); - } else if constexpr (tools::json_is_binary || is_expandable_list || is_tuple_like) { + } else if constexpr (is_binary_parameter || is_expandable_list || is_tuple_like) { try { e.get_to(val); } catch (const std::exception& e) { throw std::domain_error{"Invalid values in '" + key + "'"}; } } else { diff --git a/src/rpc/common/json_binary_proxy.cpp b/src/rpc/common/rpc_binary.cpp similarity index 96% rename from src/rpc/common/json_binary_proxy.cpp rename to src/rpc/common/rpc_binary.cpp index ac2b1d7fbc3..eef636c4737 100644 --- a/src/rpc/common/json_binary_proxy.cpp +++ b/src/rpc/common/rpc_binary.cpp @@ -1,8 +1,8 @@ -#include "json_binary_proxy.h" +#include "rpc_binary.h" #include #include -namespace tools { +namespace cryptonote::rpc { void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) { if (allow_raw && bytes.size() == raw_size) { diff --git a/src/rpc/common/json_binary_proxy.h b/src/rpc/common/rpc_binary.h similarity index 74% rename from src/rpc/common/json_binary_proxy.h rename to src/rpc/common/rpc_binary.h index 42f8ddc8bd8..f202a6bb6b7 100644 --- a/src/rpc/common/json_binary_proxy.h +++ b/src/rpc/common/rpc_binary.h @@ -8,37 +8,37 @@ using namespace std::literals; -namespace tools { +namespace cryptonote::rpc { // Binary types that we support for rpc input/output. For json, these must be specified as hex or // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. template - inline constexpr bool json_is_binary = false; - template <> inline constexpr bool json_is_binary = true; - template <> inline constexpr bool json_is_binary = true; - template <> inline constexpr bool json_is_binary = true; - template <> inline constexpr bool json_is_binary = true; - template <> inline constexpr bool json_is_binary = true; - template <> inline constexpr bool json_is_binary = true; + inline constexpr bool is_binary_parameter = false; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; + template <> inline constexpr bool is_binary_parameter = true; template - inline constexpr bool json_is_binary_container = false; + inline constexpr bool is_binary_container = false; template - inline constexpr bool json_is_binary_container> = json_is_binary; + inline constexpr bool is_binary_container> = is_binary_parameter; template - inline constexpr bool json_is_binary_container> = json_is_binary; + inline constexpr bool is_binary_container> = is_binary_parameter; // De-referencing wrappers around the above: - template inline constexpr bool json_is_binary = json_is_binary; - template inline constexpr bool json_is_binary = json_is_binary; - template inline constexpr bool json_is_binary_container = json_is_binary_container; - template inline constexpr bool json_is_binary_container = json_is_binary_container; + template inline constexpr bool is_binary_parameter = is_binary_parameter; + template inline constexpr bool is_binary_parameter = is_binary_parameter; + template inline constexpr bool is_binary_container = is_binary_container; + template inline constexpr bool is_binary_container = is_binary_container; void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw // bytes. - template >> + template >> void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) { load_binary_parameter_impl(bytes, sizeof(T), allow_raw, reinterpret_cast(&val)); } @@ -82,13 +82,13 @@ namespace tools { /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its /// contents as the binary value. - template , int> = 0> + template , int> = 0> nlohmann::json& operator=(const T& val) { return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; } /// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning /// each one into a new array of binary values. - template , int> = 0> + template , int> = 0> nlohmann::json& operator=(const T& vals) { auto a = nlohmann::json::array(); for (auto& val : vals) @@ -122,14 +122,14 @@ namespace tools { // invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. namespace nlohmann { template - struct adl_serializer>> { + struct adl_serializer>> { static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); static void to_json(json& j, const T&) { throw std::logic_error{"Internal error: binary types are not directly serializable"}; } static void from_json(const json& j, T& val) { - tools::load_binary_parameter(j.get(), false /*no raw*/, val); + cryptonote::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); } }; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 4071c7c283e..7519ccca1d6 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -37,7 +37,6 @@ #include #include #include -#include "common/json_binary_proxy.h" #include #include "epee/net/network_throttle.hpp" #include "common/string_util.h" @@ -71,7 +70,6 @@ #include "net/parse.h" #include "crypto/hash.h" #include "p2p/net_node.h" -#include "serialization/json_archive.h" #include "version.h" #include @@ -82,8 +80,6 @@ namespace cryptonote::rpc { using nlohmann::json; - using tools::json_binary_proxy; - namespace { template @@ -664,13 +660,13 @@ namespace cryptonote::rpc { // a single one we want just the value itself; this does that. Returns a reference to the // assigned value (whether as a top-level value or array element). template - json& set(const std::string& key, T&& value, bool binary = tools::json_is_binary_parameter || tools::json_is_binary_container) { + json& set(const std::string& key, T&& value, bool binary = is_binary_parameter || is_binary_container) { auto* x = &entry[key]; if (!x->is_null() && !x->is_array()) x = &(entry[key] = json::array({std::move(*x)})); if (x->is_array()) x = &x->emplace_back(); - if constexpr (tools::json_is_binary || tools::json_is_binary_container || std::is_convertible_v) { + if constexpr (is_binary_parameter || is_binary_container || std::is_convertible_v) { if (binary) return json_binary_proxy{*x, format} = std::forward(value); } @@ -1025,31 +1021,12 @@ namespace cryptonote::rpc { return; } } - std::optional extra; if (get.request.tx_extra) - load_tx_extra_data(extra.emplace(), tx, nettype(), hf_version, get.is_bt()); - if (get.request.tx_extra_raw) - e_bin["tx_extra_raw"] = std::string_view{reinterpret_cast(tx.extra.data()), tx.extra.size()}; - - // Clear it because we don't want/care about it in the RPC output (we already got it more - // usefully from the above). - tx.extra.clear(); - - { - serialization::json_archiver ja{ - get.is_bt() ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex}; + load_tx_extra_data(e["extra"], tx, nettype(), hf_version, get.is_bt()); - serialize(ja, tx); - auto dumped = std::move(ja).json(); - e.update(dumped); - } - - if (extra) - e["extra"] = std::move(*extra); - else - e.erase("extra"); auto ptx_it = found_in_pool.find(tx_hash); bool in_pool = ptx_it != found_in_pool.end(); + e["in_pool"] = in_pool; auto height = std::numeric_limits::max(); if (uint64_t fee, burned; get_tx_miner_fee(tx, fee, hf_version >= feature::FEE_BURNING, &burned)) { @@ -1060,7 +1037,6 @@ namespace cryptonote::rpc { if (in_pool) { const auto& meta = ptx_it->second.meta; - e["in_pool"] = true; e["weight"] = meta.weight; e["relayed"] = (bool) ptx_it->second.meta.relayed; e["received_timestamp"] = ptx_it->second.meta.receive_time; @@ -2996,7 +2972,7 @@ namespace cryptonote::rpc { update = 0; // Reset our last ping time to 0 so that we won't send a ping until we get // success back again (even if we had an earlier acceptable ping within the // cutoff time). - else if (cur_version < required) { + } else if (cur_version < required) { status = fmt::format("Outdated {}. Current: {}.{}.{}, Required: {}.{}.{}",name, cur_version[0], cur_version[1], cur_version[2], required[0], required[1], required[2]); MERROR(status); } else if (!pubkey_ed25519.empty() && !(pubkey_ed25519.find_first_not_of('0') == std::string_view::npos) // TODO: once belnet & ss are always sending this we can remove this empty bypass diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index 97be1d49206..a79df8d2655 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -239,8 +239,8 @@ namespace cryptonote::rpc { struct request { bool flashed_txs_only; // Optional: If true only transactions that were sent via flash and approved are queried. - bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using OMQ RPC. - crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using OMQ RPC. + bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using LMQ RPC. + crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using LMQ RPC. KV_MAP_SERIALIZABLE }; diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 2c2b85ef416..e99898c53c0 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -114,15 +114,20 @@ namespace cryptonote::rpc { if (auto it = json_in->find("txs_hashes"); it != json_in->end()) (*json_in)["tx_hashes"] = std::move(*it); + std::optional data; get_values(in, - "data", get.request.data, + "data", data, "memory_pool", get.request.memory_pool, "prune", get.request.prune, "split", get.request.split, "tx_extra", get.request.tx_extra, - "tx_extra_raw", get.request.tx_extra_raw, "tx_hashes", get.request.tx_hashes); + if (data) + get.request.data = *data; + else + get.request.data = !(get.request.prune || get.request.split); + if (get.request.memory_pool && !get.request.tx_hashes.empty()) throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 072affa064d..010c10e5949 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -56,8 +56,8 @@ namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { bt = true; - response_b64.format = tools::json_binary_proxy::fmt::bt; - response_hex.format = tools::json_binary_proxy::fmt::bt; + response_b64.format = json_binary_proxy::fmt::bt; + response_hex.format = json_binary_proxy::fmt::bt; } void to_json(nlohmann::json& j, const block_header_response& h) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 7595e33ce6a..f4905d9b149 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -45,6 +45,7 @@ // setlocal comments+=:// #include "rpc/common/rpc_version.h" +#include "rpc/common/rpc_binary.h" #include "rpc/common/command_decorators.h" #include "crypto/crypto.h" @@ -174,7 +175,7 @@ namespace cryptonote::rpc { /// - \p double_spend_seen -- set to true if one or more outputs in this mempool transaction /// have already been spent (and thus the tx cannot currently be added to the blockchain). /// - \p data -- Full, unpruned transaction data. For a json request this is hex-encoded; for a - /// bt-encoded request this is raw bytes. This field is omitted if any of `tx_extra`, + /// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`, /// `split`, or `prune` is requested; or if the transaction has been pruned in the database. /// - \p pruned -- The non-prunable part of the transaction, encoded as hex (for json requests). /// Always included if `split` or `prune` are specified; without those options it will be @@ -284,13 +285,10 @@ namespace cryptonote::rpc { bool memory_pool = false; /// If set to true then parse and return tx-extra information bool tx_extra = false; - /// If set to true then include the raw tx-extra information in the tx_extra_raw field. This - /// will be hex-encoded for json, raw bytes for bt-encoded requests. - bool tx_extra_raw = false; /// Controls whether the `data` (or `pruned`, if pruned) field containing raw tx data is - /// included. By default it is not included; you typically want `details` rather than this - /// field. - bool data = false; + /// included: if explicitly specified then the raw data will be included if true. Otherwise + /// the raw data is included only when neither of `split` nor `prune` are set to true. + bool data = true; /// If set to true then always split transactions into non-prunable and prunable parts in the /// response. bool split = false; diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index 214145413a1..fca49ac5e62 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -89,7 +89,7 @@ namespace cryptonote::rpc { : m_server{server}, m_restricted{restricted} { // uWS is designed to work from a single thread, which is good (we pull off the requests and - // then stick them into the OMQ job queue to be scheduled along with other jobs). But as a + // then stick them into the LMQ job queue to be scheduled along with other jobs). But as a // consequence, we need to create everything inside that thread. We *also* need to get the // (thread local) event loop pointer back from the thread so that we can shut it down later // (injecting a callback into it is one of the few thread-safe things we can do across threads). @@ -270,7 +270,7 @@ namespace cryptonote::rpc { void invoke_txpool_hashes_bin(std::shared_ptr data); - // Invokes the actual RPC request; this is called (via lokimq) from some random OMQ worker thread, + // Invokes the actual RPC request; this is called (via lokimq) from some random LMQ worker thread, // which means we can't just write our reply; instead we have to post it to the uWS loop. void invoke_rpc(std::shared_ptr dataptr) { @@ -494,7 +494,7 @@ namespace cryptonote::rpc { auto& omq = data->core_rpc.get_core().get_omq(); std::string cat{data->call->is_public ? "rpc" : "admin"}; - std::string cmd{"http:" + data->uri}; // Used for OMQ job logging; prefixed with http: so we can distinguish it + std::string cmd{"http:" + data->uri}; // Used for LMQ job logging; prefixed with http: so we can distinguish it std::string remote{data->request.context.remote}; omq.inject_task(std::move(cat), std::move(cmd), std::move(remote), [data=std::move(data)] { invoke_rpc(std::move(data)); }); }); @@ -560,7 +560,7 @@ namespace cryptonote::rpc { auto& omq = data->core_rpc.get_core().get_omq(); std::string cat{data->call->is_public ? "rpc" : "admin"}; - std::string cmd{"jsonrpc:" + *method}; // Used for OMQ job logging; prefixed with jsonrpc: so we can distinguish it + std::string cmd{"jsonrpc:" + *method}; // Used for LMQ job logging; prefixed with jsonrpc: so we can distinguish it std::string remote{data->request.context.remote}; omq.inject_task(std::move(cat), std::move(cmd), std::move(remote), [data=std::move(data)] { invoke_rpc(std::move(data)); }); }); diff --git a/src/rpc/omq_server.cpp b/src/rpc/lmq_server.cpp similarity index 94% rename from src/rpc/omq_server.cpp rename to src/rpc/lmq_server.cpp index afd3066414b..d0c9ff44525 100755 --- a/src/rpc/omq_server.cpp +++ b/src/rpc/lmq_server.cpp @@ -1,11 +1,12 @@ -#include "omq_server.h" +#include "lmq_server.h" #include "rpc/common/param_parser.hpp" #include "cryptonote_config.h" #include "oxenmq/oxenmq.h" #include "oxenc/bt.h" #include +// FIXME: Rename this to omq_server.{h,cpp} #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc" @@ -51,7 +52,7 @@ const command_line::arg_descriptor> arg_omq_local_contr #ifndef _WIN32 const command_line::arg_descriptor arg_omq_umask{ "lmq-umask", - "Sets the umask to apply to any listening ipc:///path/to/sock OMQ sockets, in octal.", + "Sets the umask to apply to any listening ipc:///path/to/sock LMQ sockets, in octal.", "0007"}; #endif @@ -69,20 +70,20 @@ auto as_x_pubkeys(const std::vector& pk_strings) { pks.reserve(pk_strings.size()); for (const auto& pkstr : pk_strings) { if (pkstr.size() != 64 || !oxenc::is_hex(pkstr)) - throw std::runtime_error("Invalid OMQ login pubkey: '" + pkstr + "'; expected 64-char hex pubkey"); + throw std::runtime_error("Invalid LMQ login pubkey: '" + pkstr + "'; expected 64-char hex pubkey"); pks.emplace_back(); oxenc::to_hex(pkstr.begin(), pkstr.end(), reinterpret_cast(&pks.back())); } return pks; } -// OMQ RPC responses consist of [CODE, DATA] for code we (partially) mimic HTTP error codes: 200 +// LMQ RPC responses consist of [CODE, DATA] for code we (partially) mimic HTTP error codes: 200 // means success, anything else means failure. (We don't have codes for Forbidden or Not Found -// because those happen at the OMQ protocol layer). +// because those happen at the LMQ protocol layer). constexpr std::string_view - OMQ_OK{"200"sv}, - OMQ_BAD_REQUEST{"400"sv}, - OMQ_ERROR{"500"sv}; + LMQ_OK{"200"sv}, + LMQ_BAD_REQUEST{"400"sv}, + LMQ_ERROR{"500"sv}; } // end anonymous namespace @@ -110,21 +111,21 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // the quorumnet listener set up in cryptonote_core). for (const auto &addr : command_line::get_arg(vm, arg_omq_public)) { check_omq_listen_addr(addr); - MGINFO("OMQ listening on " << addr << " (public unencrypted)"); + MGINFO("LMQ listening on " << addr << " (public unencrypted)"); omq.listen_plain(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::basic); }); } for (const auto &addr : command_line::get_arg(vm, arg_omq_curve_public)) { check_omq_listen_addr(addr); - MGINFO("OMQ listening on " << addr << " (public curve)"); + MGINFO("LMQ listening on " << addr << " (public curve)"); omq.listen_curve(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::basic); }); } for (const auto &addr : command_line::get_arg(vm, arg_omq_curve)) { check_omq_listen_addr(addr); - MGINFO("OMQ listening on " << addr << " (curve restricted)"); + MGINFO("LMQ listening on " << addr << " (curve restricted)"); omq.listen_curve(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::denied); }); } @@ -146,7 +147,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog } for (const auto &addr : locals) { check_omq_listen_addr(addr); - MGINFO("OMQ listening on " << addr << " (unauthenticated local admin)"); + MGINFO("LMQ listening on " << addr << " (unauthenticated local admin)"); omq.listen_plain(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::admin); }); } @@ -194,7 +195,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog omq.add_request_command(cmd.second->is_public ? "rpc" : "admin", cmd.first, [name=std::string_view{cmd.first}, &call=*cmd.second, this](oxenmq::Message& m) { if (m.data.size() > 1) - m.send_reply(OMQ_BAD_REQUEST, "Bad request: RPC commands must have at most one data part " + m.send_reply(LMQ_BAD_REQUEST, "Bad request: RPC commands must have at most one data part " "(received " + std::to_string(m.data.size()) + ")"); rpc_request request{}; @@ -216,7 +217,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog return std::move(v); } }, call.invoke(std::move(request), rpc_)); - m.send_reply(OMQ_OK, std::move(result)); + m.send_reply(LMQ_OK, std::move(result)); return; } catch (const parse_error& e) { // This isn't really WARNable as it's the client fault; log at info level instead. @@ -225,25 +226,25 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // warnings that get generated deep inside epee, for example when passing a string or // number instead of a JSON object. If you want to find some, `grep number2 epee` (for // real). - MINFO("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); + MINFO("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); MDEBUG("Bad request body:" << m.data.empty() ? "(empty)" : m.data[0]); - m.send_reply(OMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); + m.send_reply(LMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); return; } catch (const rpc_error& e) { - MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' failed with: " << e.what()); - m.send_reply(OMQ_ERROR, e.what()); + MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' failed with: " << e.what()); + m.send_reply(LMQ_ERROR, e.what()); return; } catch (const std::exception& e) { - MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " + MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " "raised an exception: " << e.what()); } catch (...) { - MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " + MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " "raised an unknown exception"); } // Don't include the exception message in case it contains something that we don't want go // back to the user. If we want to support it eventually we could add some sort of // `rpc::user_visible_exception` that carries a message to send back to the user. - m.send_reply(OMQ_ERROR, "An exception occured while processing your request"); + m.send_reply(LMQ_ERROR, "An exception occured while processing your request"); }); } diff --git a/src/rpc/omq_server.h b/src/rpc/lmq_server.h similarity index 97% rename from src/rpc/omq_server.h rename to src/rpc/lmq_server.h index f21838fe835..498f1e5e4c4 100755 --- a/src/rpc/omq_server.h +++ b/src/rpc/lmq_server.h @@ -40,7 +40,7 @@ namespace cryptonote::rpc { void init_omq_options(boost::program_options::options_description& desc); /** - * OMQ RPC server class. This doesn't actually hold the OxenMQ instance--that's in + * LMQ RPC server class. This doesn't actually hold the OxenMQ instance--that's in * cryptonote_core--but it works with it to add RPC endpoints, make it listen on RPC ports, and * handles RPC requests. */ diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 726fc2799ef..0b205114d3c 100755 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -57,6 +57,27 @@ static_assert(-1 == ~0, "Non 2s-complement architecture not supported!"); using binary_variant_tag_type = uint8_t; +// RAII class for `begin_array()`. This particular implementation is a no-op. +template +struct binary_archive_nested_array { + Archive& ar; + + // Call before writing an element to add a delimiter. (For binary_archive this is a no-op). + // Returns the archive itself, allowing you to write: + // + // auto arr = ar.begin_array(); + // for (auto& val : whatever) + // value(arr.element(), val); + // + Archive& element() { return ar; } + ~binary_archive_nested_array() {} // Explicitly empty constructor to silent unused variable warnings +}; + +// Do-nothing object for the RAII `begin_object` interface. +struct binary_archive_nested_object { + ~binary_archive_nested_object() {} // As above. +}; + /* \struct binary_unarchiver * * \brief the deserializer class for a binary archive @@ -111,28 +132,23 @@ class binary_unarchiver : public deserializer throw std::runtime_error{"deserialization of varint failed"}; } - // RAII class for `begin_array()`/`begin_object()`. This particular implementation is a no-op. - struct nested { - ~nested() {}; // Avoids unused variable warnings - }; - // Reads array size into s and returns an RAII object to help delimit and end it. - [[nodiscard]] nested begin_array(size_t& s) + [[nodiscard]] binary_archive_nested_array begin_array(size_t& s) { serialize_varint(s); - return {}; + return {*this}; } // Begins a sizeless array (this requires that the size is provided by some other means). - [[nodiscard]] nested begin_array() + [[nodiscard]] binary_archive_nested_array begin_array() { - return {}; + return {*this}; } // Does nothing. (This is used for tag annotations for archivers such as json) void tag(std::string_view) { } - [[nodiscard]] nested begin_object() { return {}; } + [[nodiscard]] binary_archive_nested_object begin_object() { return {}; } void read_variant_tag(binary_variant_tag_type &t) { serialize_int(t); @@ -216,29 +232,24 @@ class binary_archiver : public serializer tools::write_varint(std::ostreambuf_iterator{stream_}, v); } - // RAII class for `begin_array()`/`begin_object()`. This particular implementation is a no-op. - struct nested { - ~nested() {}; // Avoids unused variable warnings - }; - // Begins an array and returns an RAII object that is used to delimit array elements. For // binary_archiver the size is written when the array begins, and the RAII is a no-op. - [[nodiscard]] nested begin_array(size_t& s) + [[nodiscard]] binary_archive_nested_array begin_array(size_t& s) { serialize_varint(s); - return {}; + return {*this}; } // Begins a sizeless array. (Typically requires that size be stored some other way). - [[nodiscard]] nested begin_array() + [[nodiscard]] binary_archive_nested_array begin_array() { - return {}; + return {*this}; } // Does nothing. (This is used for tag annotations for archivers such as json) void tag(std::string_view) { } - [[nodiscard]] nested begin_object() { return {}; } + [[nodiscard]] binary_archive_nested_object begin_object() { return {}; } void write_variant_tag(binary_variant_tag_type t) { serialize_int(t); } diff --git a/src/serialization/container.h b/src/serialization/container.h index 20851633500..4d32f903c7b 100755 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -101,6 +101,7 @@ void serialize_container(Archive& ar, C& v) static_assert(detail::has_emplace_back || detail::has_value_insert, "Unsupported container type"); for (size_t i = 0; i < cnt; i++) { + arr.element(); if constexpr (detail::has_emplace_back) detail::serialize_container_element(ar, v.emplace_back()); else { @@ -118,7 +119,7 @@ void serialize_container(Archive& ar, C& v) size_t cnt = v.size(); auto arr = ar.begin_array(cnt); for (auto& e : v) - serialize_container_element(ar, e); + serialize_container_element(arr.element(), e); } } // namespace detail diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index c8940d6622f..ae05435878d 100755 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, The Beldex Project +// Copyright (c) 2018-2020, The Beldex Project // Copyright (c) 2014-2019, The Monero Project // // All rights reserved. @@ -38,11 +38,11 @@ #include "serialization.h" #include "base.h" -#include "common/json_binary_proxy.h" #include +#include +#include #include -#include -#include +#include namespace serialization { @@ -50,118 +50,159 @@ using json_variant_tag_type = std::string_view; /*! \struct json_archiver * - * \brief serialize data to JSON via nlohmann::json + * \brief a archive using the JSON standard * * \detailed there is no deserializing counterpart; we only support JSON serializing here. */ struct json_archiver : public serializer { - explicit json_archiver(tools::json_binary_proxy::fmt bin_format = tools::json_binary_proxy::fmt::hex) - : bin_format_{bin_format} {} + using variant_tag_type = std::string_view; - /// Returns the current nlohmann::json. - const nlohmann::json& json() const& { return top_; } - nlohmann::json&& json() && { return std::move(top_); } - - /// Dumps the current nlohmann::json; arguments are forwarded to nlohmann::json::dump() - template - auto dump(T&&... args) const { - return top_.dump(std::forward(args)...); + json_archiver(std::ostream& s, bool indent = false) + : stream_{s}, indent_{indent} + { + exc_restore_ = stream_.exceptions(); + stream_.exceptions(std::istream::badbit | std::istream::failbit | std::istream::eofbit); } - // Sets the tag for the next object value we will write. + ~json_archiver() { stream_.exceptions(exc_restore_); } + void tag(std::string_view tag) { - tag_ = tag; + if (!object_begin) + stream_ << (indent_ ? ", "sv : ","sv); + make_indent(); + stream_ << '"' << tag << (indent_ ? "\": "sv : "\":"); + + object_begin = false; } - struct nested_value { + struct nested_object { json_archiver& ar; - ~nested_value() { - assert(ar.stack_.size() >= 2); - ar.stack_.pop_back(); + ~nested_object() { + --ar.depth_; + ar.make_indent(); + ar.stream_ << '}'; } - nested_value(const nested_value&) = delete; - nested_value& operator=(const nested_value&) = delete; - nested_value(nested_value&&) = delete; - nested_value& operator=(nested_value&&) = delete; + nested_object(const nested_object&) = delete; + nested_object& operator=(const nested_object&) = delete; + nested_object(nested_object&&) = delete; + nested_object& operator=(nested_object&&) = delete; }; - [[nodiscard]] nested_value begin_object() + [[nodiscard]] nested_object begin_object() { - stack_.emplace_back(set(nlohmann::json::object())); - return {*this}; + stream_ << '{'; + ++depth_; + object_begin = true; + return nested_object{*this}; } - [[nodiscard]] nested_value begin_array(size_t s=0) + template + static auto promote_to_printable_integer_type(T v) { - stack_.emplace_back(set(nlohmann::json::array())); - return {*this}; + // Unary operator '+' performs integral promotion on type T [expr.unary.op]. + // If T is signed or unsigned char, it's promoted to int and printed as number. + return +v; } template void serialize_int(T v) { - set(v); + stream_ << std::dec << promote_to_printable_integer_type(v); + } + + void serialize_blob(void *buf, size_t len, std::string_view delimiter="\""sv) { + stream_ << delimiter; + auto* begin = static_cast(buf); + oxenc::to_hex(begin, begin + len, std::ostreambuf_iterator{stream_}); + stream_ << delimiter; + } + + template + void serialize_blobs(const std::vector& blobs, std::string_view delimiter="\""sv) { + serialize_blob(blobs.data(), blobs.size()*sizeof(T), delimiter); } template void serialize_varint(T &v) { - serialize_int(v); + stream_ << std::dec << promote_to_printable_integer_type(v); } - void serialize_blob(const void *buf, size_t len) { - nlohmann::json val; - tools::json_binary_proxy{val, bin_format_} = std::string_view{static_cast(buf), len}; - set(std::move(val)); - } + struct nested_array { + json_archiver& ar; + int exc_count = std::uncaught_exceptions(); + bool first = true; + + // Call before writing an element to add a delimiter. The first element() call adds no + // delimiter. Returns the archive itself, allowing you to write: + // + // auto arr = ar.begin_array(); + // for (auto& val : whatever) + // value(arr.element(), val); + // + json_archiver& element() { + if (first) first = false; + else ar.delimit_array(); + return ar; + } - template - void serialize_blobs(const std::vector& blobs) { - serialize_blob(blobs.data(), blobs.size()*sizeof(T)); + ~nested_array() noexcept(false) { + if (std::uncaught_exceptions() == exc_count) { // Normal destruction + --ar.depth_; + if (ar.inner_array_contents_) + ar.make_indent(); + ar.stream_ << ']'; + } + // else we're destructing during a stack unwind so some other serialization failed, thus don't + // try terminating the array (since it might *also* throw if an IO error occurs). + } + + // Non-copyable, non-moveable + nested_array(const nested_array&) = delete; + nested_array& operator=(const nested_array&) = delete; + nested_array(nested_array&&) = delete; + nested_array& operator=(nested_array&&) = delete; + }; + + // Begins an array and returns an RAII object that is used to delimit array elements and + // terminates the array on destruction. + [[nodiscard]] nested_array begin_array(size_t s=0) + { + inner_array_contents_ = s > 0; + ++depth_; + stream_ << '['; + return {*this}; } + void delimit_array() { stream_ << (indent_ ? ", "sv : ","sv); } + void write_variant_tag(std::string_view t) { tag(t); } -private: - nlohmann::json& curr() { - if (stack_.empty()) - return top_; - else - return stack_.back(); - } + // Returns the current position (i.e. stream.tellp()) of the output stream. + unsigned int streampos() { return static_cast(stream_.tellp()); } - template - nlohmann::json& set(T&& val) { - auto& c = curr(); - if (stack_.empty()) { - c = std::forward(val); - return c; - } - if (c.is_array()) { - c.push_back(std::forward(val)); - return c.back(); +private: + static constexpr std::string_view indents{" "}; + void make_indent() + { + if (indent_) + { + stream_ << '\n'; + auto in = 2 * depth_; + for (; in > indents.size(); in -= indents.size()) + stream_ << indents; + stream_ << indents.substr(0, in); } - return (c[tag_] = std::forward(val)); } - nlohmann::json top_; - std::vector> stack_{}; - tools::json_binary_proxy::fmt bin_format_; - std::string tag_; + std::ostream& stream_; + std::ios_base::iostate exc_restore_; + bool indent_ = false; + bool object_begin = false; + bool inner_array_contents_ = false; + size_t depth_ = 0; }; - -/*! serializes the data in v to a string. Throws on error. -*/ -template -std::string dump_json(T& v, int indent = -1) -{ - json_archiver oar; - serialize(oar, v); - return oar.dump(indent); -} - - } // namespace serialization diff --git a/src/serialization/json_utils.h b/src/serialization/json_utils.h new file mode 100755 index 00000000000..82b4eb26141 --- /dev/null +++ b/src/serialization/json_utils.h @@ -0,0 +1,60 @@ +// Copyright (c) 2014-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include +#include "json_archive.h" + +namespace serialization { + +/// Subclass of json_archiver that writes to a std::ostringstream and returns the string on +/// demand. +class json_string_archiver : public json_archiver { + std::ostringstream oss; +public: + /// Constructor; takes no arguments. + json_string_archiver() : json_archiver{oss} {} + + /// Returns the string from the std::ostringstream + std::string str() { return oss.str(); } +}; + +/*! serializes the data in v to a string. Throws on error. +*/ +template +std::string dump_json(T& v) +{ + json_string_archiver oar; + serialize(oar, v); + return oar.str(); +} + +} // namespace serialization diff --git a/src/serialization/pair.h b/src/serialization/pair.h index f2c08f8daf0..8f02e97a0e4 100755 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -58,8 +58,10 @@ void serialize_value(Archive& ar, std::pair& p) if (!Archive::is_serializer && cnt != 2) throw std::runtime_error("Serialization failed: expected pair, found " + std::to_string(cnt) + " values"); - detail::serialize_pair_element(ar, p.first); - detail::serialize_pair_element(ar, p.second); + detail::serialize_pair_element(arr.element(), p.first); + detail::serialize_pair_element(arr.element(), p.second); + + ar.end_array(); } } From c7ef088a7de0e0ac4d0e1e323411f71e195b650f Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 5 May 2025 15:36:57 +0530 Subject: [PATCH 123/182] RPC: BNS_NAMES_TO_OWNERS updated --- src/common/string_util.h | 2 +- .../cryptonote_format_utils.cpp | 1 + src/rpc/core_rpc_server.cpp | 83 +++++++++---------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 7 +- src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 38 ++------- src/wallet/wallet_rpc_server.cpp | 1 + 8 files changed, 56 insertions(+), 79 deletions(-) diff --git a/src/common/string_util.h b/src/common/string_util.h index c0def3ec4b3..265636368bd 100755 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -164,7 +164,7 @@ T make_from_guts(std::string_view s) { if (s.size() != sizeof(T)) throw std::runtime_error("Cannot reconstitute type: wrong type content size"); T x; - std::memcpy(&x, s.data(), sizeof(T)); + std::memcpy(static_cast(&x), s.data(), sizeof(T)); return x; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 38af1e118bc..0c0f5c2b399 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -295,6 +295,7 @@ namespace cryptonote //--------------------------------------------------------------- bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev) { + // This tries to compute the key image using a hardware device, if this succeeds return immediately, otherwise continue if (hwdev.compute_key_image(ack, out_key, recv_derivation, real_output_index, received_index, in_ephemeral, ki)) { return true; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7519ccca1d6..393f58c8fbe 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3206,50 +3206,45 @@ namespace cryptonote::rpc { test_trigger_uptime_proof.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - BNS_NAMES_TO_OWNERS::response core_rpc_server::invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context) + void core_rpc_server::invoke(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_context context) { - BNS_NAMES_TO_OWNERS::response res{}; - if (!context.admin) - check_quantity_limit(req.entries.size(), BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES); + { + check_quantity_limit(bns_names_to_owners.request.name_hash.size(), BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES); + } std::optional height = m_core.get_current_blockchain_height(); auto hf_version = get_network_version(nettype(), *height); - if (req.include_expired) height = std::nullopt; - - std::vector types; + if (bns_names_to_owners.request.include_expired) height = std::nullopt; bns::name_system_db &db = m_core.get_blockchain_storage().name_system_db(); - for (size_t request_index = 0; request_index < req.entries.size(); request_index++) + for (size_t request_index = 0; request_index < bns_names_to_owners.request.name_hash.size(); request_index++) { - std::string const &req_name_hash = req.entries[request_index]; + const auto& request = bns_names_to_owners.request.name_hash[request_index]; // This also takes 32 raw bytes, but that is undocumented (because it is painful to pass // through json). - auto name_hash = bns::name_hash_input_to_base64(req_name_hash); + auto name_hash = bns::name_hash_input_to_base64(bns_names_to_owners.request.name_hash[request_index]); if (!name_hash) throw rpc_error{ERROR_WRONG_PARAM, "Invalid name_hash: expected hash as 64 hex digits or 43/44 base64 characters"}; std::vector records = db.get_mappings(*name_hash, height); for (auto const &record : records) { - auto& entry = res.entries.emplace_back(); - entry.entry_index = request_index; - entry.name_hash = record.name_hash; - entry.owner = record.owner.to_string(nettype()); - if (record.backup_owner) entry.backup_owner = record.backup_owner.to_string(nettype()); - entry.encrypted_bchat_value = oxenc::to_hex(record.encrypted_bchat_value.to_view()); - entry.encrypted_wallet_value = oxenc::to_hex(record.encrypted_wallet_value.to_view()); - entry.encrypted_belnet_value = oxenc::to_hex(record.encrypted_belnet_value.to_view()); - entry.encrypted_eth_addr_value = oxenc::to_hex(record.encrypted_eth_addr_value.to_view()); - entry.expiration_height = record.expiration_height; - entry.update_height = record.update_height; - entry.txid = tools::type_to_hex(record.txid); + auto& elem = bns_names_to_owners.response["result"].emplace_back(); + elem["name_hash"] = record.name_hash; + elem["owner"] = record.owner.to_string(nettype()); + if (record.backup_owner) elem["backup_owner"] = record.backup_owner.to_string(nettype()); + elem["encrypted_bchat_value"] = oxenc::to_hex(record.encrypted_bchat_value.to_view()); + elem["encrypted_wallet_value"] = oxenc::to_hex(record.encrypted_wallet_value.to_view()); + elem["encrypted_belnet_value"] = oxenc::to_hex(record.encrypted_belnet_value.to_view()); + elem["encrypted_eth_addr_value"] = oxenc::to_hex(record.encrypted_eth_addr_value.to_view()); + elem["expiration_height"] = record.expiration_height; + elem["update_height"] = record.update_height; + elem["txid"] = tools::type_to_hex(record.txid); } } - - res.status = STATUS_OK; - return res; + bns_names_to_owners.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ @@ -3259,40 +3254,40 @@ namespace cryptonote::rpc { std::string name = tools::lowercase_ascii_string(std::move(req.name)); - BNS_NAMES_TO_OWNERS::request name_to_owner_req{}; - name_to_owner_req.entries.push_back(bns::name_to_base64_hash(name)); - auto name_to_owner_res = invoke(std::move(name_to_owner_req), context); + BNS_NAMES_TO_OWNERS name_to_owner{}; + name_to_owner.request.name_hash.push_back(bns::name_to_base64_hash(name)); + invoke(name_to_owner, context); - if(name_to_owner_res.entries.size() != 1){ + if(name_to_owner.response["result"].size() != 1){ throw rpc_error{ERROR_INVALID_RESULT, "Invalid data returned from BNS_NAMES_TO_OWNERS"}; } - auto entries = name_to_owner_res.entries.back(); + auto entries = name_to_owner.response["result"].back(); { - res.name_hash = entries.name_hash; - res.owner = entries.owner; - if (entries.backup_owner) res.backup_owner = entries.backup_owner; - res.expiration_height = entries.expiration_height; - res.update_height = entries.update_height; - res.txid = entries.txid; + res.name_hash = entries["name_hash"]; + res.owner = entries["owner"]; + if (!entries["backup_owner"].empty()) res.backup_owner = entries["backup_owner"]; + res.expiration_height = entries["expiration_height"]; + res.update_height = entries["update_height"]; + res.txid = entries["txid"]; - if(!entries.encrypted_bchat_value.empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "bchat", entries.encrypted_bchat_value}; + if(!entries["encrypted_bchat_value"].empty()){ + BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "bchat", entries["encrypted_bchat_value"]}; auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); res.bchat_value = bns_value_decrypt_res.value; } - if(!entries.encrypted_belnet_value.empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "belnet", entries.encrypted_belnet_value}; + if(!entries["encrypted_belnet_value"].empty()){ + BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "belnet", entries["encrypted_belnet_value"]}; auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); res.belnet_value = bns_value_decrypt_res.value; } - if(!entries.encrypted_wallet_value.empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "wallet", entries.encrypted_wallet_value}; + if(!entries["encrypted_wallet_value"].empty()){ + BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "wallet", entries["encrypted_wallet_value"]}; auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); res.wallet_value = bns_value_decrypt_res.value; } - if(!entries.encrypted_eth_addr_value.empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "eth_addr", entries.encrypted_eth_addr_value}; + if(!entries["encrypted_eth_addr_value"].empty()){ + BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "eth_addr", entries["encrypted_eth_addr_value"]}; auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); res.eth_addr_value = bns_value_decrypt_res.value; } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 15c6e0d1df6..3f55a820d3a 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -192,6 +192,7 @@ namespace cryptonote::rpc { void invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context); void invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context); void invoke(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_context context); + void invoke(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_context context); // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -207,7 +208,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - BNS_NAMES_TO_OWNERS::response invoke(BNS_NAMES_TO_OWNERS::request&& req, rpc_context context); BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); BNS_VALUE_DECRYPT::response invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index e99898c53c0..2c559e12fed 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -381,8 +381,13 @@ namespace cryptonote::rpc { "include_expired", bns_owners_to_names.request.include_expired); } - void parse_request(GET_QUORUM_STATE& qs, rpc_input in) { + void parse_request(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_input in) { + get_values(in, + "name_hash", required{bns_names_to_owners.request.name_hash}, + "include_expired", bns_names_to_owners.request.include_expired); + } + void parse_request(GET_QUORUM_STATE& qs, rpc_input in) { get_values(in, "end_height", qs.request.end_height, "quorum_type", qs.request.quorum_type, diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index e9130c7aef5..37ea898c961 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -38,6 +38,7 @@ namespace cryptonote::rpc { void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); void parse_request(BELNET_PING& belnet_ping, rpc_input in); void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in); + void parse_request(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_input in); void parse_request(BNS_RESOLVE& bns, rpc_input in); void parse_request(OUT_PEERS& out_peers, rpc_input in); void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index f4905d9b149..b18b74a86e6 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2087,45 +2087,19 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("test_trigger_uptime_proof"); } }; - BELDEX_RPC_DOC_INTROSPECT // Get the name mapping for a Beldex Name Service entry. Beldex currently supports mappings - // for Bchat and Belnet. + // for Bchat,Wallet,eth_address and Belnet. struct BNS_NAMES_TO_OWNERS : PUBLIC { static constexpr auto names() { return NAMES("bns_names_to_owners", "lns_names_to_owners"); } static constexpr size_t MAX_REQUEST_ENTRIES = 256; - struct request - { - std::vector entries; // Entries to look up - bool include_expired; // Optional: if provided and true, include entries in the results even if they are expired - - KV_MAP_SERIALIZABLE - }; - - struct response_entry - { - uint64_t entry_index; // The index in request_entry's `entries` array that was resolved via Beldex Name Service. - std::string name_hash; // The hash of the name that was queried, in base64 - std::string owner; // The public key that purchased the Beldex Name Service entry. - std::optional backup_owner; // The backup public key that the owner specified when purchasing the Beldex Name Service entry. Omitted if no backup owner. - std::string encrypted_bchat_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - std::string encrypted_wallet_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - std::string encrypted_belnet_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - std::string encrypted_eth_addr_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - uint64_t update_height; // The last height that this Beldex Name Service entry was updated on the Blockchain. - std::optional expiration_height;// For records that expire, this will be set to the expiration block height. - std::string txid; // The txid of the mapping's most recent update or purchase. - KV_MAP_SERIALIZABLE - }; - struct response + struct request_parameters { - std::vector entries; - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE - }; + std::vector name_hash; // The 32-byte BLAKE2b hash of the name to resolve to a public key via Beldex Name Service. The value must be provided either in hex (64 hex digits) or base64 (44 characters with padding, or 43 characters without). + bool include_expired; // Optional: if provided and true, include entries in the results even if they are expired + } request; }; BELDEX_RPC_DOC_INTROSPECT @@ -2340,6 +2314,7 @@ namespace cryptonote::rpc { BELNET_PING, MINING_STATUS, BNS_OWNERS_TO_NAMES, + BNS_NAMES_TO_OWNERS, BNS_RESOLVE, OUT_PEERS, POP_BLOCKS, @@ -2363,7 +2338,6 @@ namespace cryptonote::rpc { RELAY_TX, GET_OUTPUT_DISTRIBUTION, GET_MASTER_NODE_REGISTRATION_CMD, - BNS_NAMES_TO_OWNERS, BNS_LOOKUP, BNS_VALUE_DECRYPT >; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 11810cbe333..4136a4df8c0 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3302,6 +3302,7 @@ namespace { BNS_KNOWN_NAMES::response wallet_rpc_server::invoke(BNS_KNOWN_NAMES::request&& req) { + //TODO sean this needs to fit the new request format require_open(); BNS_KNOWN_NAMES::response res{}; From 7ab6f70b2a9ce361b9993ac1176461c208fc1cc5 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Mon, 5 May 2025 15:59:06 +0530 Subject: [PATCH 124/182] Fix MN cache bug, enforce config validation, and optimize string handling --- CMakeLists.txt | 2 +- .../core_rpc_server_commands_defs.h | 1 + docs/daemon-rpc/make-docs.sh | 58 + docs/daemon-rpc/rpc-to-markdown.py | 376 +++ docs/daemon-rpc/static/index.md | 62 + docs/daemon-rpc/static/preamble-blockchain.md | 4 + docs/daemon-rpc/static/preamble-bns.md | 3 + docs/daemon-rpc/static/preamble-daemon.md | 4 + .../daemon-rpc/static/preamble-master_node.md | 4 + docs/daemon-rpc/static/preamble-network.md | 4 + docs/daemon-rpc/static/sidebar.md | 5 + src/rpc/core_rpc_server.cpp | 47 +- src/rpc/core_rpc_server_binary_commands.cpp | 3 + src/rpc/core_rpc_server_binary_commands.h | 2 + src/rpc/core_rpc_server_command_parser.cpp | 33 +- src/rpc/core_rpc_server_commands_defs.cpp | 4 +- src/rpc/core_rpc_server_commands_defs.h | 2130 ++++++++++------- src/rpc/http_server.cpp | 10 +- src/wallet/node_rpc_proxy.cpp | 31 +- src/wallet/wallet2.cpp | 68 +- 20 files changed, 1888 insertions(+), 963 deletions(-) create mode 100644 docs/daemon-rpc/core_rpc_server_commands_defs.h create mode 100644 docs/daemon-rpc/make-docs.sh create mode 100644 docs/daemon-rpc/rpc-to-markdown.py create mode 100644 docs/daemon-rpc/static/index.md create mode 100644 docs/daemon-rpc/static/preamble-blockchain.md create mode 100644 docs/daemon-rpc/static/preamble-bns.md create mode 100644 docs/daemon-rpc/static/preamble-daemon.md create mode 100644 docs/daemon-rpc/static/preamble-master_node.md create mode 100644 docs/daemon-rpc/static/preamble-network.md create mode 100644 docs/daemon-rpc/static/sidebar.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b8b0986b05..35a7ef8599b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -607,7 +607,7 @@ else() # GCC 12's stringop-overflow warnings are really broken, with tons and tons of false positives all # over the place (not just in our code, but also in its own stdlibc++ code). - if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 12 AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 12) set(CXX_WARNINGS "${CXX_WARNINGS} -Wno-stringop-overflow") endif() diff --git a/docs/daemon-rpc/core_rpc_server_commands_defs.h b/docs/daemon-rpc/core_rpc_server_commands_defs.h new file mode 100644 index 00000000000..7d9b000d289 --- /dev/null +++ b/docs/daemon-rpc/core_rpc_server_commands_defs.h @@ -0,0 +1 @@ +../../src/rpc/core_rpc_server_commands_defs.h \ No newline at end of file diff --git a/docs/daemon-rpc/make-docs.sh b/docs/daemon-rpc/make-docs.sh new file mode 100644 index 00000000000..d19dddf543c --- /dev/null +++ b/docs/daemon-rpc/make-docs.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +if [ "$(basename $(pwd))" != "daemon-rpc" ]; then + echo "Error: you must run this from the docs/daemon-rpc directory" >&2 + exit 1 +fi + +rm -rf api + +docsify init --local api + +rm -f api/README.md + +if [ -n "$NPM_PACKAGES" ]; then + npm_dir="$NPM_PACKAGES/lib/node_modules" +elif [ -n "$NODE_PATH" ]; then + npm_dir="$NODE_PATH" +elif [ -d "$HOME/node_modules" ]; then + npm_dir="$HOME/node_modules" +elif [ -d "/usr/local/lib/node_modules" ]; then + npm_dir="/usr/local/lib/node_modules" +else + echo "Can't determine your node_modules path; set NPM_PACKAGES or NODE_PATH appropriately" >&2 + exit 1 +fi + +cp $npm_dir/docsify/node_modules/prismjs/components/prism-{json,python}.min.js api/vendor + +./rpc-to-markdown.py core_rpc_server_commands_defs.h "$@" + +perl -ni.bak -e ' +BEGIN { $first = 0; } +if (m{^\s*\s*$}) { + if (not $first) { + $first = false; + print qq{ + \n}; + } +} else { + s{.*}{Beldex Daemon RPC}; + s{(name="description" content=)"[^"]*"}{$1"Beldex Daemon RPC endpoint documentation"}; + if (m{^\s*}) { + print qq{ + + \n}; + } + print; +}' api/index.html \ No newline at end of file diff --git a/docs/daemon-rpc/rpc-to-markdown.py b/docs/daemon-rpc/rpc-to-markdown.py new file mode 100644 index 00000000000..89f18c40dcf --- /dev/null +++ b/docs/daemon-rpc/rpc-to-markdown.py @@ -0,0 +1,376 @@ +#!/usr/bin/env python3 + +import sys +import os +import shutil +import re +import fileinput +from enum import Enum, auto +import json +import requests +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument( + "-L", + "--markdown-level", + type=int, + choices=[1, 2, 3, 4], + default=2, + help="Specify a heading level for the top-level endpoints; the default is 2, which means " + "endpoints start in a `## name` section. For example, 3 would start endpoints with `### name` " + "instead.", +) +parser.add_argument("--out", "-o", metavar='DIR', default="api", help="Output directory for generated endpoints") +parser.add_argument("--disable-public", action='store_true', help="disable PUBLIC endpoint detection (and disable marking endpoints as requiring admin)") +parser.add_argument("--disable-no-args", action='store_true', help="disable NO_ARGS enforcement of `Inputs: none`") +parser.add_argument("--dev", action='store_true', help="generate dev mode docs, which include endpoints marked 'Dev-RPC'") +parser.add_argument("--no-sort", "-S", action='store_true', help="disable sorting endpoints by name (use file order)") +parser.add_argument("--no-group", "-G", action='store_true', help="disable grouping endpoints by category") +parser.add_argument("--no-emdash", "-M", action='store_true', help="disable converting ' -- ' to ' — ' (em-dashes)") +parser.add_argument("--rpc", metavar='URL', default="http://public-na.optf.ngo:22023", help="URL to a running beldexd RPC node for live example fetching") +parser.add_argument("filename", nargs="+") +args = parser.parse_args() + +for f in args.filename: + if not os.path.exists(f): + parser.error(f"{f} does not exist!") + + +# We parse the file looking for `///` comment blocks beginning with "RPC: /". +# +# is the RPC endpoint name to use in the documentation (alternative names can be specified +# using "Old names:"; see below). +# +# is the category for grouping endpoints together. +# +# Following comment lines are then a Markdown long description, until we find one or more of: +# +# "Inputs: none." +# "Outputs: none." +# "Inputs:" followed by markdown (typically an unordered list) until the next match from this list. +# "Outputs:" followed by markdown +# "Example input:" followed by a code block (i.e. containing json) +# "Example output:" followed by a code block (i.e. json output) +# "Example-JSON-Fetch" goes and fetches the endpoint (live) with the previous example input as the +# "params" value (or no params if "Inputs: none"). +# "Old names: a, b, c" +# +# subject to the following rules: +# - each section must have exactly one Input; if the type inherits NO_ARGS then it *must* be an +# "Inputs: none". +# - each section must have exactly one Output +# - "Example input:" section must be immediately followed by an "Example output" +# - "Example output:" sections are permitted without a preceding example input only if the endpoint +# takes no inputs. +# - 0 or more example pairs are permitted. +# - Old names is permitted only once, if it occurs at all; the given names will be indicated as +# deprecated, old names for the endpoint. +# +# Immediately following the command we expect to find a not-only-comment line (e.g. `struct +# `) and apply some checks to this: +# - if the line does *not* contain the word `PUBLIC` then we mark the endpoint as requiring admin +# access in its description. +# - if the line contains the word `NO_ARGS` then we double-check that "Inputs: none" was also given +# and error if a more complex Inputs: section was written. + + +hdr = '#' * args.markdown_level +MD_INPUT_HEADER = f"{hdr}# Parameters" +MD_OUTPUT_HEADER = f"{hdr}# Returns" + +MD_EXAMPLES_HEADER = f"{hdr}# Examples" +MD_EXAMPLE_IN_HDR = f"{hdr}## Input" +MD_EXAMPLE_OUT_HDR = f"{hdr}## Output" + +MD_EX_SINGLE_IN_HDR = f"{hdr}# Example Input" +MD_EX_SINGLE_OUT_HDR = f"{hdr}# Example Output" + +MD_NO_INPUT = "This endpoint takes no inputs. _(An optional empty dict/object may be provided, but is not required.)_" +MD_ADMIN = "\n\n> _This endpoint requires admin RPC access; it is not available on public RPC servers._" + +RPC_COMMENT = re.compile(r"^\s*/// ?") +RPC_START = re.compile(r"^RPC:\s*([\w/]+)(.*)$") +DEV_RPC_START = re.compile(r"^Dev-RPC:\s*([\w/]+)(.*)$") +IN_NONE = re.compile(r"^Inputs?: *[nN]one\.?$") +IN_SOME = re.compile(r"^Inputs?:\s*$") +OUT_SOME = re.compile(r"^Outputs?:\s*$") +EXAMPLE_IN = re.compile(r"^Example [iI]nputs?:\s*$") +EXAMPLE_OUT = re.compile(r"^Example [oO]utputs?:\s*$") +EXAMPLE_JSON_FETCH = re.compile(r"^Example-JSON-Fetch\s*$") +OLD_NAMES = re.compile(r"[Oo]ld [nN]ames?:") +PLAIN_NAME = re.compile(r"\w+") +PUBLIC = re.compile(r"\bPUBLIC\b") +NO_ARGS = re.compile(r"\bNO_ARGS\b") + +input = fileinput.input(args.filename) +rpc_name = None + + +def error(msg): + print( + f"\x1b[31;1mERROR\x1b[0m[{input.filename()}:{input.filelineno()}] " + f"while parsing endpoint {rpc_name}:", + file=sys.stderr, + ) + if msg and isinstance(msg, list): + for m in msg: + print(f" - {m}", file=sys.stderr) + else: + print(f" {msg}", file=sys.stderr) + sys.exit(1) + + +class Parsing(Enum): + DESC = auto() + INPUTS = auto() + OUTPUTS = auto() + EX_IN = auto() + EX_OUT = auto() + NONE = auto() + + +cur_file = None +found_some = True + +endpoints = {} + +while True: + line = input.readline() + if not line: + break + + if cur_file is None or cur_file != input.filename(): + if not found_some: + error(f"Found no parseable endpoint descriptions in {cur_file}") + cur_file = input.filename() + found_some = False + + line, removed_comment = re.subn(RPC_COMMENT, "", line, count=1) + if not removed_comment: + continue + + m = re.search(RPC_START, line) + if not m and args.dev: + m = re.search(DEV_RPC_START, line) + if not m: + continue + if m and m[2]: + error(f"found trailing garbage after 'RPC: m[1]': {m[2]}") + if m[1].count('/') != 1: + error(f"Found invalid RPC name: expected 'cat/name', not '{m[1]}'") + + cat, rpc_name = m[1].split('/') + if args.no_group: + cat = '' + description, inputs, outputs = "", "", "" + done_desc = False + no_inputs = False + examples = [] + cur_ex_in = None + old_names = [] + + mode = Parsing.DESC + + while True: + line = input.readline() + line, removed_comment = re.subn(RPC_COMMENT, "", line, count=1) + if not removed_comment: + break + + if re.search(IN_NONE, line): + if inputs: + error("found multiple Inputs:") + inputs, no_inputs, mode = MD_NO_INPUT, True, Parsing.NONE + + elif re.search(IN_SOME, line): + if inputs: + error("found multiple Inputs:") + mode = Parsing.INPUTS + + elif re.search(OUT_SOME, line): + if outputs: + error("found multiple Outputs:") + mode = Parsing.OUTPUTS + + elif re.search(EXAMPLE_IN, line): + if cur_ex_in is not None: + error("found multiple input examples without paired output examples") + cur_ex_in = "" + mode = Parsing.EX_IN + + elif re.search(EXAMPLE_OUT, line): + if not cur_ex_in and not no_inputs: + error( + "found output example without preceding input example (or 'Inputs: none.')" + ) + examples.append([cur_ex_in, ""]) + cur_ex_in = None + mode = Parsing.EX_OUT + + elif re.search(EXAMPLE_JSON_FETCH, line): + if not cur_ex_in and not no_inputs: + error( + "found output example fetch instruction without preceding input (or 'Inputs: none.')" + ) + params = None + if cur_ex_in: + params = cur_ex_in.strip() + if not params.startswith("```json\n"): + error("current example input is not tagged as json for Example-JSON-Fetch") + params = params[8:] + if not params.endswith("\n```"): + error("current example input doesn't look right (expected trailing ```)") + params = params[:-4] + try: + params = json.loads(params) + except Exception as e: + error("failed to parse json example input as json") + + result = requests.post(args.rpc + "/json_rpc", json={"jsonrpc": "2.0", "id": "0", "method": rpc_name, "params": params}).json() + if 'error' in result: + error(f"JSON fetched example returned an error: {result['error']}") + elif 'result' not in result: + error(f"JSON fetched example doesn't contain a \"result\" key: {result}") + ex_out = json.dumps(result["result"], indent=2, sort_keys=True) + + examples.append([cur_ex_in, f"\n```json\n{ex_out}\n```\n"]) + cur_ex_in = None + mode = Parsing.NONE + + elif re.search(OLD_NAMES, line): + old_names = [x.strip() for x in line.split(':', 1)[1].split(',')] + if not old_names or not all(re.fullmatch(PLAIN_NAME, n) for n in old_names): + error(f"found unparseable old names line: {line}") + + elif mode == Parsing.NONE: + if line and not line.isspace(): + error(f"Found unexpected content while looking for a tag: '{line}'") + + elif mode == Parsing.DESC: + description += line + + elif mode == Parsing.INPUTS: + inputs += line + + elif mode == Parsing.OUTPUTS: + outputs += line + + elif mode == Parsing.EX_IN: + cur_ex_in += line + + elif mode == Parsing.EX_OUT: + examples[-1][1] += line + + problems = [] + # We hit the end of the commented section + if not description or inputs.isspace(): + problems.append("endpoint has no description") + if not inputs or inputs.isspace(): + problems.append( + "endpoint has no inputs description; perhaps you need to add 'Inputs: none.'?" + ) + if not outputs or outputs.isspace(): + problems.append("endpoint has no outputs description") + if cur_ex_in is not None: + problems.append( + "endpoint has a trailing example input without a following example output" + ) + if not no_inputs and any(not x[0] or x[0].isspace() for x in examples): + problems.append("found one or more blank input examples") + if any(not x[1] or x[1].isspace() for x in examples): + problems.append("found one or more blank output examples") + + public = args.disable_public or re.search(PUBLIC, line) + if not public: + description += MD_ADMIN + + if old_names: + s = 's' if len(old_names) > 1 else '' + description += f"\n\n> _For backwards compatibility this endpoint is also accessible via the following deprecated endpoint name{s}:_" + for n in old_names: + description += f"\n> - _`{n}`_" + + if not args.disable_no_args: + if re.search(NO_ARGS, line) and not no_inputs: + problems.append("found NO_ARGS, but 'Inputs: none' was specified in description") + + if problems: + error(problems) + + md = f""" +{hdr} `{rpc_name}` + +{description} + +{MD_INPUT_HEADER} + +{inputs} + +{MD_OUTPUT_HEADER} + +{outputs} +""" + + if examples: + if len(examples) > 1: + md += f"\n\n{MD_EXAMPLES_HEADER}\n\n" + for ex in examples: + if ex[0] is not None: + md += f""" +{MD_EXAMPLE_IN_HDR} + +{ex[0]} +""" + md += f""" +{MD_EXAMPLE_OUT_HDR} + +{ex[1]} +""" + + else: + if examples[0][0] is not None: + md += f"\n\n{MD_EX_SINGLE_IN_HDR}\n\n{examples[0][0]}" + md += f"\n\n{MD_EX_SINGLE_OUT_HDR}\n\n{examples[0][1]}" + + if not args.no_emdash: + md = md.replace(" -- ", " — ") + + if cat in endpoints: + endpoints[cat].append((rpc_name, md)) + else: + endpoints[cat] = [(rpc_name, md)] + +if not endpoints: + error(f"Found no parseable endpoint descriptions in {cur_file}") + +if not args.no_sort: + for v in endpoints.values(): + v.sort(key=lambda x: x[0]) + +os.makedirs(args.out, exist_ok=True) + +static_path = os.path.dirname(os.path.realpath(__file__)) + '/static' + +for f in ('index.md', 'sidebar.md'): + shutil.copy(f"{static_path}/{f}", f"{args.out}/{f}") + print(f"Copied static/{f} => {args.out}/{f}") + +preamble_prefix = static_path + '/preamble-' + +for cat, eps in endpoints.items(): + out = f"{args.out}/{cat}.md" + with open(out, "w") as f: + preamble = f"{preamble_prefix}{cat}.md" + if os.path.isfile(preamble): + with open(preamble, "r") as fp: + for line in fp: + f.write(line) + f.write("\n\n") + else: + print(f"Warning: {preamble} doesn't exist, writing generic preamble for {cat}", file=sys.stderr) + f.write(f"# {cat} endpoints\n\n") + + for _, md in eps: + f.write(md) + print(f"Wrote {out}") \ No newline at end of file diff --git a/docs/daemon-rpc/static/index.md b/docs/daemon-rpc/static/index.md new file mode 100644 index 00000000000..9d407932c5c --- /dev/null +++ b/docs/daemon-rpc/static/index.md @@ -0,0 +1,62 @@ +# Beldex Daemon RPC Endpoints + +These pages describe the available RPC endpoints available from a running `beldexd` node. These +endpoints are used for querying blockchain data, submitting transactions, obtaining master node +information, and controlling the running beldexd. + +Many of the endpoints described here are publicly accessible; those that are not are marked +accordingly and can only be used by a local administrator. + +## HTTP JSON access + +Accessing an endpoint using HTTP and JSON can be done by making a JSON RPC request. For example, to +call the `get_info` endpoint on a master node with an HTTP RPC listener on `localhost:22023` (the +default) using JSON RPC you would make a POST request to `http://localhost:22023/json_rpc` with a +JSON body: + +```json +{ + "jsonrpc": "2.0", + "id": "0", + "method": "get_info", + "params": { "foo": 123 } +} +``` + +The pages here describe only the values of the inner "params" value; the outer boilerplate is the +same for all requests. For methods that do not require any input parameters the `"params"` field +may be omitted entirely. + +### Command-line usage + +For example, to make a request using `curl` to the public RPC node `public-na.optf.ngo`, using the +command-line with `jq` to "prettify" the json response: + +``` +curl -sSX POST http://public-na.optf.ngo:22023/json_rpc \ + -d '{"jsonrpc":"2.0","id":"0","method":"get_info"}' | jq . +``` + +## OxenMQ RPC access + +All beldexd endpoints are also available via OxenMQ at either the `rpc.ENDPOINT` or `admin.ENDPOINT` +name (the latter if the endpoint is marked admin-only), with an optional additional data part +containing a JSON or bencoded request. + +### Command-line usage: + +The beldex-core source code contains a script (`utils/lmq-rpc.py`) that can invoke such a request: + +``` +./utils/lmq-rpc.py ipc://$HOME/.beldex/beldexd.sock rpc.get_info | jq . +``` +to query a local beldexd, or: +``` +./utils/lmq-rpc.py tcp://public-na.optf.ngo:22027 02ae9aa1bdface3ce32488874d16671b04d44f611d1076033c92f3379f221161 rpc.get_info | jq . +``` +or +``` +./utils/lmq-rpc.py tcp://public-na.optf.ngo:22029 rpc.get_info '{}' | jq . +``` +to query a public node. (The first version uses an encrypted public connection given the remote +beldexd's X25519 pubkey; the second version uses an unencrypted public connection). \ No newline at end of file diff --git a/docs/daemon-rpc/static/preamble-blockchain.md b/docs/daemon-rpc/static/preamble-blockchain.md new file mode 100644 index 00000000000..9eeff202297 --- /dev/null +++ b/docs/daemon-rpc/static/preamble-blockchain.md @@ -0,0 +1,4 @@ +# Blockchain Endpoints + +These endpoints are used for querying various data from the blockchain; unless otherwise indicated, +these are accessible using a public RPC node. \ No newline at end of file diff --git a/docs/daemon-rpc/static/preamble-bns.md b/docs/daemon-rpc/static/preamble-bns.md new file mode 100644 index 00000000000..19f53d1fbb8 --- /dev/null +++ b/docs/daemon-rpc/static/preamble-bns.md @@ -0,0 +1,3 @@ +# Beldex Name System Query Endpoints + +This endpoints allow querying Beldex Name System records. \ No newline at end of file diff --git a/docs/daemon-rpc/static/preamble-daemon.md b/docs/daemon-rpc/static/preamble-daemon.md new file mode 100644 index 00000000000..f99abea3fa1 --- /dev/null +++ b/docs/daemon-rpc/static/preamble-daemon.md @@ -0,0 +1,4 @@ +# Daemon Administration Endpoints + +These endpoints allow for controlling and querying privileged information about the running beldexd. +They require administrator access. \ No newline at end of file diff --git a/docs/daemon-rpc/static/preamble-master_node.md b/docs/daemon-rpc/static/preamble-master_node.md new file mode 100644 index 00000000000..504c8ce21b9 --- /dev/null +++ b/docs/daemon-rpc/static/preamble-master_node.md @@ -0,0 +1,4 @@ +# Master Node Administration Endpoints + +These endpoints allow administering and controlling an active master node. They generally require +the queried beldexd is running as a master node, and require administrator access. \ No newline at end of file diff --git a/docs/daemon-rpc/static/preamble-network.md b/docs/daemon-rpc/static/preamble-network.md new file mode 100644 index 00000000000..69cba179f8d --- /dev/null +++ b/docs/daemon-rpc/static/preamble-network.md @@ -0,0 +1,4 @@ +# Network Information Endpoints + +These endpoints are used for querying various general information about the network that are not +(directly) blockchain data. \ No newline at end of file diff --git a/docs/daemon-rpc/static/sidebar.md b/docs/daemon-rpc/static/sidebar.md new file mode 100644 index 00000000000..5df6f1a2598 --- /dev/null +++ b/docs/daemon-rpc/static/sidebar.md @@ -0,0 +1,5 @@ +- [Network information](network.md) +- [Blockchain data](blockchain.md) +- [Beldex Name System](bns.md) +- [Daemon administration](daemon.md) +- [Master Node administration](master_node.md) \ No newline at end of file diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 393f58c8fbe..0d978685fec 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -108,7 +108,7 @@ namespace cryptonote::rpc { cmd->is_binary = true; // Legacy binary request; these still use epee serialization, and should be considered - // deprecated (tentatively to be removed in Oxen 11). + // deprecated (tentatively to be removed in Beldex 11). cmd->invoke = [](rpc_request&& request, core_rpc_server& server) -> rpc_command::result_type { typename RPC::request req{}; std::string_view data; @@ -1494,6 +1494,7 @@ namespace cryptonote::rpc { getblockhash.response_hex[tools::int_to_string(h)] = m_core.get_block_id_by_height(h); } + getblockhash.response["height"] = curr_height; getblockhash.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1863,10 +1864,6 @@ namespace cryptonote::rpc { if (i->second > now) { ban b; b.host = i->first; - b.ip = 0; - uint32_t ip; - if (epee::string_tools::get_ip_int32_from_string(ip, b.host)) - b.ip = ip; b.seconds = i->second - now; get_bans.response["bans"].push_back(b); } @@ -1877,7 +1874,6 @@ namespace cryptonote::rpc { if (i->second > now) { ban b; b.host = i->first.host_str(); - b.ip = 0; b.seconds = i->second - now; get_bans.response["bans"].push_back(b); } @@ -1918,32 +1914,22 @@ namespace cryptonote::rpc { epee::net_utils::network_address na; // try subnet first - if (!set_bans.request.host.empty()) + auto ns_parsed = net::get_ipv4_subnet_address(set_bans.request.host); + if (ns_parsed) { - auto ns_parsed = net::get_ipv4_subnet_address(set_bans.request.host); - if (ns_parsed) - { - if (set_bans.request.ban) - m_p2p.block_subnet(*ns_parsed, set_bans.request.seconds); - else - m_p2p.unblock_subnet(*ns_parsed); - set_bans.response["status"] = STATUS_OK; - return; - } + if (set_bans.request.ban) + m_p2p.block_subnet(*ns_parsed, set_bans.request.seconds); + else + m_p2p.unblock_subnet(*ns_parsed); + set_bans.response["status"] = STATUS_OK; + return; } // then host - if (!set_bans.request.host.empty()) - { - auto na_parsed = net::get_network_address(set_bans.request.host, 0); - if (!na_parsed) - throw rpc_error{ERROR_WRONG_PARAM, "Unsupported host/subnet type"}; - na = std::move(*na_parsed); - } - else - { - na = epee::net_utils::ipv4_network_address{set_bans.request.ip, 0}; - } + auto na_parsed = net::get_network_address(set_bans.request.host, 0); + if (!na_parsed) + throw rpc_error{ERROR_WRONG_PARAM, "Unsupported host/subnet type"}; + na = std::move(*na_parsed); if (set_bans.request.ban) m_p2p.block_host(na, set_bans.request.seconds); else @@ -2242,7 +2228,7 @@ namespace cryptonote::rpc { sync.response["height"] = top_height + 1; // turn top block height into blockchain height if (auto target_height = m_core.get_target_blockchain_height(); target_height > top_height + 1) sync.response["target_height"] = target_height; - // Don't put this into the response until it actually does something on Oxen: + // Don't put this into the response until it actually does something on Beldex: if (false) sync.response["next_needed_pruning_seed"] = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second; @@ -2975,8 +2961,7 @@ namespace cryptonote::rpc { } else if (cur_version < required) { status = fmt::format("Outdated {}. Current: {}.{}.{}, Required: {}.{}.{}",name, cur_version[0], cur_version[1], cur_version[2], required[0], required[1], required[2]); MERROR(status); - } else if (!pubkey_ed25519.empty() && !(pubkey_ed25519.find_first_not_of('0') == std::string_view::npos) // TODO: once belnet & ss are always sending this we can remove this empty bypass - && (pubkey_ed25519 != our_pubkey_ed25519)) { + } else if (pubkey_ed25519 != our_pubkey_ed25519) { status = fmt::format("Invalid {} pubkey: expected {}, received {}", name, our_pubkey_ed25519, pubkey_ed25519); MERROR(status); } else { diff --git a/src/rpc/core_rpc_server_binary_commands.cpp b/src/rpc/core_rpc_server_binary_commands.cpp index 3c61c5c49b1..3ddc42c85d3 100644 --- a/src/rpc/core_rpc_server_binary_commands.cpp +++ b/src/rpc/core_rpc_server_binary_commands.cpp @@ -2,6 +2,9 @@ namespace cryptonote::rpc { + KV_SERIALIZE_MAP_CODE_BEGIN(EMPTY) + KV_SERIALIZE_MAP_CODE_END() + void to_json(nlohmann::json& j, const GET_BLOCKS_BIN::tx_output_indices& toi) { j = nlohmann::json{{"indices", toi.indices}}; diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index a79df8d2655..b62be9b75a8 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -35,6 +35,8 @@ namespace cryptonote::rpc { + struct EMPTY { KV_MAP_SERIALIZABLE }; + /// Specifies that the RPC call is legacy, deprecated Monero custom binary input/ouput. If not /// given then the command is JSON/bt-encoded values. For HTTP RPC this also means the command is /// *not* available via the HTTP JSON RPC. diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 2c559e12fed..d14811d8abd 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -225,17 +225,17 @@ namespace cryptonote::rpc { void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in) { get_values(in, - "level", set_log_level.request.level); + "level", required{set_log_level.request.level}); } void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in) { get_values(in, - "categories", set_log_categories.request.categories); + "categories", required{set_log_categories.request.categories}); } void parse_request(BANNED& banned, rpc_input in) { get_values(in, - "address", banned.request.address); + "address", required{banned.request.address}); } void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in) { @@ -268,23 +268,23 @@ namespace cryptonote::rpc { void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ get_values(in, - "nblocks", pop_blocks.request.nblocks); + "nblocks", required{pop_blocks.request.nblocks}); } void parse_request(BELNET_PING& belnet_ping, rpc_input in){ get_values(in, - "version", belnet_ping.request.version, "error", belnet_ping.request.error, - "pubkey_ed25519", belnet_ping.request.pubkey_ed25519); + "pubkey_ed25519", belnet_ping.request.pubkey_ed25519, + "version", required{belnet_ping.request.version}); } void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ get_values(in, - "ed25519_pubkey", storage_server_ping.request.pubkey_ed25519, "error", storage_server_ping.request.error, - "https_port", storage_server_ping.request.https_port, - "omq_port", storage_server_ping.request.omq_port, - "version", storage_server_ping.request.version); + "https_port", required{storage_server_ping.request.https_port}, + "omq_port", required{storage_server_ping.request.omq_port}, + "ed25519_pubkey", required{storage_server_ping.request.pubkey_ed25519}, + "version", required{storage_server_ping.request.version}); } void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ @@ -327,10 +327,9 @@ namespace cryptonote::rpc { void parse_request(SET_BANS& set_bans, rpc_input in) { get_values(in, - "host", set_bans.request.host, - "ip", set_bans.request.ip, - "seconds", set_bans.request.seconds, - "ban", set_bans.request.ban); + "ban", required{set_bans.request.ban}, + "host", required{set_bans.request.host}, + "seconds", required{set_bans.request.seconds}); } void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in) { @@ -377,7 +376,7 @@ namespace cryptonote::rpc { void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in) { get_values(in, - "entries", bns_owners_to_names.request.entries, + "entries", required{bns_owners_to_names.request.entries}, "include_expired", bns_owners_to_names.request.include_expired); } @@ -411,9 +410,9 @@ namespace cryptonote::rpc { void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in) { get_values(in, - "args", cmd.request.args, + "args", required{cmd.request.args}, "make_friendly", cmd.request.make_friendly, - "staking_requirement", cmd.request.staking_requirement); + "staking_requirement", required{cmd.request.staking_requirement}); } void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in) { diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 010c10e5949..c384f2e3835 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -189,8 +189,8 @@ void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(EMPTY) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(EMPTY) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(GET_HEIGHT::response) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index b18b74a86e6..3723f1a3b41 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -103,172 +103,191 @@ namespace cryptonote::rpc { STATUS_NOT_MINING = "NOT MINING", STATUS_TX_LONG_POLL_TIMED_OUT = "Long polling client timed out before txpool had an update"; - /// Generic, serializable, no-argument request or response type, use as - /// `struct request : EMPTY {};` or `using response = EMPTY;` - struct EMPTY { KV_MAP_SERIALIZABLE }; - + /// RPC: blockchain/get_height + /// /// Get the node's current height. /// /// Inputs: none. /// /// Outputs: /// - /// - \p height -- The current blockchain height according to the queried daemon. - /// - \p status -- Generic RPC error code. "OK" is the success value. - /// - \p untrusted -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. - /// - \p hash -- Hash of the block at the current height - /// - \p immutable_height -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 MN checkpoints. Omitted if not available. - /// - \p immutable_hash -- Hash of the highest block in the chain that cannot be reorganized. + /// - `height` -- The current blockchain height according to the queried daemon. + /// - `status` -- Generic RPC error code. "OK" is the success value. + /// - `untrusted` -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// - `hash` -- Hash of the block at the current height + /// - `immutable_height` -- The latest height in the blockchain that cannot be reorganized because of a hardcoded checkpoint or 2 MN checkpoints. Omitted if not available. + /// - `immutable_hash` -- Hash of the highest block in the chain that cannot be reorganized. + /// + /// Example-JSON-Fetch struct GET_HEIGHT : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_height", "getheight"); } }; + /// RPC: blockchain/get_transactions + /// /// Look up one or more transactions by hash. /// + /// Inputs: + /// + /// - `tx_hashes` -- List of transaction hashes to look up. (Will also be accepted as json input + /// key `"txs_hashes"` for backwards compatibility). Exclusive of `memory_pool`. + /// - `memory_pool` -- If true then return all transactions and spent key images currently in the + /// memory pool. This field is exclusive of `tx_hashes`. + /// - `tx_extra` -- If set to true then parse and return tx-extra information + /// - `tx_extra_raw` -- If set to true then include the raw tx-extra information in the + /// `tx_extra_raw` field. This will be hex-encoded for json, raw bytes for bt-encoded requests. + /// - `data` -- Controls whether the `data` (or `pruned`, if pruned) field containing raw tx data + /// is included. By default it is not included; you typically want `details` rather than this + /// field. + /// - `split` -- If set to true then always split transactions into non-prunable and prunable + /// parts in the response. + /// - `prune` -- Like `split`, but also omits the prunable part of transactions from the response + /// details. + /// /// Outputs: /// - /// - \p status -- Generic RPC error code. "OK" is the success value. - /// - \p untrusted -- If the result is obtained using bootstrap mode then this will be set to - /// - \p missed_tx -- set of transaction hashes that were not found. If all were found then this + /// - `status` -- Generic RPC error code. "OK" is the success value. + /// - `untrusted` -- If the result is obtained using bootstrap mode then this will be set to + /// - `missed_tx` -- set of transaction hashes that were not found. If all were found then this /// field is omitted. There is no particular ordering of hashes in this list. - /// - \p txs -- list of transaction details; each element is a dict containing: - /// - \p tx_hash -- Transaction hash. - /// - \p size -- Size of the transaction, in bytes. Note that if the transaction has been pruned + /// - `txs` -- list of transaction details; each element is a dict containing: + /// - `tx_hash` -- Transaction hash. + /// - `size` -- Size of the transaction, in bytes. Note that if the transaction has been pruned /// this is the post-pruning size, not the original size. - /// - \p in_pool -- Will be set to true if the transaction is in the transaction pool (`true`) + /// - `in_pool` -- Will be set to true if the transaction is in the transaction pool (`true`) /// and omitted if mined into a block. - /// - \p flash -- True if this is an approved, flash transaction; this information is generally + /// - `flash` -- True if this is an approved, flash transaction; this information is generally /// only available for approved in-pool transactions and txes in very recent blocks. - /// - \p fee -- the transaction fee (in atomic BELDEX) incurred in this transaction (not including + /// - `fee` -- the transaction fee (in atomic BELDEX) incurred in this transaction (not including /// any burned amount). - /// - \p burned -- the amount of BELDEX (in atomic units) burned by this transaction. - /// - \p block_height -- Block height including the transaction. Omitted for tx pool + /// - `burned` -- the amount of BELDEX (in atomic units) burned by this transaction. + /// - `block_height` -- Block height including the transaction. Omitted for tx pool /// transactions. - /// - \p block_timestamp -- Unix time at which the block has been added to the blockchain. + /// - `block_timestamp` -- Unix time at which the block has been added to the blockchain. /// Omitted for tx pool transactions. - /// - \p output_indices -- List of transaction indexes. Omitted for tx pool transactions. - /// - \p relayed -- For `in_pool` transactions this field will be set to indicate whether the + /// - `output_indices` -- List of transaction indexes. Omitted for tx pool transactions. + /// - `relayed` -- For `in_pool` transactions this field will be set to indicate whether the /// transaction has been relayed to the network. - /// - \p double_spend_seen -- Will be set to true for tx pool transactions that are + /// - `double_spend_seen` -- Will be set to true for tx pool transactions that are /// double-spends (and thus cannot be added to the blockchain). Omitted for mined /// transactions. - /// - \p received_timestamp -- Timestamp transaction was received in the pool. Omitted for + /// - `received_timestamp` -- Timestamp transaction was received in the pool. Omitted for /// mined blocks. - /// - \p max_used_block -- the hash of the highest block referenced by this transaction; only + /// - `max_used_block` -- the hash of the highest block referenced by this transaction; only /// for mempool transactions. - /// - \p max_used_height -- the height of the highest block referenced by this transaction; only + /// - `max_used_height` -- the height of the highest block referenced by this transaction; only /// for mempool transactions. - /// - \p last_failed_block -- the hash of the last block where this transaction was attempted to + /// - `last_failed_block` -- the hash of the last block where this transaction was attempted to /// be mined (but failed). - /// - \p max_used_height -- the height of the last block where this transaction failed to be + /// - `max_used_height` -- the height of the last block where this transaction failed to be /// acceptable for a block. - /// - \p weight -- the transaction "weight" which is the size of the transaction with padding + /// - `weight` -- the transaction "weight" which is the size of the transaction with padding /// removed. Only included for mempool transactions (for mined transactions the size and /// weight at the same and so only `size` is included). - /// - \p kept_by_block will be present and true if this is a mempool transaction that was added + /// - `kept_by_block` will be present and true if this is a mempool transaction that was added /// to the mempool after being popped off a block (e.g. because of a blockchain /// reorganization). - /// - \p last_relayed_time indicates the last time this block was relayed to the network; only + /// - `last_relayed_time` indicates the last time this block was relayed to the network; only /// for mempool transactions. - /// - \p do_not_relay -- set to true for mempool blocks that are marked "do not relay" - /// - \p double_spend_seen -- set to true if one or more outputs in this mempool transaction + /// - `do_not_relay` -- set to true for mempool blocks that are marked "do not relay" + /// - `double_spend_seen` -- set to true if one or more outputs in this mempool transaction /// have already been spent (and thus the tx cannot currently be added to the blockchain). - /// - \p data -- Full, unpruned transaction data. For a json request this is hex-encoded; for a + /// - `data` -- Full, unpruned transaction data. For a json request this is hex-encoded; for a /// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`, /// `split`, or `prune` is requested; or if the transaction has been pruned in the database. - /// - \p pruned -- The non-prunable part of the transaction, encoded as hex (for json requests). + /// - `pruned` -- The non-prunable part of the transaction, encoded as hex (for json requests). /// Always included if `split` or `prune` are specified; without those options it will be /// included instead of `data` if the transaction has been pruned. - /// - \p prunable -- The prunable part of the transaction. Only included when `split` is + /// - `prunable` -- The prunable part of the transaction. Only included when `split` is /// specified, the transaction is prunable, and the tx has not been pruned from the database. - /// - \p prunable_hash -- The hash of the prunable part of the transaction. Will be provided if + /// - `prunable_hash` -- The hash of the prunable part of the transaction. Will be provided if /// either: the tx has been pruned; or the tx is prunable and either of `prune` or `split` are /// specified. - /// - \p extra -- Parsed "extra" transaction information; omitted unless specifically requested + /// - `extra` -- Parsed "extra" transaction information; omitted unless specifically requested /// (via the `tx_extra` request parameter). This is a dict containing one or more of the /// following keys. - /// - \p pubkey -- The tx extra public key - /// - \p burn_amount -- The amount of BELDEX that this transaction burns, if any. - /// - \p extra_nonce -- Optional extra nonce value (in hex); will be empty if nonce is + /// - `pubkey` -- The tx extra public key + /// - `burn_amount` -- The amount of BELDEX that this transaction burns, if any. + /// - `extra_nonce` -- Optional extra nonce value (in hex); will be empty if nonce is /// recognized as a payment id - /// - \p payment_id -- The payment ID, if present. This is either a 16 hex character (8-byte) + /// - `payment_id` -- The payment ID, if present. This is either a 16 hex character (8-byte) /// encrypted payment id, or a 64 hex character (32-byte) deprecated, unencrypted payment ID - /// - \p mm_depth -- (Merge-mining) the merge-mined depth - /// - \p mm_root -- (Merge-mining) the merge mining merkle root hash - /// - \p additional_pubkeys -- Additional public keys - /// - \p mn_winner -- Master node block reward winner public key - /// - \p mn_pubkey -- Master node public key (e.g. for registrations, stakes, unlocks) - /// - \p mn_contributor -- Master node contributor wallet address (for stakes) - /// - \p tx_secret_key -- The transaction secret key, included in registrations/stakes to + /// - `mm_depth` -- (Merge-mining) the merge-mined depth + /// - `mm_root` -- (Merge-mining) the merge mining merkle root hash + /// - `additional_pubkeys` -- Additional public keys + /// - `mn_winner` -- Master node block reward winner public key + /// - `mn_pubkey` -- Master node public key (e.g. for registrations, stakes, unlocks) + /// - `mn_contributor` -- Master node contributor wallet address (for stakes) + /// - `tx_secret_key` -- The transaction secret key, included in registrations/stakes to /// decrypt transaction amounts and recipients - /// - \p locked_key_images -- Key image(s) locked by the transaction (for registrations, + /// - `locked_key_images` -- Key image(s) locked by the transaction (for registrations, /// stakes) - /// - \p key_image_unlock -- A key image being unlocked in a stake unlock request (an unlock + /// - `key_image_unlock` -- A key image being unlocked in a stake unlock request (an unlock /// will be started for *all* key images locked in the same MN contributions). - /// - \p mn_registration -- Master node registration details; this is a dict containing: - /// - \p fee the operator fee expressed in millionths (i.e. 234567 == 23.4567%) - /// - \p expiry the unix timestamp at which the registration signature expires - /// - \p contributors: dict of (wallet => portion) pairs indicating the staking portions + /// - `mn_registration` -- Master node registration details; this is a dict containing: + /// - `fee` the operator fee expressed in millionths (i.e. 234567 == 23.4567%) + /// - `expiry` the unix timestamp at which the registration signature expires + /// - `contributors`: dict of (wallet => portion) pairs indicating the staking portions /// reserved for the operator and any reserved contribution spots in the registration. /// Portion is expressed in millionths (i.e. 250000 = 25% staking portion). - /// - \p mn_state_change -- Information for a "state change" transaction such as a + /// - `mn_state_change` -- Information for a "state change" transaction such as a /// deregistration, decommission, recommission, or ip change reset transaction. This is a /// dict containing: - /// - \p old_dereg will be set to true if this is an "old" deregistration transaction + /// - `old_dereg` will be set to true if this is an "old" deregistration transaction /// (before the BDX 4 hardfork), omitted for more modern state change txes. - /// - \p type string indicating the state change type: "dereg", "decomm", "recomm", or "ip" + /// - `type` string indicating the state change type: "dereg", "decomm", "recomm", or "ip" /// for a deregistration, decommission, recommission, or ip change penalty transaction. - /// - \p height the voting block height for the changing master node and voting master + /// - `height` the voting block height for the changing master node and voting master /// nodes that produced this state change transaction. - /// - \p index the position of the affected node in the random list of tested nodes for this + /// - `index` the position of the affected node in the random list of tested nodes for this /// `height`. - /// - \p voters the positions of validators in the testing quorum for this `height` who + /// - `voters` the positions of validators in the testing quorum for this `height` who /// tested and voted for this state change. This typically contains the first 7 voters /// who voted for the state change (out of a possible set of 10). - /// - \p reasons list of reported reasons for a decommission or deregistration as reported + /// - `reasons` list of reported reasons for a decommission or deregistration as reported /// by the voting quorum. This contains any reasons that all 7+ voters agreed on, and /// contains one or more of: - /// - \p "uptime" -- the master node was missing uptime proofs - /// - \p "checkpoints" -- the master node missed too many recent checkpoint votes - /// - \p "pos" -- the master node missed too many recent pos votes - /// - \p "storage" -- the master node's storage server was unreachable for too long - /// - \p "belnet" -- the master node's belnet router was unreachable for too long - /// - \p "timecheck" -- the master node's beldexd was not reachable for too many recent + /// - `"uptime"` -- the master node was missing uptime proofs + /// - `"checkpoints"` -- the master node missed too many recent checkpoint votes + /// - `"pos"` -- the master node missed too many recent pos votes + /// - `"storage"` -- the master node's storage server was unreachable for too long + /// - `"belnet"` -- the master node's belnet router was unreachable for too long + /// - `"timecheck"` -- the master node's beldexd was not reachable for too many recent /// time synchronization checks. (This generally means beldexd's quorumnet port is not /// reachable). - /// - \p "timesync" -- the master node's clock was too far out of sync + /// - `"timesync"` -- the master node's clock was too far out of sync /// The list is omitted entirely if there are no reasons at all or if there are no reasons /// that were agreed upon by all voting master nodes. - /// - \p reasons_maybe list of reported reasons that some but not all master nodes provided + /// - `reasons_maybe` list of reported reasons that some but not all master nodes provided /// for the deregistration/decommission. Possible values are identical to the above. /// This list is omitted entirely if it would be empty (i.e. there are no reasons at all, /// or all voting master nodes agreed on all given reasons). - /// - \p bns -- BNS registration or update transaction details. This contains keys: - /// - \p buy -- set to true if this is an BNS buy record; omitted otherwise. - /// - \p update -- set to true if this is an BNS record update; omitted otherwise. - /// - \p renew -- set to true if this is an BNS renewal; omitted otherwise. - /// - \p bchat_value -- the BNS request type string. For registrations: "bchat", + /// - `bns` -- BNS registration or update transaction details. This contains keys: + /// - `buy` -- set to true if this is an BNS buy record; omitted otherwise. + /// - `update` -- set to true if this is an BNS record update; omitted otherwise. + /// - `renew` -- set to true if this is an BNS renewal; omitted otherwise. + /// - `bchat_value` -- the BNS request type string. For registrations: "bchat", /// for a record update: "update". - /// - \p wallet_value -- the BNS request type string. For registrations: "wallet", + /// - `wallet_value` -- the BNS request type string. For registrations: "wallet", /// for a record update: "update". - /// - \p belnet_value -- the BNS request type string. For registrations: "belnet", + /// - `belnet_value` -- the BNS request type string. For registrations: "belnet", /// for a record update: "update". - /// - \p blocks -- The registration length in blocks; omitted for registrations (such as + /// - `blocks` -- The registration length in blocks; omitted for registrations (such as /// Session/Wallets) that do not expire. - /// - \p name_hash -- The hashed name of the record being purchased/updated. Encoded in hex + /// - `name_hash` -- The hashed name of the record being purchased/updated. Encoded in hex /// for json requests. Note that the actual name is not provided on the blockchain. - /// - \p prev_txid -- For an update this field is set to the txid of the previous BNS update + /// - `prev_txid` -- For an update this field is set to the txid of the previous BNS update /// or registration (i.e. the most recent transaction that this record is updating). - /// - \p value -- The encrypted value of the record (in hex for json requests) being - /// set/updated. \see ONS_RESOLVE for details on encryption/decryption. - /// - \p owner -- the owner of this record being set in a registration or update; this can + /// - `value` -- The encrypted value of the record (in hex for json requests) being + /// set/updated. See [`bns_resolve`](#bns_resolve) for details on encryption/decryption. + /// - `owner` -- the owner of this record being set in a registration or update; this can /// be a primary wallet address, wallet subaddress, or a plain public key. - /// - \p backup_owner -- an optional backup owner who also has permission to edit the + /// - `backup_owner` -- an optional backup owner who also has permission to edit the /// record. - /// - \p stake_amount -- Set to the calculated transaction stake amount (only applicable if the + /// - `stake_amount` -- Set to the calculated transaction stake amount (only applicable if the /// transaction is a master node registration or stake). - /// - \p mempool_key_images -- dict of spent key images of mempool transactions. Only included + /// - `mempool_key_images` -- dict of spent key images of mempool transactions. Only included /// when `memory_pool` is set to true. Each key is the key image (in hex, for json requests) /// and each value is a list of transaction hashes that spend that key image (typically just /// one, but in the case of conflicting transactions there can be multiple). @@ -277,26 +296,18 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_transactions", "gettransactions"); } struct request_parameters { - /// List of transaction hashes to look up. (Will also be accepted as json input key - /// "txs_hashes" for backwards compatibility). Exclusive of `memory_pool`. std::vector tx_hashes; - /// If true then return all transactions and spent key images currently in the memory pool. - /// This field is exclusive of `tx_hashes`. bool memory_pool = false; - /// If set to true then parse and return tx-extra information bool tx_extra = false; - /// Controls whether the `data` (or `pruned`, if pruned) field containing raw tx data is - /// included: if explicitly specified then the raw data will be included if true. Otherwise - /// the raw data is included only when neither of `split` nor `prune` are set to true. - bool data = true; - /// If set to true then always split transactions into non-prunable and prunable parts in the - /// response. + bool tx_extra_raw = false; + bool data = false; bool split = false; - /// Like `split`, but also omits the prunable part of transactions from the response details. bool prune = false; } request; }; + /// RPC: daemon/get_transaction_pool + /// /// DEPRECATED. This endpoint is for backwards compatibility for old clients obtaining /// transactions in the transaction pool. The replacement is to use `get_transactions` with /// `"memory_pool": true`. @@ -313,22 +324,24 @@ namespace cryptonote::rpc { static constexpr auto names() { return NAMES("get_transaction_pool"); } }; + /// RPC: blockchain/is_key_image_spent + /// /// Queries whether outputs have been spent using the key image associated with the output. /// /// Inputs: /// - /// - \p key_images list of key images to check. For json requests these must be hex or + /// - `key_images` -- list of key images to check. For json requests these must be hex or /// base64-encoded; for bt-requests they can be hex/base64 or raw bytes. /// /// Outputs /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// - \p spent_status array of status codes returned in the same order as the `key_images` input. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore + /// - `spent_status` -- array of status codes returned in the same order as the `key_images` input. /// Each value is one of: - /// - \p 0 the key image is unspent - /// - \p 1 the key image is spent in a mined block - /// - \p 2 the key image is spent in a transaction currently in the mempool + /// - `0` -- the key image is unspent + /// - `1` -- the key image is spent in a mined block + /// - `2` -- the key image is spent in a transaction currently in the mempool struct IS_KEY_IMAGE_SPENT : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("is_key_image_spent"); } @@ -344,27 +357,29 @@ namespace cryptonote::rpc { } request; }; - //----------------------------------------------- + + /// RPC: blockchain/get_outs + /// /// Retrieve outputs /// /// Inputs: /// - /// - \p outputs Array of output indices. For backwards compatibility these may also be passed as + /// - `outputs` -- Array of output indices. For backwards compatibility these may also be passed as /// an array of {"amount":0,"index":n} dicts. - /// - \p get_txid Request the TXID (i.e. hash) of the transaction as well. - /// - \p as_tuple Requests the returned outs variable as a tuple of values rather than a dict. + /// - `get_txid` -- Request the TXID (i.e. hash) of the transaction as well. + /// - `as_tuple` -- Requests the returned outs variable as a tuple of values rather than a dict. /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// - \p outs List of outkey information; if `as_tuple` is not set then these are dicts containing + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore + /// - `outs` -- List of outkey information; if `as_tuple` is not set then these are dicts containing /// keys: - /// - \p key The public key of the output. - /// - \p mask - /// - \p unlocked States if output is locked (`false`) or not (`true`). - /// - \p height Block height of the output. - /// - \p txid Transaction id; only present if requested via the `get_txid` parameter. + /// - `key` -- The public key of the output. + /// - `mask` -- + /// - `unlocked` -- States if output is locked (`false`) or not (`true`). + /// - `height` -- Block height of the output. + /// - `txid` -- Transaction id; only present if requested via the `get_txid` parameter. /// Otherwise, when `as_tuple` is set, these are 4- or 5-element arrays (depending on whether /// `get_txid` is desired) containing the values in the order listed above. struct GET_OUTPUTS : PUBLIC, LEGACY @@ -382,49 +397,51 @@ namespace cryptonote::rpc { } request; }; - /// Submit a transaction to be broadcast to the network. + /// RPC: blockchain/submit_transaction + /// + /// Submit a transaction to be broadcast to the network. /// /// Inputs: /// - /// - \p tx the full transaction data itself. Can be hex- or base64-encoded for json requests; + /// - `tx` -- the full transaction data itself. Can be hex- or base64-encoded for json requests; /// can also be those or raw bytes for bt-encoded requests. For backwards compatibility, - /// hex-encoded data can also be passed in a json request via the parameter \p tx_as_hex but + /// hex-encoded data can also be passed in a json request via the parameter `tx_as_hex` but /// that is deprecated and will eventually be removed. - /// - \p flash Should be set to true if this transaction is a flash transaction that should be + /// - `flash` -- Should be set to true if this transaction is a flash transaction that should be /// submitted to a flash quorum rather than distributed through the mempool. /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// - \p reason String containing additional information on why a transaction failed. - /// - \p flash_status Set to the result of submitting this transaction to the flash quorum. 1 + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore + /// - `reason` -- String containing additional information on why a transaction failed. + /// - `flash_status` -- Set to the result of submitting this transaction to the flash quorum. 1 /// means the quorum rejected the transaction; 2 means the quorum accepted it; 3 means there was /// a timeout connecting to or waiting for a response from the flash quorum. Note that a /// timeout response does *not* necessarily mean the transaction has not made it to the network. - /// - \p not_relayed will be set to true if some problem with the transactions prevents it from + /// - `not_relayed` -- will be set to true if some problem with the transactions prevents it from /// being relayed to the network, omitted otherwise. - /// - \p reason_codes If the transaction was rejected this will be set to a set of reason string + /// - `reason_codes` -- If the transaction was rejected this will be set to a set of reason string /// codes indicating why the transaction failed: - /// - \c "failed" -- general "bad transaction" code - /// - \c "altchain" -- the transaction is spending outputs that exist on an altchain. - /// - \c "mixin" -- the transaction has the wrong number of decoys - /// - \c "double_spend" -- the transaction is spending outputs that are already spent - /// - \c "invalid_input" -- one or more inputs in the transaction are invalid - /// - \c "invalid_output" -- out or more outputs in the transaction are invalid - /// - \c "too_few_outputs" -- the transaction does not create enough outputs (at least two are + /// - `"failed"` -- general "bad transaction" code + /// - `"altchain"` -- the transaction is spending outputs that exist on an altchain. + /// - `"mixin"` -- the transaction has the wrong number of decoys + /// - `"double_spend"` -- the transaction is spending outputs that are already spent + /// - `"invalid_input"` -- one or more inputs in the transaction are invalid + /// - `"invalid_output"` -- out or more outputs in the transaction are invalid + /// - `"too_few_outputs"` -- the transaction does not create enough outputs (at least two are /// required, currently). - /// - \c "too_big" -- the transaction is too large - /// - \c "overspend" -- the transaction spends (via outputs + fees) more than the inputs - /// - \c "fee_too_low" -- the transaction fee is insufficient - /// - \c "invalid_version" -- the transaction version is invalid (the wallet likely needs an + /// - `"too_big"` -- the transaction is too large + /// - `"overspend"` -- the transaction spends (via outputs + fees) more than the inputs + /// - `"fee_too_low"` -- the transaction fee is insufficient + /// - `"invalid_version"` -- the transaction version is invalid (the wallet likely needs an /// update). - /// - \c "invalid_type" -- the transaction type is invalid - /// - \c "mnode_locked" -- one or more outputs are currently staked to a registred master node + /// - `"invalid_type"` -- the transaction type is invalid + /// - `"mnode_locked"` -- one or more outputs are currently staked to a registred master node /// and thus are not currently spendable on the blockchain. - /// - \c "blacklisted" -- the outputs are currently blacklisted (from being in the 30-day - /// penalty period following a master node deregistration). - /// - \c "flash" -- the flash transaction failed (see `flash_status`) + /// - `"blacklisted"` -- the outputs are currently blacklisted (from being in the 30-day penalty + /// period following a master node deregistration). + /// - `"flash"` -- the flash transaction failed (see `flash_status`) struct SUBMIT_TRANSACTION : PUBLIC, LEGACY { static constexpr auto names() { return NAMES("submit_transaction", "send_raw_transaction", "sendrawtransaction"); } @@ -437,19 +454,20 @@ namespace cryptonote::rpc { }; - //----------------------------------------------- + /// RPC: daemon/start_mining + /// /// Start mining on the daemon /// /// Inputs: /// - /// - \p miner_address Account address to mine to. - /// - \p threads_count Number of mining threads to run. Defaults to 1 thread if omitted or 0. - /// - \p num_blocks Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). - /// - \p slow_mining Do slow mining (i.e. don't allocate RandomX cache); primarily intended for testing. + /// - `miner_address` -- Account address to mine to. + /// - `threads_count` -- Number of mining threads to run. Defaults to 1 thread if omitted or 0. + /// - `num_blocks` -- Mine until the blockchain has this many new blocks, then stop (no limit if 0, the default). + /// - `slow_mining` -- Do slow mining (i.e. don't allocate RandomX cache); primarily intended for testing. /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct START_MINING : LEGACY { static constexpr auto names() { return NAMES("start_mining"); } @@ -462,174 +480,204 @@ namespace cryptonote::rpc { } request; }; - //----------------------------------------------- + /// RPC: daemon/stop_mining + /// /// Stop mining on the daemon. /// /// Inputs: none /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct STOP_MINING : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("stop_mining"); } }; - //----------------------------------------------- + /// RPC: daemon/mining_status + /// /// Get the mining status of the daemon. /// /// Inputs: none /// - /// Output values available from a restricted/admin RPC endpoint: - /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p active States if mining is enabled (`true`) or disabled (`false`). - /// - \p speed Mining power in hashes per seconds. - /// - \p threads_count Number of running mining threads. - /// - \p address Account address daemon is mining to. Empty if not mining. - /// - \p pow_algorithm Current hashing algorithm name - /// - \p block_target The expected time to solve per block, i.e. TARGET_BLOCK_TIME - /// - \p block_reward Block reward for the current block being mined. - /// - \p difficulty The difficulty for the current block being mined. + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `active` -- States if mining is enabled (`true`) or disabled (`false`). + /// - `speed` -- Mining power in hashes per seconds. + /// - `threads_count` -- Number of running mining threads. + /// - `address` -- Account address daemon is mining to. Empty if not mining. + /// - `pow_algorithm` -- Current hashing algorithm name + /// - `block_target` -- The expected time to solve per block, i.e. TARGET_BLOCK_TIME + /// - `block_reward` -- Block reward for the current block being mined. + /// - `difficulty` -- The difficulty for the current block being mined. struct MINING_STATUS : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("mining_status"); } }; + /// RPC: network/get_info + /// /// Retrieve general information about the state of the node and the network. /// /// Inputs: none. /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p height Current length of longest chain known to daemon. - /// - \p target_height The height of the next block in the chain. - /// - \p immutable_height The latest height in the blockchain that can not be reorganized (i.e. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `height` -- Current length of longest chain known to daemon. + /// - `target_height` -- The height of the next block in the chain. + /// - `immutable_height` -- The latest height in the blockchain that can not be reorganized (i.e. /// is backed by at least 2 Master Node, or 1 hardcoded checkpoint, 0 if N/A). Omitted if it /// cannot be determined (typically because the node is still syncing). - /// - \p POS will be true if the next expected block is a POS block, false otherwise. - /// - \p POS_ideal_timestamp For POS blocks this is the ideal timestamp of the next block, + /// - `POS` -- will be true if the next expected block is a POS block, false otherwise. + /// - `POS_ideal_timestamp` -- For POS blocks this is the ideal timestamp of the next block, /// that is, the timestamp if the network was operating with perfect 2-minute blocks since the /// POS hard fork. - /// - \p POS_target_timestamp For POS blocks this is the target timestamp of the next block, + /// - `POS_target_timestamp` -- For POS blocks this is the target timestamp of the next block, /// which targets 2 minutes after the previous block but will be slightly faster/slower if the /// previous block is behind/ahead of the ideal timestamp. - /// - \p difficulty Network mining difficulty; omitted when the network is expecting a POS + /// - `difficulty` -- Network mining difficulty; omitted when the network is expecting a POS /// block. - /// - \p target Current target for next proof of work. - /// - \p tx_count Total number of non-coinbase transaction in the chain. - /// - \p tx_pool_size Number of transactions that have been broadcast but not included in a block. - /// - \p mainnet Indicates whether the node is on the main network (`true`) or not (`false`). - /// - \p testnet Indicates that the node is on the test network (`true`). Will be omitted for + /// - `target` -- Current target for next proof of work. + /// - `tx_count` -- Total number of non-coinbase transaction in the chain. + /// - `tx_pool_size` -- Number of transactions that have been broadcast but not included in a block. + /// - `mainnet` -- Indicates whether the node is on the main network (`true`) or not (`false`). + /// - `testnet` -- Indicates that the node is on the test network (`true`). Will be omitted for /// non-testnet. - /// - \p devnet Indicates that the node is on the dev network (`true`). Will be omitted for + /// - `devnet` -- Indicates that the node is on the dev network (`true`). Will be omitted for /// non-devnet. - /// - \p fakechain States that the node is running in "fakechain" mode (`true`). Omitted + /// - `fakechain` -- States that the node is running in "fakechain" mode (`true`). Omitted /// otherwise. - /// - \p nettype String value of the network type (mainnet, testnet, devnet, or fakechain). - /// - \p top_block_hash Hash of the highest block in the chain. Will be hex for JSON requests, + /// - `nettype` -- String value of the network type (mainnet, testnet, devnet, or fakechain). + /// - `top_block_hash` -- Hash of the highest block in the chain. Will be hex for JSON requests, /// 32-byte binary value for bt requests. - /// - \p immutable_block_hash Hash of the highest block in the chain that can not be reorganized. + /// - `immutable_block_hash` -- Hash of the highest block in the chain that can not be reorganized. /// Hex string for json, bytes for bt. - /// - \p cumulative_difficulty Cumulative difficulty of all blocks in the blockchain. - /// - \p block_size_limit Maximum allowed block size. - /// - \p block_size_median Median block size of latest 100 blocks. - /// - \p bns_counts BNS registration counts. - /// - \p offline Indicates that the node is offline, if true. Omitted for online nodes. - /// - \p untrusted Indicates that the result was obtained using a bootstrap mode, and is therefore - /// - \p database_size Current size of Blockchain data. Over public RPC this is rounded up to the + /// - `cumulative_difficulty` -- Cumulative difficulty of all blocks in the blockchain. + /// - `block_size_limit` -- Maximum allowed block size. + /// - `block_size_median` -- Median block size of latest 100 blocks. + /// - `bns_counts` -- BNS registration counts. + /// - `offline` -- Indicates that the node is offline, if true. Omitted for online nodes. + /// - `untrusted` -- Indicates that the result was obtained using a bootstrap mode, and is therefore + /// - `database_size` -- Current size of Blockchain data. Over public RPC this is rounded up to the /// next-largest GB value. - /// - \p version Current version of this daemon, as a string. For a public node this will just be + /// - `version` -- Current version of this daemon, as a string. For a public node this will just be /// the major and minor version (e.g. "9"); for an admin rpc endpoint this will return the full /// version (e.g. "9.2.1"). - /// - \p status_line A short one-line summary string of the node (requires an admin/unrestricted + /// - `status_line` -- A short one-line summary string of the node (requires an admin/unrestricted /// connection for most details) /// /// If the endpoint is a restricted (i.e. admin) endpoint then the following fields are also /// included: /// - /// - \p alt_blocks_count Number of alternative blocks to main chain. - /// - \p outgoing_connections_count Number of peers that you are connected to and getting + /// - `alt_blocks_count` -- Number of alternative blocks to main chain. + /// - `outgoing_connections_count` -- Number of peers that you are connected to and getting /// information from. - /// - \p incoming_connections_count Number of peers connected to and pulling from your node. - /// - \p white_peerlist_size White Peerlist Size - /// - \p grey_peerlist_size Grey Peerlist Size - /// - \p master_node Will be true if the node is running in --master-node mode. - /// - \p start_time Start time of the daemon, as UNIX time. - /// - \p last_storage_server_ping Last ping time of the storage server (0 if never or not running + /// - `incoming_connections_count` -- Number of peers connected to and pulling from your node. + /// - `white_peerlist_size` -- White Peerlist Size + /// - `grey_peerlist_size` -- Grey Peerlist Size + /// - `master_node` -- Will be true if the node is running in --master-node mode. + /// - `start_time` -- Start time of the daemon, as UNIX time. + /// - `last_storage_server_ping` -- Last ping time of the storage server (0 if never or not running /// as a master node) - /// - \p last_belnet_ping Last ping time of belnet (0 if never or not running as a master node) - /// - \p free_space Available disk space on the node. - /// - \p bootstrap_daemon_address Bootstrap node to give immediate usability to wallets while + /// - `last_belnet_ping` -- Last ping time of belnet (0 if never or not running as a master node) + /// - `free_space` -- Available disk space on the node. + /// - `bootstrap_daemon_address` -- Bootstrap node to give immediate usability to wallets while /// syncing by proxying RPC to it. (Note: the replies may be untrustworthy). - /// - \p height_without_bootstrap Current length of the local chain of the daemon. Only included + /// - `height_without_bootstrap` -- Current length of the local chain of the daemon. Only included /// if a bootstrap daemon is configured. - /// - \p was_bootstrap_ever_used States if the bootstrap node has ever been used since the daemon + /// - `was_bootstrap_ever_used` -- States if the bootstrap node has ever been used since the daemon + /// + /// Example-JSON-Fetch struct GET_INFO : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_info", "getinfo"); } }; - //----------------------------------------------- - /// Retrieve general information about the state of the network. + /// RPC: daemon/get_net_stats + /// + /// Retrieve general information about the network statistics of the daemon. /// /// Inputs: none. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p start_time something. - /// - \p total_packets_in something. - /// - \p total_bytes_in something. - /// - \p total_packets_out something. - /// - \p total_bytes_out something. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `start_time` -- something. + /// - `total_packets_in` -- something. + /// - `total_bytes_in` -- something. + /// - `total_packets_out` -- something. + /// - `total_bytes_out` -- something. struct GET_NET_STATS : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_net_stats"); } }; - //----------------------------------------------- + /// RPC: daemon/save_bc + /// /// Save the blockchain. The blockchain does not need saving and is always saved when modified, /// however it does a sync to flush the filesystem cache onto the disk for safety purposes, /// against Operating System or Hardware crashes. /// /// Inputs: none /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct SAVE_BC : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("save_bc"); } }; - //----------------------------------------------- + /// RPC: blockchain/get_block_count + /// /// Look up how many blocks are in the longest chain known to the node. /// /// Inputs: none /// - /// Output values available from a public RPC endpoint: + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `count` -- Number of blocks in logest chain seen by the node. /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p count Number of blocks in logest chain seen by the node. + /// Example-JSON-Fetch struct GET_BLOCK_COUNT : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_block_count", "getblockcount"); } }; + /// RPC: blockchain/get_block_hash + /// /// Look up one or more blocks' hashes by their height. /// /// Inputs: /// - heights array of block heights of which to look up the block hashes. Accepts at most 1000 /// heights per request. /// - /// Output values are pairs of heights as keys to block hashes as values: - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p height the current blockchain height of this node - /// - \p the block hash of the block with the given height. Note that each height key is + /// Output: + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `height` -- the current blockchain height of this node + /// - `""` -- the block hash of the block with the given height. Note that each height key is /// the stringified integer value, e.g. "3456" rather than 3456. + /// + /// Example input: + /// + /// ```json + /// { "heights": [42, 123456] } + /// ``` + /// + /// Example output: + /// + /// ```json + /// { + /// "status": "OK", + /// "height": 123456, + /// "42": "b269367374fa87ec517405bf120f831e9b13b12c0ee6721dcca69d2c0fe73a0f", + /// "123456": "aa1f3b566aba42e522f8097403a3c513069206286ff08c2ff2871757dbc3e436" + /// } + /// ``` struct GET_BLOCK_HASH : PUBLIC { static constexpr auto names() { return NAMES("get_block_hash", "on_get_block_hash", "on_getblockhash"); } @@ -639,47 +687,85 @@ namespace cryptonote::rpc { } request; // Block hash (string). }; - BELDEX_RPC_DOC_INTROSPECT + // FIXME: This struct should go; it's just a bit of indirection (in _commands_defs.cpp) that isn't + // solve anything (because we can just set the fields directly in the output json values rather + // than use `fill_block_header_response`). struct block_header_response { - uint8_t major_version; // The major version of the beldex protocol at this block height. - uint8_t minor_version; // The minor version of the beldex protocol at this block height. - uint64_t timestamp; // The unix time at which the block was recorded into the blockchain. - std::string prev_hash; // The hash of the block immediately preceding this block in the chain. - uint32_t nonce; // A cryptographic random one-time number used in mining a Beldex block. - bool orphan_status; // Usually `false`. If `true`, this block is not part of the longest chain. - uint64_t height; // The number of blocks preceding this block on the blockchain. - uint64_t depth; // The number of blocks succeeding this block on the blockchain. A larger number means an older block. - std::string hash; // The hash of this block. - difficulty_type difficulty; // The strength of the Beldex network based on mining power. - difficulty_type cumulative_difficulty; // The cumulative strength of the Beldex network based on mining power. - uint64_t reward; // The amount of new BELDEX (in atomic units) generated in this block and allocated to master nodes and governance. As of Bledex 10 (HF) this is the *earned* amount, but not the *paid* amount which occurs in batches. - uint64_t coinbase_payouts; // The amount of BELDEX paid out in this block. As of Beldex 10 (HF 19), this reflects the current batched amounts being paid from earnings batched over previous blocks, not the amounts *earned* in the current block. - uint64_t block_size; // The block size in bytes. - uint64_t block_weight; // The block weight in bytes. - uint64_t num_txes; // Number of transactions in the block, not counting the coinbase tx. - std::optional pow_hash; // The hash of the block's proof of work (requires `fill_pow_hash`) - uint64_t long_term_weight; // Long term weight of the block. - std::string miner_tx_hash; // The TX hash of the miner transaction - std::vector tx_hashes; // The TX hashes of all non-coinbase transactions (requires `get_tx_hashes`) - std::string master_node_winner; // Master node that received a reward for this block - - KV_MAP_SERIALIZABLE + uint8_t major_version; + uint8_t minor_version; + uint64_t timestamp; + std::string prev_hash; + uint32_t nonce; + bool orphan_status; + uint64_t height; + uint64_t depth; + std::string hash; + difficulty_type difficulty; + difficulty_type cumulative_difficulty; + uint64_t reward; + uint64_t coinbase_payouts; + uint64_t block_size; + uint64_t block_weight; + uint64_t num_txes; + std::optional pow_hash; + uint64_t long_term_weight; + std::string miner_tx_hash; + std::vector tx_hashes; + std::string master_node_winner; }; void to_json(nlohmann::json& j, const block_header_response& h); void from_json(const nlohmann::json& j, block_header_response& h); - /// Block header information for the most recent block is easily retrieved with this method. No inputs are needed. - /// - /// Inputs: - /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. - /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// RPC: blockchain/get_last_block_header /// - /// Output values available from a public RPC endpoint: + /// Block header information for the most recent block is easily retrieved with this method. No + /// inputs are needed. /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p block_header A structure containing block header information. + /// Inputs: + /// - `fill_pow_hash` -- Tell the daemon if it should fill out pow_hash field. + /// - `get_tx_hashes` -- If true (default false) then include the hashes of non-coinbase transactions + /// + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `block_header` -- A structure containing block header information. + /// - `major_version` -- The major version of the beldex protocol at this block height. + /// - `minor_version` -- The minor version of the beldex protocol at this block height. + /// - `timestamp` -- The unix time at which the block was recorded into the blockchain. + /// - `prev_hash` -- The hash of the block immediately preceding this block in the chain. + /// - `nonce` -- A cryptographic random one-time number used in mining a Beldex block. + /// - `orphan_status` -- Usually `false`. If `true`, this block is not part of the longest + /// chain. + /// - `height` -- The number of blocks preceding this block on the blockchain. + /// - `depth` -- The number of blocks succeeding this block on the blockchain. A larger number + /// means an older block. + /// - `hash` -- The hash of this block. + /// - `difficulty` -- The strength of the Beldex network based on mining power. + /// - `cumulative_difficulty` -- The cumulative strength of the Beldex network based on mining + /// power. + /// - `reward` -- The amount of new BELDEX (in atomic units) generated in this block and allocated + /// to master nodes and governance. As of BELDEX 10 (HF 19) this is the *earned* amount, but + /// not the *paid* amount which occurs in batches. + /// - `coinbase_payouts` -- The amount of BELDEX paid out in this block. As of Beldex 10 (HF 19), + /// this reflects the current batched amounts being paid from earnings batched over previous + /// blocks, not the amounts *earned* in the current block. + /// - `block_size` -- The block size in bytes. + /// - `block_weight` -- The block weight in bytes. + /// - `num_txes` -- Number of transactions in the block, not counting the coinbase tx. + /// - `pow_hash` -- The hash of the block's proof of work (requires `fill_pow_hash`) + /// - `long_term_weight` -- Long term weight of the block. + /// - `miner_tx_hash` -- The TX hash of the miner transaction + /// - `tx_hashes` -- The TX hashes of all non-coinbase transactions (requires `get_tx_hashes`) + /// - `master_node_winner` -- Master node that received a reward for this block + /// + /// Example input: + /// ```json + /// {} + /// ``` + /// + /// Example-JSON-Fetch struct GET_LAST_BLOCK_HEADER : PUBLIC { static constexpr auto names() { return NAMES("get_last_block_header", "getlastblockheader"); } @@ -691,105 +777,124 @@ namespace cryptonote::rpc { } request; }; - /// Block header information can be retrieved using either a block's hash or height. This method includes a block's hash as an input parameter to retrieve basic information about the block. + /// RPC: blockchain/get_block_header_by_hash + /// + /// Block header information can be retrieved using either a block's hash or height. This method + /// includes a block's hash as an input parameter to retrieve basic information about the block. /// /// Inputs: - /// - \p hash The block's SHA256 hash. - /// - \p hashes Request multiple blocks via an array of hashes - /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. - /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// - `hash` -- The block's SHA256 hash. + /// - `hashes` -- Request multiple blocks via an array of hashes + /// - `fill_pow_hash` -- Tell the daemon if it should fill out pow_hash field. + /// - `get_tx_hashes` -- If true (default false) then include the hashes of non-coinbase transactions /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p block_header Block header information for the requested `hash` block - /// - \p block_headers Block header information for the requested `hashes` blocks + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `block_header` -- Block header information for the requested `hash` block + /// - `block_headers` -- Block header information for the requested `hashes` blocks struct GET_BLOCK_HEADER_BY_HASH : PUBLIC { static constexpr auto names() { return NAMES("get_block_header_by_hash", "getblockheaderbyhash"); } struct request_parameters { - std::string hash; // The block's SHA256 hash. - std::vector hashes; // Request multiple blocks via an array of hashes - bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. - bool get_tx_hashes; // If true (default false) then include the hashes of non-coinbase transactions + std::string hash; + std::vector hashes; + bool fill_pow_hash; + bool get_tx_hashes; } request; }; - /// Similar to get_block_header_by_hash above, this method includes a block's height as an input parameter to retrieve basic information about the block. + /// RPC: blockchain/get_block_header_by_height + /// + /// Similar to get_block_header_by_hash above, this method includes a block's height as an input + /// parameter to retrieve basic information about the block. /// /// Inputs: /// - /// - \p height A block height to look up; returned in `block_header` - /// - \p heights Block heights to retrieve; returned in `block_headers` - /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. - /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// - `height` -- A block height to look up; returned in `block_header` + /// - `heights` -- Block heights to retrieve; returned in `block_headers` + /// - `fill_pow_hash` -- Tell the daemon if it should fill out pow_hash field. + /// - `get_tx_hashes` -- If true (default false) then include the hashes of non-coinbase transactions /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p block_header Block header information for the requested `height` block - /// - \p block_headers Block header information for the requested `heights` blocks + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `block_header` -- Block header information for the requested `height` block + /// - `block_headers` -- Block header information for the requested `heights` blocks struct GET_BLOCK_HEADER_BY_HEIGHT : PUBLIC { static constexpr auto names() { return NAMES("get_block_header_by_height", "getblockheaderbyheight"); } struct request_parameters { - std::optional height; // A block height to look up; returned in `block_header` - std::vector heights; // Block heights to retrieve; returned in `block_headers` - bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. - bool get_tx_hashes; // If true (default false) then include the hashes of non-coinbase transactions + std::optional height; + std::vector heights; + bool fill_pow_hash; + bool get_tx_hashes; } request; }; - /// Full block information can be retrieved by either block height or hash, like with the above block header calls. - /// For full block information, both lookups use the same method, but with different input parameters. + /// RPC: blockchain/get_block + /// + /// Full block information can be retrieved by either block height or hash, like with the above + /// block header calls. For full block information, both lookups use the same method, but with + /// different input parameters. /// /// Inputs: /// - /// - \p hash The block's hash. - /// - \p height A block height to look up; returned in `block_header` - /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. + /// - `hash` -- The block's hash. + /// - `height` -- A block height to look up; returned in `block_header` + /// - `fill_pow_hash` -- Tell the daemon if it should fill out pow_hash field. + /// + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `block_header` -- Block header information for the requested `height` block + /// - `tx_hashes` -- List of hashes of non-coinbase transactions in the block. If there are no other transactions, this will be an empty list. + /// - `blob` -- Hexadecimal blob of block information. + /// - `json` -- JSON formatted block details. /// - /// Output values available from a public RPC endpoint: + /// Example input: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p block_header Block header information for the requested `height` block - /// - \p tx_hashes List of hashes of non-coinbase transactions in the block. If there are no other transactions, this will be an empty list. - /// - \p blob Hexadecimal blob of block information. - /// - \p json JSON formatted block details. + /// ```json + /// { "height": 42 } + /// ``` + /// + /// Example-JSON-Fetch struct GET_BLOCK : PUBLIC { static constexpr auto names() { return NAMES("get_block", "getblock"); } struct request_parameters { - std::string hash; // The block's hash. - uint64_t height; // The block's height. - bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. + std::string hash; + uint64_t height; + bool fill_pow_hash; } request; }; + /// RPC: daemon/get_peer_list + /// /// Get the list of current network peers known to this node. /// /// Inputs: none /// - /// Output values (requires a restricted/admin RPC endpoint): + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p white_list list of "whitelist" peers (see below), that is, peers that were recorded + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `white_list` -- list of "whitelist" peers (see below), that is, peers that were recorded /// reachable the last time this node connected to them. Peers that are unreachable or not /// synchronized with the network are moved to the graylist. - /// - \p gray_list list of peers (see below) that this node knows of but has not (recently) tried + /// - `gray_list` -- list of peers (see below) that this node knows of but has not (recently) tried /// to connect to. /// /// Each peer list is an array of dicts containing the following fields: - /// - \p id a unique integer locally identifying the peer - /// - \p host the peer's IP address (as a string) - /// - \p port the port on which the peer is reachable - /// - \p last_seen unix timestamp when this node last connected to the peer. Will be omitted if + /// - `id` -- a unique integer locally identifying the peer + /// - `host` -- the peer's IP address (as a string) + /// - `port` -- the port on which the peer is reachable + /// - `last_seen` -- unix timestamp when this node last connected to the peer. Will be omitted if /// never connected (e.g. for a peer we received from another node but haven't yet tried). struct GET_PEER_LIST : LEGACY { @@ -802,73 +907,72 @@ namespace cryptonote::rpc { }; + /// RPC: daemon/set_log_level + /// /// Set the daemon log level. By default, log level is set to `0`. For more fine-tuned logging /// control set the set_log_categories command instead. /// /// Inputs: - /// - \p level Daemon log level to set from `0` (less verbose) to `4` (most verbose) + /// - `level` -- Daemon log level to set from `0` (less verbose) to `4` (most verbose) /// - /// Output values (requires a restricted/admin RPC endpoint): + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct SET_LOG_LEVEL : LEGACY { static constexpr auto names() { return NAMES("set_log_level"); } struct request_parameters { - int8_t level; // Daemon log level to set from `0` (less verbose) to `4` (most verbose) + int8_t level; } request; }; - /// Set the daemon log categories. Categories are represented as a comma separated list of - /// `:` (similarly to syslog standard `:`), where: - /// Category is one of the following: * (all facilities), default, net, net.http, net.p2p, - /// logging, net.trottle, blockchain.db, blockchain.db.lmdb, bcutil, checkpoints, net.dns, net.dl, - /// i18n, perf,stacktrace, updates, account, cn ,difficulty, hardfork, miner, blockchain, txpool, - /// cn.block_queue, net.cn, daemon, debugtools.deserialize, debugtools.objectsizes, device.ledger, - /// wallet.gen_multisig, multisig, bulletproofs, ringct, daemon.rpc, wallet.simplewallet, - /// WalletAPI, wallet.ringdb, wallet.wallet2, wallet.rpc, tests.core. + /// RPC: daemon/set_log_categories + /// + /// Set the daemon log categories for debugging purposes. /// - /// Level is one of the following: FATAL - higher level, ERROR, WARNING, INFO, DEBUG, TRACE. - /// Lower level A level automatically includes higher level. By default, categories are set to: - /// `*:WARNING,net:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,stacktrace:INFO,logging:INFO,msgwriter:INFO` - /// Setting the categories to "" prevent any logs to be outputed. + /// Categories are represented as a comma separated list of `:`, where + /// `` is is one of the various internal debugging categories defined in the beldex source + /// code, or `*` to refer to all logging categories. + /// + /// Level is one of the following: FATAL, ERROR, WARNING, INFO, DEBUG, TRACE. /// /// You can append to the current the log level for updating just one or more categories while /// leaving other log levels unchanged by specifying one or more ":" pairs /// preceded by a "+", for example "+difficulty:DEBUG,net:WARNING". /// /// Inputs: - /// - \p categories Optional, daemon log categores to enable + /// - `categories` -- Optional, daemon log categores to enable /// - /// Output values (requires a restricted/admin RPC endpoint): + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p categories Daemon log enabled categories + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `categories` -- Daemon log enabled categories struct SET_LOG_CATEGORIES : LEGACY { static constexpr auto names() { return NAMES("set_log_categories"); } struct request_parameters { - std::string categories; // Optional, daemon log categories to enable + std::string categories; } request; }; - //----------------------------------------------- + /// RPC: blockchain/get_transaction_pool_hashes + /// /// Get hashes from transaction pool. /// /// Inputs: none /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p tx_hashes List of transaction hashes, - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `tx_hashes` -- List of transaction hashes, + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore not struct GET_TRANSACTION_POOL_HASHES : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_hashes"); } @@ -883,44 +987,45 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; - //----------------------------------------------- + /// RPC: blockchain/get_transaction_pool_stats + /// /// Get the transaction pool statistics. /// /// Inputs: none /// - /// Output values available from a public RPC endpoint: - /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p pool_stats Dict of pool statistics: - /// - \p bytes_total the total size (in bytes) of the transactions in the transaction pool. - /// - \p bytes_min the size of the smallest transaction in the tx pool. - /// - \p bytes_max the size of the largest transaction in the pool. - /// - \p bytes_med the median transaction size in the pool. - /// - \p fee_total the total fees of all transactions in the transaction pool. - /// - \p txs_total the total number of transactions in the transaction pool - /// - \p num_failing the number of failing transactions: that is, transactions that are in the + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `pool_stats` -- Dict of pool statistics: + /// - `bytes_total` -- the total size (in bytes) of the transactions in the transaction pool. + /// - `bytes_min` -- the size of the smallest transaction in the tx pool. + /// - `bytes_max` -- the size of the largest transaction in the pool. + /// - `bytes_med` -- the median transaction size in the pool. + /// - `fee_total` -- the total fees of all transactions in the transaction pool. + /// - `txs_total` -- the total number of transactions in the transaction pool + /// - `num_failing` -- the number of failing transactions: that is, transactions that are in the /// mempool but are not currently eligible to be added to the blockchain. - /// - \p num_10m the number of transactions received within the last ten minutes - /// - \p num_not_relayed the number of transactions which are not being relayed to the - /// network. Only included when the \p include_unrelayed request parameter is set to true. - /// - \p num_double_spends the number of transactions in the mempool that are marked as + /// - `num_10m` -- the number of transactions received within the last ten minutes + /// - `num_not_relayed` -- the number of transactions which are not being relayed to the + /// network. Only included when the `include_unrelayed` request parameter is set to true. + /// - `num_double_spends` -- the number of transactions in the mempool that are marked as /// double-spends of existing blockchain transactions. - /// - \p oldest the unix timestamp of the oldest transaction in the pool. - /// - \p histo pairs of [# txes, size of bytes] that form a histogram of transactions in the + /// - `oldest` -- the unix timestamp of the oldest transaction in the pool. + /// - `histo` -- pairs of [# txes, size of bytes] that form a histogram of transactions in the /// mempool, if there are at least two transactions in the mempool (and omitted entirely /// otherwise). When present, this field will contain 10 pairs: /// - When `histo_max` is given then `histo` consists of 10 equally-spaced bins from - /// newest to oldest where the newest bin begins at age 0 and the oldest bin ends at age `\p - /// histo_max`. For example, bin `[3]` contains statistics for transactions with ages + /// newest to oldest where the newest bin begins at age 0 and the oldest bin ends at age + /// `histo_max`. For example, bin `[3]` contains statistics for transactions with ages /// between `3*histo_max/10` and `4*histo_max/10`. /// - Otherwise `histo_98pc` will be present in which case `histo` contains 9 equally spaced /// bins from newest to oldest where the newest bin begins at age 0 and the oldest bin ends /// at age `histo_98pc`, and at least 98% of the mempool transactions will fall in these 9 /// bins. The 10th bin contains statistics for all transactions with ages greater than /// `histo_98pc`. - /// - \p histo_98pc See `histo` for details. - /// - \p histo_max See `histo` for details. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not + /// - `histo_98pc` -- See `histo` for details. + /// - `histo_max` -- See `histo` for details. + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore not struct GET_TRANSACTION_POOL_STATS : PUBLIC, LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_transaction_pool_stats"); } @@ -933,71 +1038,142 @@ namespace cryptonote::rpc { } request; }; + /// RPC: daemon/get_connections + /// /// Retrieve information about incoming and outgoing P2P connections to your node. /// /// Inputs: none /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p connections List of all connections and their info; each element is a dict containing: - /// - \p incoming bool of whether this connection was established by the remote to us (true) or + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `connections` -- List of all connections and their info; each element is a dict containing: + /// - `incoming` -- bool of whether this connection was established by the remote to us (true) or /// by us to the remove (false). - /// - \p ip address of the remote peer - /// - \p port the remote port of the peer connection - /// - \p address_type - 1/2/3/4 for ipv4/ipv6/i2p/tor, respectively. - /// - \p peer_id a string that uniquely identifies a peer node - /// - \p recv_count number of bytes of data received from this peer - /// - \p recv_idle_ms number of milliseconds since we last received data from this peer - /// - \p send_count number of bytes of data send to this peer - /// - \p send_idle_ms number of milliseconds since we last sent data to this peer - /// - \p state returns the current state of the connection with this peer as a string, one of: - /// - \c before_handshake - the connection is still being established/negotiated - /// - \c synchronizing - we are synchronizing the blockchain with this peer - /// - \c standby - the peer is available for synchronizing but we are not currently using it - /// - \c normal - this is a regular, synchronized peer - /// - \p live_ms - number of milliseconds since this connection was initiated - /// - \p avg_download - the average download speed from this peer in bytes per second - /// - \p current_download - the current (i.e. average over a very recent period) download speed + /// - `ip` -- address of the remote peer + /// - `port` -- the remote port of the peer connection + /// - `address_type` -- - 1/2/3/4 for ipv4/ipv6/i2p/tor, respectively. + /// - `peer_id` -- a string that uniquely identifies a peer node + /// - `recv_count` -- number of bytes of data received from this peer + /// - `recv_idle_ms` -- number of milliseconds since we last received data from this peer + /// - `send_count` -- number of bytes of data send to this peer + /// - `send_idle_ms` -- number of milliseconds since we last sent data to this peer + /// - `state` -- returns the current state of the connection with this peer as a string, one of: + /// - `before_handshake` -- the connection is still being established/negotiated + /// - `synchronizing` -- we are synchronizing the blockchain with this peer + /// - `standby` -- the peer is available for synchronizing but we are not currently using it + /// - `normal` -- this is a regular, synchronized peer + /// - `live_ms` -- number of milliseconds since this connection was initiated + /// - `avg_download` -- the average download speed from this peer in bytes per second + /// - `current_download` -- the current (i.e. average over a very recent period) download speed /// from this peer in bytes per second. - /// - \p avg_upload - the average upload speed to this peer in bytes per second - /// - \p current_upload - the current upload speed to this peer in bytes per second - /// - \p connection_id - a unique random string identifying this connection - /// - \p height - the height of the peer - /// - \p host - the hostname for this peer; only included if != \p ip - /// - \p localhost - set to true if the peer is a localhost connection; omitted otherwise. - /// - \p local_ip - set to true if the peer is a non-public, local network connection; omitted + /// - `avg_upload` -- the average upload speed to this peer in bytes per second + /// - `current_upload` -- the current upload speed to this peer in bytes per second + /// - `connection_id` -- a unique random string identifying this connection + /// - `height` -- the height of the peer + /// - `host` -- the hostname for this peer; only included if not the same as `ip` + /// - `localhost` -- set to true if the peer is a localhost connection; omitted otherwise. + /// - `local_ip` -- set to true if the peer is a non-public, local network connection; omitted /// otherwise. + /// + /// Example output: + /// ```json + /// { + /// "connections": [ + /// { + /// "address": "1.2.3.4:51890", + /// "address_type": 1, + /// "avg_download": 0, + /// "avg_upload": 2, + /// "connection_id": "abcdef0123456789abcdef0123456789", + /// "current_download": 0, + /// "current_upload": 0, + /// "height": 1088107, + /// "host": "1.2.3.4", + /// "incoming": true, + /// "ip": "1.2.3.4", + /// "live_time": 33, + /// "local_ip": false, + /// "localhost": false, + /// "peer_id": "fedcba9876543210", + /// "port": "51890", + /// "pruning_seed": 0, + /// "recv_count": 20628, + /// "recv_idle_time": 0, + /// "rpc_port": 0, + /// "send_count": 83253, + /// "send_idle_time": 0, + /// "state": "normal", + /// "support_flags": 1 + /// }, + /// { + /// "address": "5.6.7.8:22022", + /// "address_type": 1, + /// "avg_download": 1, + /// "avg_upload": 1, + /// "connection_id": "00112233445566778899aabbccddeeff", + /// "current_download": 0, + /// "current_upload": 0, + /// "height": 1088107, + /// "host": "5.6.7.8", + /// "incoming": false, + /// "ip": "5.6.7.8", + /// "live_time": 66, + /// "local_ip": false, + /// "localhost": false, + /// "peer_id": "ffddbb9977553311", + /// "port": "22022", + /// "pruning_seed": 0, + /// "recv_count": 95687, + /// "recv_idle_time": 0, + /// "rpc_port": 0, + /// "send_count": 85542, + /// "send_idle_time": 0, + /// "state": "normal", + /// "support_flags": 1 + /// } + /// ], + /// "status": "OK" + /// } + /// ``` struct GET_CONNECTIONS : NO_ARGS { static constexpr auto names() { return NAMES("get_connections"); } }; + /// RPC: blockchain/get_block_headers_range + /// /// Similar to get_block_header_by_height above, but for a range of blocks. /// This method includes a starting block height and an ending block height as /// parameters to retrieve basic information about the range of blocks. /// /// Inputs: /// - /// - \p start_height The starting block's height. - /// - \p end_height The ending block's height. - /// - \p fill_pow_hash Tell the daemon if it should fill out pow_hash field. - /// - \p get_tx_hashes If true (default false) then include the hashes of non-coinbase transactions + /// - `start_height` -- The starting block's height. + /// - `end_height` -- The ending block's height. + /// - `fill_pow_hash` -- Tell the daemon if it should fill out pow_hash field. + /// - `get_tx_hashes` -- If true (default false) then include the hashes of non-coinbase transactions + /// + /// Output: /// - /// Output values available from a public RPC endpoint: + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `headers` -- Array of block_header (a structure containing block header information. See get_last_block_header). + /// Example input: + /// ```json + /// { "start_height": 1087845, "end_height": 1087847, "get_tx_hashes": true } + /// ``` /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p headers Array of block_header (a structure containing block header information. See get_last_block_header). + /// Example-JSON-Fetch struct GET_BLOCK_HEADERS_RANGE : PUBLIC { static constexpr auto names() { return NAMES("get_block_headers_range", "getblockheadersrange"); } struct request_parameters { - uint64_t start_height; // The starting block's height. - uint64_t end_height; // The ending block's height. - bool fill_pow_hash; // Tell the daemon if it should fill out pow_hash field. - bool get_tx_hashes; // If true (default false) then include the hashes or txes in the block details + uint64_t start_height; + uint64_t end_height; + bool fill_pow_hash; + bool get_tx_hashes; } request; }; @@ -1019,45 +1195,52 @@ namespace cryptonote::rpc { // struct response : STATUS {}; // }; - //----------------------------------------------- + /// RPC: daemon/stop_daemon + /// /// Stop the daemon. /// /// Inputs: none /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct STOP_DAEMON : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("stop_daemon"); } }; + /// RPC: daemon/get_limit + /// /// Get daemon p2p bandwidth limits. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Inputs: none + /// + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p limit_up Upload limit in kiB/s - /// - \p limit_down Download limit in kiB/s + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `limit_up` -- Upload limit in kiB/s + /// - `limit_down` -- Download limit in kiB/s struct GET_LIMIT : LEGACY, NO_ARGS { static constexpr auto names() { return NAMES("get_limit"); } }; + /// RPC: daemon/set_limit + /// /// Set daemon p2p bandwidth limits. /// /// Inputs: /// - /// - \p limit_down Download limit in kBytes per second. -1 means reset to default; 0 (or + /// - `limit_down` -- Download limit in kBytes per second. -1 means reset to default; 0 (or /// omitted) means don't change the current limit - /// - \p limit_up Upload limit in kBytes per second. -1 means reset to default; 0 (or omitted) + /// - `limit_up` -- Upload limit in kBytes per second. -1 means reset to default; 0 (or omitted) /// means don't change the current limit /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p limit_up The new (or existing, if unchanged) upload limit in kiB/s - /// - \p limit_down The new (or existing, if unchanged) download limit in kiB/s + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `limit_up` -- The new (or existing, if unchanged) upload limit in kiB/s + /// - `limit_down` -- The new (or existing, if unchanged) download limit in kiB/s struct SET_LIMIT : LEGACY { static constexpr auto names() { return NAMES("set_limit"); } @@ -1068,18 +1251,20 @@ namespace cryptonote::rpc { } request; }; + /// RPC: daemon/out_peers + /// /// Limit number of Outgoing peers. /// /// Inputs: /// - /// - \p set If true, set the number of outgoing peers, otherwise the response returns the current + /// - `set` -- If true, set the number of outgoing peers, otherwise the response returns the current /// limit of outgoing peers. (Defaults to true) - /// - \p out_peers Max number of outgoing peers + /// - `out_peers` -- Max number of outgoing peers /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p out_peers The current limit set for outgoing peers. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `out_peers` -- The current limit set for outgoing peers. struct OUT_PEERS : LEGACY { static constexpr auto names() { return NAMES("out_peers"); } @@ -1091,18 +1276,20 @@ namespace cryptonote::rpc { } request; }; + /// RPC: daemon/in_peers + /// /// Limit number of Incoming peers. /// /// Inputs: /// - /// - \p set If true, set the number of incoming peers, otherwise the response returns the current + /// - `set` -- If true, set the number of incoming peers, otherwise the response returns the current /// limit of incoming peers. (Defaults to true) - /// - \p in_peers Max number of incoming peers + /// - `in_peers` -- Max number of incoming peers /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p in_peers The current limit set for incoming peers. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `in_peers` -- The current limit set for incoming peers. struct IN_PEERS : LEGACY { static constexpr auto names() { return NAMES("in_peers"); } @@ -1114,39 +1301,77 @@ namespace cryptonote::rpc { } request; }; - /// Output values available from a public RPC endpoint: + /// RPC: network/hard_fork_info + /// + /// Retrieves information about the current or a specific hard fork network rules. + /// + /// Inputs: + /// + /// - `version` -- If specified, this is the hard fork (i.e. major block) version for the fork. + /// Only one of `version` and `height` may be given; returns the current hard fork info if + /// neither is given. + /// - `height` -- Request hard fork info by querying a particular height. Only one of `version` + /// and `height` may be given. /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore - /// - \p version The major block version for the fork. - /// - \p enabled Indicates whether the hard fork is enforced on the blockchain (that is, whether + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore + /// - `version` -- The major block version for the fork. + /// - `enabled` -- Indicates whether the hard fork is enforced on the blockchain (that is, whether /// the blockchain height is at or above the requested hardfork). - /// - \p earliest_height Block height at which the hard fork will become enabled. - /// - \p last_height The last block height at which this hard fork will be active; will be omitted + /// - `earliest_height` -- Block height at which the hard fork will become enabled. + /// - `last_height` -- The last block height at which this hard fork will be active; will be omitted /// if this beldexd is not aware of any following hard fork. + /// + /// Example input: + /// + /// ```json + /// { "version": 19 } + /// ``` + /// + /// Example-JSON-Fetch struct HARD_FORK_INFO : PUBLIC { static constexpr auto names() { return NAMES("hard_fork_info"); } struct request_parameters { - /// If specified, this is the hard fork (i.e. major block) version for the fork. Only one of - /// `version` and `height` may be given; returns the current hard fork info if neither is - /// given. uint8_t version = 0; - /// Request hard fork info by querying a particular height. Only one of `version` and - /// `height` may be given. uint64_t height = 0; } request; }; + /// RPC: daemon/get_bans + /// /// Get list of banned IPs. /// /// Inputs: None /// - /// Output values available from a restricted/admin RPC endpoint: - /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p bans List of banned nodes + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `bans` -- List of banned nodes. Each element is a dict containing: + /// - `host` -- Banned host (IP in A.B.C.D form). + /// - `seconds` -- Unix timestamp when the ban expires + /// + /// Example output: + /// ```json + /// { + /// "bans": [ + /// { + /// "host": "1.2.3.4", + /// "ip": 67305985, + /// "seconds": 5504 + /// }, + /// { + /// "host": "8.8.8.8", + /// "ip": 134744072, + /// "seconds": 679104 + /// } + /// ], + /// "status": "OK" + /// } + /// ``` struct GET_BANS : NO_ARGS { static constexpr auto names() { return NAMES("get_bans"); } @@ -1154,46 +1379,75 @@ namespace cryptonote::rpc { struct ban { - std::string host; // Banned host (IP in A.B.C.D form). - uint32_t ip; // Banned IP address, in Int format. - uint32_t seconds; // Local Unix time that IP is banned until + std::string host; + uint32_t seconds; }; - inline void to_json(nlohmann::json& j, const ban& b) { j = nlohmann::json{{"host", b.host}, {"ip", b.ip}, {"seconds", b.seconds} }; }; + inline void to_json(nlohmann::json& j, const ban& b) { j = nlohmann::json{{"host", b.host}, {"seconds", b.seconds} }; }; + /// RPC: daemon/set_bans + /// /// Ban another node by IP. /// /// Inputs: - /// - \p host Banned host (IP in A.B.C.D form). - /// - \p ip Banned IP address, in Int format. - /// - \p seconds Local Unix time that IP is banned until, or Number of seconds to ban node - /// - \p ban Set true to ban. + /// - `host` -- Banned host (IP in A.B.C.D form). + /// - `ip` -- Banned IP address, in Int format. + /// - `seconds` -- Local Unix time that IP is banned until, or Number of seconds to ban node + /// - `ban` -- Set true to ban. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct SET_BANS : RPC_COMMAND { static constexpr auto names() { return NAMES("set_bans"); } struct request_parameters { - std::string host; // Banned host (IP in A.B.C.D form). - uint32_t ip; // Banned IP address, in Int format. - uint32_t seconds; // Local Unix time that IP is banned until, or Number of seconds to ban node - bool ban; // Set true to ban. + std::string host; + uint32_t seconds; + bool ban; } request; }; + /// RPC: daemon/banned + /// /// Determine whether a given IP address is banned /// /// Inputs: - /// - \p address The IP address to check. - /// - /// Output values available from a restricted/admin RPC endpoint: - /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p banned True if the given address is banned, false otherwise. - /// - \p seconds The number of seconds remaining in the ban. + /// - `address` -- The IP address to check. + /// + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `banned` -- True if the given address is banned, false otherwise. + /// - `seconds` -- The number of seconds remaining in the ban. + /// + /// Example input: + /// ```json + /// { "address": "1.2.3.4" } + /// ``` + /// + /// Example output: + /// ```json + /// { + /// "banned": true, + /// "seconds": 5710, + /// "status": "OK" + /// } + /// + /// Example input: + /// ```json + /// { "addess": "4.3.2.1" } + /// ``` + /// + /// Example output: + /// ```json + /// { + /// "banned": false, + /// "seconds": 0, + /// "status": "OK" + /// } + /// ``` struct BANNED : RPC_COMMAND { static constexpr auto names() { return NAMES("banned"); } @@ -1204,14 +1458,16 @@ namespace cryptonote::rpc { } request; }; + /// RPC: daemon/flush_txpool + /// /// Flush tx ids from transaction pool.. /// /// Inputs: - /// - \p txids Optional, list of transactions IDs to flosh from pool (all tx ids flushed if empty) + /// - `txids` -- Optional, list of transactions IDs to flosh from pool (all tx ids flushed if empty) /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct FLUSH_TRANSACTION_POOL : RPC_COMMAND { static constexpr auto names() { return NAMES("flush_txpool"); } @@ -1222,41 +1478,43 @@ namespace cryptonote::rpc { } request; }; + /// RPC: blockchain/get_output_histogram + /// /// Get a histogram of output amounts. For all amounts (possibly filtered by parameters), /// gives the number of outputs on the chain for that amount. RingCT outputs counts as 0 amount. /// /// Inputs: /// - /// - \p amounts list of amounts in Atomic Units. - /// - \p min_count The minimum amounts you are requesting. - /// - \p max_count The maximum amounts you are requesting. - /// - \p unlocked Look for locked only. - /// - \p recent_cutoff + /// - `amounts` -- list of amounts in Atomic Units. + /// - `min_count` -- The minimum amounts you are requesting. + /// - `max_count` -- The maximum amounts you are requesting. + /// - `unlocked` -- Look for locked only. + /// - `recent_cutoff` /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p histogram List of histogram entries. Each element is structured as follows: - /// - \p uint64_t amount Output amount in atomic units. - /// - \p uint64_t total_instances - /// - \p uint64_t unlocked_instances - /// - \p uint64_t recent_instances + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `histogram` -- List of histogram entries. Each element is structured as follows: + /// - `uint64_t` -- amount Output amount in atomic units. + /// - `uint64_t` -- total_instances + /// - `uint64_t` -- unlocked_instances + /// - `uint64_t` -- recent_instances struct GET_OUTPUT_HISTOGRAM : PUBLIC { static constexpr auto names() { return NAMES("get_output_histogram"); } struct request_parameters { - std::vector amounts; // list of amounts in Atomic Units. - uint64_t min_count; // The minimum amounts you are requesting. - uint64_t max_count; // The maximum amounts you are requesting. - bool unlocked; // Look for locked only. + std::vector amounts; + uint64_t min_count; + uint64_t max_count; + bool unlocked; uint64_t recent_cutoff; } request; struct entry { - uint64_t amount; // Output amount in atomic units. + uint64_t amount; uint64_t total_instances; uint64_t unlocked_instances; uint64_t recent_instances; @@ -1265,35 +1523,57 @@ namespace cryptonote::rpc { void to_json(nlohmann::json& j, const GET_OUTPUT_HISTOGRAM::entry& c); void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& c); + /// RPC: daemon/get_version + /// /// Get current RPC protocol version. /// /// Inputs: None /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p version RPC current version. - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `version` -- RPC current version. + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore not struct GET_VERSION : PUBLIC, NO_ARGS { static constexpr auto names() { return NAMES("get_version"); } - - struct request : EMPTY {}; }; - /// Get the coinbase amount and the fees amount for n last blocks starting at particular height. + /// RPC: blockchain/get_coinbase_tx_sum /// - /// Inputs: + /// Get the coinbase amount and the fees amount for n last blocks starting at particular height. /// - /// - \p height Block height from which getting the amounts. - /// - \p count Number of blocks to include in the sum. + /// Note that this call can be extremely slow the first time it is called, particularly when + /// requesting the values for the entire chain (by specifying `height=0`), as it has to scan the + /// full blockchain to calculate the result. As such this call is restricted to admin + /// connections. Future versions may lift this restriction. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Inputs: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p emission_amount Amount of coinbase reward in atomic units. - /// - \p fee_amount Amount of fees in atomic units. - /// - \p burn_amount Amount of burnt beldex. + /// - `height` -- Block height from which getting the amounts. + /// - `count` -- Number of blocks to include in the sum. + /// + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `emission_amount` -- Amount of coinbase reward in atomic units. + /// - `fee_amount` -- Amount of fees in atomic units. + /// - `burn_amount` -- Amount of burnt beldex. + /// + /// Example input: + /// ```json + /// {"height": 0, "count":1000000000} + /// ``` + /// + /// Example output: + /// ```json + /// { + /// "burn_amount": 720279959424405, + /// "emission_amount": 59537648307402880, + /// "fee_amount": 80671056941541, + /// "status": "OK" + /// } + /// ``` struct GET_COINBASE_TX_SUM : RPC_COMMAND { static constexpr auto names() { return NAMES("get_coinbase_tx_sum"); } @@ -1306,25 +1586,36 @@ namespace cryptonote::rpc { }; - /// Gives an estimation of per-output + per-byte fees + /// RPC: network/get_fee_estimate + /// + /// Gives an estimation of per-output + per-byte fees /// /// Inputs: /// - /// - \p grace_blocks Optional. - /// - /// Output values available from a public RPC endpoint: - /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p emission_amount Amount of coinbase reward in atomic units. - /// - \p fee_amount Amount of fees in atomic units. - /// - \p burn_amount Amount of burnt beldex. - /// - \p fee_per_byte Amount of fees estimated per byte in atomic units - /// - \p fee_per_output Amount of fees per output generated by the tx (adds to the `fee_per_byte` per-byte value) - /// - \p flash_fee_per_byte Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. - /// - \p flash_fee_per_output Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. - /// - \p flash_fee_fixed Fixed flash fee in addition to the per-output and per-byte amounts. The portion of the overall flash fee above the overall base fee is burned. - /// - \p quantization_mask - /// - \p untrusted States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// - `grace_blocks` -- If specified, make sure that the fee is high enough to cover any fee + /// increases in the next `grace_blocks` blocks. + /// + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `emission_amount` -- Amount of coinbase reward in atomic units. + /// - `fee_amount` -- Amount of fees in atomic units. + /// - `burn_amount` -- Amount of burnt beldex. + /// - `fee_per_byte` -- Amount of fees estimated per byte in atomic units + /// - `fee_per_output` -- Amount of fees per output generated by the tx (adds to the `fee_per_byte` per-byte value) + /// - `flash_fee_per_byte` -- Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. + /// - `flash_fee_per_output` -- Value for sending a flash. The portion of the overall flash fee above the overall base fee is burned. + /// - `flash_fee_fixed` -- Fixed flash fee in addition to the per-output and per-byte amounts. The portion of the overall flash fee above the overall base fee is burned. + /// - `quantization_mask` + /// - `untrusted` -- States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + /// + /// Example input: + /// + /// ```json + /// {} + /// ``` + /// + /// Example-JSON-Fetch struct GET_BASE_FEE_ESTIMATE : PUBLIC { static constexpr auto names() { return NAMES("get_fee_estimate"); } @@ -1336,30 +1627,32 @@ namespace cryptonote::rpc { }; + /// RPC: blockchain/get_alternative_chains + /// /// Display alternative chains seen by the node. /// /// Inputs: None /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p chains Array of Chains. Each element is contains the following keys: - /// - \p block_hash The block hash of the first diverging block of this alternative chain. - /// - \p height The block height of the first diverging block of this alternative chain. - /// - \p length The length in blocks of this alternative chain, after divergence. - /// - \p difficulty The cumulative difficulty of all blocks in the alternative chain. - /// - \p block_hashes List containing hex block hashes - /// - \p main_chain_parent_block + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `chains` -- Array of Chains. Each element is contains the following keys: + /// - `block_hash` -- The block hash of the first diverging block of this alternative chain. + /// - `height` -- The block height of the first diverging block of this alternative chain. + /// - `length` -- The length in blocks of this alternative chain, after divergence. + /// - `difficulty` -- The cumulative difficulty of all blocks in the alternative chain. + /// - `block_hashes` -- List containing hex block hashes + /// - `main_chain_parent_block` struct GET_ALTERNATE_CHAINS : NO_ARGS { static constexpr auto names() { return NAMES("get_alternative_chains"); } struct chain_info { - std::string block_hash; // The block hash of the first diverging block of this alternative chain. - uint64_t height; // The block height of the first diverging block of this alternative chain. - uint64_t length; // The length in blocks of this alternative chain, after divergence. - uint64_t difficulty; // The cumulative difficulty of all blocks in the alternative chain. + std::string block_hash; + uint64_t height; + uint64_t length; + uint64_t difficulty; std::vector block_hashes; std::string main_chain_parent_block; }; @@ -1367,26 +1660,30 @@ namespace cryptonote::rpc { void to_json(nlohmann::json& j, const GET_ALTERNATE_CHAINS::chain_info& c); void from_json(const nlohmann::json& j, GET_ALTERNATE_CHAINS::chain_info& c); + /// RPC: daemon/relay_tx + /// /// Relay a list of transaction IDs. /// /// Inputs: /// - /// - \p txids List of transactions IDs to relay from pool. + /// - `txids` -- List of transactions IDs to relay from pool. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. struct RELAY_TX : RPC_COMMAND { static constexpr auto names() { return NAMES("relay_tx"); } struct request_parameters { - std::vector txids; // List of transactions IDs to relay from pool. + std::vector txids; } request; }; + /// RPC: daemon/sync_info + /// /// Get node synchronisation information. This returns information on the node's syncing "spans" /// which are block segments being downloaded from peers while syncing; spans are generally /// downloaded out of order from multiple peers, and so these spans reflect in-progress downloaded @@ -1396,24 +1693,24 @@ namespace cryptonote::rpc { /// /// Inputs: none /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p height Current block height - /// - \p target_height If the node is currently syncing then this is the target height the node + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `height` -- Current block height + /// - `target_height` -- If the node is currently syncing then this is the target height the node /// wants to reach. If fully synced then this field is omitted. - /// - \p peers dict of connection information about peers. The key is the peer connection_id; the - /// value is identical to the values of the \p connections field of GET_CONNECTIONS. \sa - /// GET_CONNECTIONS. - /// - \p span array of span information of current in progress synchronization. Element element + /// - `peers` -- dict of connection information about peers. The key is the peer connection_id; the + /// value is identical to the values of the `connections` field of the + /// [`get_connections`](#get_connections) endpoint. + /// - `span` -- array of span information of current in progress synchronization. Element element /// contains: - /// - \p start_block_height Block height of the first block in the span - /// - \p nblocks the number of blocks in the span - /// - \p connection_id the connection_id of the connection from which we are downloading the span - /// - \p rate the most recent connection speed measurement - /// - \p speed the average connection speed over recent downloaded blocks - /// - \p size total number of block and transaction data stored in the span - /// - \p overview a string containing a one-line ascii-art depiction of the current sync status + /// - `start_block_height` -- Block height of the first block in the span + /// - `nblocks` -- the number of blocks in the span + /// - `connection_id` -- the connection_id of the connection from which we are downloading the span + /// - `rate` -- the most recent connection speed measurement + /// - `speed` -- the average connection speed over recent downloaded blocks + /// - `size` -- total number of block and transaction data stored in the span + /// - `overview` -- a string containing a one-line ascii-art depiction of the current sync status struct SYNC_INFO : NO_ARGS { static constexpr auto names() { return NAMES("sync_info"); } @@ -1427,7 +1724,44 @@ namespace cryptonote::rpc { }; - BELDEX_RPC_DOC_INTROSPECT + /// RPC: blockchain/get_output_distribution + /// + /// This endpoint returns the output distribution of the blockchain. Its primary use is for the + /// wallet to obtain the distribution of outputs on the blockchain so as to construct a feasible + /// decoy set. + /// + /// Inputs: + /// + /// - `amounts` -- Amounts to look for in atomic units. + /// - `from_height` -- starting height to check from. Optional (default is 0). + /// - `to_height` -- ending height to check up to; 0 (or omitted) means the current chain height. + /// - `cumulative` -- if true then results are cumulative output counts up to the given block; if + /// false (or omitted) then results are the number of outputs added in the given block. + /// - `binary` -- request the result in binary format (ignored for JSON requests) + /// - `compressed` -- (required `binary`) -- use varint encoding of the binary result + /// + /// Outputs: + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `distributions` -- array of distribution data; each element is an object containing keys: + /// - `amount` -- the requested amount + /// - `start_height` -- the requested start height + /// - `base` -- the number of outputs at the start_height + /// - `binary` -- true if the result is binary. This will always be false for JSON-encoded + /// responses. + /// - `compressed` -- true if the result is binary and using varint encoding. Will always be + /// false for JSON-encoded responses. + /// - `distribution` -- the distribution of rct outputs in blocks beginning after + /// `start_height`. If `binary` and `compressed` are true then this is a binary value + /// consisting of varint-encoded values; if just `binary` is set then this is a raw dump of + /// unsigned 64-bit integer binary data of the values. When `binary` is unset (i.e. for a + /// JSON response) this is an array of integer values. + /// + /// Example input: + /// ```json + /// { "amounts": [0], "from_height": 100002, "to_height": 100008, "cumulative": false } + /// ``` + /// + /// Example-JSON-Fetch struct GET_OUTPUT_DISTRIBUTION : PUBLIC { static constexpr auto names() { return NAMES("get_output_distribution"); } @@ -1465,26 +1799,30 @@ namespace cryptonote::rpc { }; }; + /// RPC: daemon/pop_blocks + /// /// Pop blocks off the main chain /// /// Inputs: /// - /// - \p nblocks Number of blocks in that span. + /// - `nblocks` -- Number of blocks in that span. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p height Height of the blockchain after blocks have been popped. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `height` -- Height of the blockchain after blocks have been popped. struct POP_BLOCKS : LEGACY { static constexpr auto names() { return NAMES("pop_blocks"); } struct request_parameters { - uint64_t nblocks; // Number of blocks in that span. + uint64_t nblocks; } request; }; + /// RPC: daemon/prune_blockchain + /// /// Pruning is the process of removing non-critical blockchain information from local storage. /// Full nodes keep an entire copy of everything that is stored on the blockchain, including data /// that is not very useful anymore. Pruned nodes remove much of this less relevant information @@ -1493,13 +1831,13 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p check Instead of running check if the blockchain has already been pruned. + /// - `check` -- Instead of running check if the blockchain has already been pruned. /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p pruned Bool returning whether the blockchain was pruned or not. - /// - \p pruning_seed The seed that determined how the blockchain was to be pruned. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `pruned` -- Bool returning whether the blockchain was pruned or not. + /// - `pruning_seed` -- The seed that determined how the blockchain was to be pruned. struct PRUNE_BLOCKCHAIN : RPC_COMMAND { static constexpr auto names() { return NAMES("prune_blockchain"); } @@ -1510,25 +1848,34 @@ namespace cryptonote::rpc { } request; }; - + /// RPC: network/get_quorum_state + /// /// Accesses the list of public keys of the nodes who are participating or being tested in a quorum. /// /// Inputs: /// - /// - \p start_height (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. - /// - \p end_height (Optional): End height, omit both start and end height to request the latest quorum - /// - \p quorum_type (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, 255 = all quorums, default is all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. + /// - `start_height` -- (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. + /// - `end_height` -- (Optional): End height, omit both start and end height to request the latest quorum + /// - `quorum_type` -- (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, 255 = all quorums, default is all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. + /// + /// Output: + /// + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `quorums` -- An array of quorums associated with the requested height. Each element is structured with the following keys: + /// - `master_node_pubkey` -- The public key of the Master Node, in hex (json) or binary (bt). + /// - `height` -- The height the quorums are relevant for + /// - `quorum_type` -- The quorum type + /// - `quorum` -- Quorum of Master Nodes. Each element is structured with the following keys: + /// - `validators` -- List of master node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and Flash these are the participating nodes (there are no workers); for POS Flash quorums these are the block signers. This is hex encoded, even for bt-encoded requests. + /// - `workers` -- Public key of the quorum workers. For obligations quorums these are the nodes being tested; for POS quorums this is the block producer. Checkpoint and Flash quorums do not populate this field. This is hex encoded, even for bt-encoded requests. /// - /// Output values available from a public RPC endpoint: + /// Example input: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p quorums An array of quorums associated with the requested height. Each element is structured with the following keys: - /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). - /// - \p height The height the quorums are relevant for - /// - \p quorum_type The quorum type - /// - \p quorum Quorum of Master Nodes. Each element is structured with the following keys: - /// - \p validators List of master node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and Flash these are the participating nodes (there are no workers); for POS Flash quorums these are the block signers. This is hex encoded, even for bt-encoded requests. - /// - \p workers Public key of the quorum workers. For obligations quorums these are the nodes being tested; for POS quorums this is the block producer. Checkpoint and Flash quorums do not populate this field. This is hex encoded, even for bt-encoded requests. + /// ```json + /// { "quorum_type": 3 } + /// ``` + /// + /// Example-JSON-Fetch struct GET_QUORUM_STATE : PUBLIC { static constexpr auto names() { return NAMES("get_quorum_state"); } @@ -1536,47 +1883,49 @@ namespace cryptonote::rpc { static constexpr size_t MAX_COUNT = 256; struct request_parameters { - std::optional start_height; // (Optional): Start height, omit both start and end height to request the latest quorum. Note that "latest" means different heights for different types of quorums as not all quorums exist at every block heights. - std::optional end_height; // (Optional): End height, omit both start and end height to request the latest quorum - std::optional quorum_type; // (Optional): Set value to request a specific quorum, 0 = Obligation, 1 = Checkpointing, 2 = Flash, 3 = POS, omitted or 255 = all quorums. For POS quorums, requesting the blockchain height (or latest) returns the primary POS quorum responsible for the next block; for heights with blocks this returns the actual quorum, which may be a backup quorum if the primary quorum did not produce in time. + std::optional start_height; + std::optional end_height; + std::optional quorum_type; } request; struct quorum_t { - std::vector validators; // List of master node public keys in the quorum. For obligations quorums these are the testing nodes; for checkpoint and flash these are the participating nodes (there are no workers); for POS flash quorums these are the block signers. - std::vector workers; // Public key of the quorum workers. For obligations quorums these are the nodes being tested; for POS quorums this is the block producer. Checkpoint and Flash quorums do not populate this field. + std::vector validators; + std::vector workers; }; struct quorum_for_height { - uint64_t height; // The height the quorums are relevant for - uint8_t quorum_type; // The quorum type - quorum_t quorum; // Quorum of Master Nodes + uint64_t height; + uint8_t quorum_type; + quorum_t quorum; }; }; void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_t& q); void to_json(nlohmann::json& j, const GET_QUORUM_STATE::quorum_for_height& q); - /// Returns the command that should be run to prepare a master node, includes correct parameters - /// and master node ids formatted ready for cut and paste into daemon console. + /// RPC: master_node/get_master_node_registration_cmd_raw + /// + /// Generates a signed master node registration command for use in the operator's Beldex wallet. + /// This endpoint is primarily for internal use by the `beldexd prepare_registration` command. /// /// Inputs: /// - /// - \p args (Developer) The list of arguments used in raw registration, i.e. portions - /// - \p make_friendly Provide information about how to use the command in the result. - /// - \p staking_requirement The staking requirement to become a Master Node the registration command will be generated upon + /// - `args` -- (Developer) The list of arguments used in raw registration, i.e. portions + /// - `make_friendly` -- Provide information about how to use the command in the result. + /// - `staking_requirement` -- The staking requirement to become a Master Node the registration command will be generated upon /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p registration_cmd The command to execute in the wallet CLI to register the queried daemon as a Master Node. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `registration_cmd` -- The command to execute in the wallet CLI to register the queried daemon as a Master Node. struct GET_MASTER_NODE_REGISTRATION_CMD_RAW : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_node_registration_cmd_raw"); } struct request_parameters { - std::vector args; + std::vector args; bool make_friendly; uint64_t staking_requirement; } request; @@ -1596,23 +1945,27 @@ namespace cryptonote::rpc { } request; }; + /// RPC: master_node/get_master_keys + /// /// Get the master public keys of the queried daemon, encoded in hex. All three keys are used /// when running as a master node; when running as a regular node only the x25519 key is regularly /// used for some RPC and and node-to-MN communication requests. /// /// Inputs: None /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p master_node_pubkey The queried daemon's master node public key. Will be empty if not running as a master node. - /// - \p master_node_ed25519_pubkey The daemon's ed25519 auxiliary public key. - /// - \p master_node_x25519_pubkey The daemon's x25519 auxiliary public key. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `master_node_pubkey` -- The queried daemon's master node public key. Will be empty if not running as a master node. + /// - `master_node_ed25519_pubkey` -- The daemon's ed25519 auxiliary public key. + /// - `master_node_x25519_pubkey` -- The daemon's x25519 auxiliary public key. struct GET_MASTER_KEYS : NO_ARGS { static constexpr auto names() { return NAMES("get_master_keys", "get_master_node_key"); } }; + /// RPC: master_node/get_master_privkeys + /// /// Get the master private keys of the queried daemon, encoded in hex. Do not ever share /// these keys: they would allow someone to impersonate your master node. All three keys are used /// when running as a master node; when running as a regular node only the x25519 key is regularly @@ -1620,248 +1973,264 @@ namespace cryptonote::rpc { /// /// Inputs: None /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status General RPC status string. `"OK"` means everything looks good. - /// - \p master_node_privkey The queried daemon's master node private key. Will be empty if not running as a master node. - /// - \p master_node_ed25519_privkey The daemon's ed25519 private key (note that this is in sodium's format, which consists of the private and public keys concatenated together) - /// - \p master_node_x25519_privkey The daemon's x25519 private key. + /// - `status` -- General RPC status string. `"OK"` means everything looks good. + /// - `master_node_privkey` -- The queried daemon's master node private key. Will be empty if not running as a master node. + /// - `master_node_ed25519_privkey` -- The daemon's ed25519 private key (note that this is in sodium's format, which consists of the private and public keys concatenated together) + /// - `master_node_x25519_privkey` -- The daemon's x25519 private key. struct GET_MASTER_PRIVKEYS : NO_ARGS { static constexpr auto names() { return NAMES("get_master_privkeys", "get_master_node_privkey"); } }; + /// RPC: master_node/get_master_nodes + /// /// Get information on some, all, or a random subset of Master Nodes. /// + /// Inputs: + /// - `fields` -- Set of fields to return; listed fields apply to both the top level (such as + /// `"height"` or `"block_hash"`) and to keys inside `master_node_states`. Fields should be + /// provided as a list of field names to include. For backwards compatibility when making a + /// json request field names can also be provided as a dictionary of `{"field_name": true}` + /// pairs, but this usage is deprecated (and not supported for bt-encoded requests). + /// + /// The special field name `"all"` can be used to request all available fields; this is the + /// default when no fields key are provided at all. Be careful when requesting all fields: + /// the response can be very large. + /// + /// When providing a list you may prefix a field name with a `-` (e.g. `"-funded"`) to remove + /// the field from the list; this is mainly useful when following `"all"` to remove some + /// fields from the returned results. (There is no equivalent mode when using the deprecated + /// dict value). + /// + /// - `master_node_pubkeys` -- Array of public keys of registered master nodes to request + /// information about. Omit to query all master nodes. For a JSON request pubkeys must be + /// specified in hex; for a bt-encoded request pubkeys can be hex or bytes. + /// + /// - `active_only` -- If true then only return active master nodes. + /// + /// - `limit` -- If specified and non-zero then only return a random selection of this number of + /// master nodes (in random order) from the result. If negative then no limiting is performed + /// but the returned result is still shuffled. + /// + /// - `poll_block_hash` -- If specified then only return results if the current top block hash is + /// different than the hash given here. This is intended to allow quick polling of results + /// without needing to do anything if the block (and thus SN registrations) have not changed + /// since the last request. + /// + /// Outputs: + /// /// Output variables available are as follows (you can request which parameters are returned; see /// the request parameters description). Note that BDX values are returned in atomic BDX units, /// which are nano-BDX (i.e. 1.000000000 BDX will be returned as 1000000000). /// - /// - \p height the height of the current top block. (Note that this is one less than the - /// "blockchain height" as would be returned by the \c get_info endpoint). - /// - \p target_height the target height of the blockchain; will be greater than height+1 if this + /// - `height` -- the height of the current top block. (Note that this is one less than the + /// "blockchain height" as would be returned by the [`get_info`](#get_info) endpoint). + /// - `target_height` -- the target height of the blockchain; will be greater than height+1 if this /// node is still syncing the chain. - /// - \p block_hash the hash of the most recent block - /// - \p hardfork the current hardfork version of the daemon - /// - \p mnode_revision the current mnode revision for non-hardfork, but mandatory, master node + /// - `block_hash` -- the hash of the most recent block + /// - `hardfork` -- the current hardfork version of the daemon + /// - `mnode_revision` -- the current mnode revision for non-hardfork, but mandatory, master node /// updates. - /// - \p status generic RPC error code; "OK" means the request was successful. - /// - \p unchanged when using poll_block_hash, this value is set to true and results are omitted + /// - `status` -- generic RPC error code; "OK" means the request was successful. + /// - `unchanged` -- when using poll_block_hash, this value is set to true and results are omitted /// if the current block hash has not changed from the requested polling block hash. If block /// hash has changed this is set to false (and results included). When not polling this value /// is omitted entirely. - /// - \p master_node_states list of information about all known master nodes; each element is a + /// - `master_node_states` -- list of information about all known master nodes; each element is a /// dict containing the following keys (which fields are included/omitted can be controlled via /// the "fields" input parameter): - /// - \p master_node_pubkey The public key of the Master Node, in hex (json) or binary (bt). - /// - \p registration_height The height at which the registration for the Master Node arrived + /// - `master_node_pubkey` -- The public key of the Master Node, in hex (json) or binary (bt). + /// - `registration_height` -- The height at which the registration for the Master Node arrived /// on the blockchain. - /// - \p registration_hf_version The current hard fork at which the registration for the Master + /// - `registration_hf_version` -- The current hard fork at which the registration for the Master /// Node arrived on the blockchain. - /// - \p requested_unlock_height If an unlock has been requested for this MN, this field + /// - `requested_unlock_height` -- If an unlock has been requested for this MN, this field /// contains the height at which the Master Node registration expires and contributions will /// be released. - /// - \p last_reward_block_height The height that determines when this master node will next + /// - `last_reward_block_height` -- The height that determines when this master node will next /// receive a reward. This field is somewhat misnamed for historic reasons: it is updated /// when receiving a reward, but is also updated when a MN is activated, recommissioned, or /// has an IP change position reset, and so does not strictly indicate when a reward was /// received. - /// - \p last_reward_transaction_index When multiple Master Nodes register (or become + /// - `last_reward_transaction_index` -- When multiple Master Nodes register (or become /// active/reactivated) at the same height (i.e. have the same last_reward_block_height), this /// field contains the activating transaction position in the block which is used to break /// ties in determining which MN is next in the reward list. - /// - \p active True if fully funded and not currently decommissioned (and so `funded && - /// !active` implicitly defines decommissioned). - /// - \p funded True if the required stakes have been submitted to activate this Master Node. - /// - \p state_height Indicates the height at which the master node entered its current state: - /// - If \p active: this is the height at which the master node last became active (i.e. - /// became fully staked, or was last recommissioned); - /// - If decommissioned (i.e. \p funded but not \p active): the decommissioning height; - /// - If awaiting contributions (i.e. not \p funded): the height at which the last - /// contribution (or registration) was processed. - /// - \p decommission_count The number of times the Master Node has been decommissioned since + /// - `active` -- True if fully funded and not currently decommissioned (and so `funded and not + /// active` implicitly defines decommissioned). + /// - `funded` -- True if the required stakes have been submitted to activate this Master Node. + /// - `state_height` -- Indicates the height at which the master node entered its current state: + /// - If `active` is true: this is the height at which the master node last became active + /// (i.e. became fully staked, or was last recommissioned); + /// - If decommissioned (i.e. `funded and not active`): the decommissioning height; + /// - If awaiting contributions (i.e. `not funded`): the height at which the last contribution + /// (or registration) was processed. + /// - `decommission_count` -- The number of times the Master Node has been decommissioned since /// registration - /// - \p last_decommission_reason_consensus_all The reason for the last decommission as voted by + /// - `last_decommission_reason_consensus_all` -- The reason for the last decommission as voted by /// the testing quorum MNs that decommissioned the node. This is a numeric bitfield made up /// of the sum of given reasons (multiple reasons may be given for a decommission). Values /// are included here if *all* quorum members agreed on the reasons: - /// - \c 0x01 - Missing uptime proofs - /// - \c 0x02 - Missed too many checkpoint votes - /// - \c 0x04 - Missed too many POS blocks - /// - \c 0x08 - Storage server unreachable - /// - \c 0x10 - Beldexd quorumnet unreachable for timesync checks - /// - \c 0x20 - Beldexd system clock is too far off - /// - \c 0x40 - Belnet unreachable - /// - \c 0x50 - Multi_mn_accept_range_not_met + /// - `0x01` - Missing uptime proofs + /// - `0x02` - Missed too many checkpoint votes + /// - `0x04` - Missed too many POS blocks + /// - `0x08` - Storage server unreachable + /// - `0x10` - Beldexd quorumnet unreachable for timesync checks + /// - `0x20` - Beldexd system clock is too far off + /// - `0x40` - Belnet unreachable + /// - `0x50` - Multi_mn_accept_range_not_met /// - other bit values are reserved for future use. - /// - \p last_decommission_reason_consensus_any The reason for the last decommission as voted by - /// *any* MNs. Reasons are set here if *any* quorum member gave a reason, even if not all - /// quorum members agreed. Bit values are the same as \p - /// last_decommission_reason_consensus_all. - /// - \p decomm_reasons - a gentler version of the last_decommission_reason_consensus_all/_any - /// values: this is returned as a dict with two keys, \c "all" and \c "some", containing a - /// list of short string identifiers of the reasons. \c "all" contains reasons that are - /// agreed upon by all voting nodes; \c "some" contains reasons that were provided by some but - /// not all nodes (and is included only if there are at least one such value). Note that, - /// unlike \p last_decommission_reason_consensus_any, the \c "some" field only includes - /// reasons that are *not* included in \c "all". Returned values in the lists are: - /// - \p "uptime" - /// - \p "checkpoints" - /// - \p "POS" - /// - \p "storage" - /// - \p "timecheck" - /// - \p "timesync" - /// - \p "belnet" - /// - \p "multi-mn" + /// - `last_decommission_reason_consensus_any` -- The reason for the last decommission as voted + /// by *any* MNs. Reasons are set here if *any* quorum member gave a reason, even if not all + /// quorum members agreed. Bit values are the same as + /// `last_decommission_reason_consensus_all`. + /// - `decomm_reasons` -- a gentler version of the last_decommission_reason_consensus_all/_any + /// values: this is returned as a dict with two keys, `"all"` and `"some"`, containing a list + /// of short string identifiers of the reasons. `"all"` contains reasons that are agreed upon + /// by all voting nodes; `"some"` contains reasons that were provided by some but not all + /// nodes (and is included only if there are at least one such value). Note that, + /// unlike `last_decommission_reason_consensus_any`, the `"some"` field only includes reasons + /// reasons that are *not* included in `"all"`. Returned values in the lists are: + /// - `"uptime"` + /// - `"checkpoints"` + /// - `"POS"` + /// - `"storage"` + /// - `"timecheck"` + /// - `"timesync"` + /// - `"belnet"` + /// - `"multi-mn"` /// - other values are reserved for future use. - /// - \p earned_downtime_blocks The number of blocks earned towards decommissioning (if + /// - `earned_downtime_blocks` -- The number of blocks earned towards decommissioning (if /// currently active), or the number of blocks remaining until the master node is eligible /// for deregistration (if currently decommissioned). - /// - \p master_node_version The three-element numeric version of the Master Node (as received + /// - `master_node_version` -- The three-element numeric version of the Master Node (as received /// in the last uptime proof). Omitted if we have never received a proof. - /// - \p belnet_version The major, minor, patch version of the Master Node's belnet router + /// - `belnet_version` -- The major, minor, patch version of the Master Node's belnet router /// (as received in the last uptime proof). Omitted if we have never received a proof. - /// - \p storage_server_version The major, minor, patch version of the Master Node's storage + /// - `storage_server_version` -- The major, minor, patch version of the Master Node's storage /// server (as received in the last uptime proof). Omitted if we have never received a proof. - /// - \p contributors Array of contributors, contributing to this Master Node. Each element is + /// - `contributors` -- Array of contributors, contributing to this Master Node. Each element is /// a dict containing: - /// - \p amount The total amount of BDX staked by this contributor into + /// - `amount` -- The total amount of BDX staked by this contributor into /// this Master Node. - /// - \p reserved The amount of BDX reserved by this contributor for this Master Node; this + /// - `reserved` -- The amount of BDX reserved by this contributor for this Master Node; this /// field will be included only if the contributor has unfilled, reserved space in the /// master node. - /// - \p address The wallet address of this contributor to which rewards are sent and from + /// - `address` -- The wallet address of this contributor to which rewards are sent and from /// which contributions were made. - /// - \p locked_contributions Array of contributions from this contributor; this field (unlike - /// the other fields inside \p contributors) is controlled by the "fields" input parameter. + /// - `locked_contributions` -- Array of contributions from this contributor; this field + /// (unlike the other fields inside `contributors`) is controlled by the "fields" input parameter. /// Each element contains: - /// - \p key_image The contribution's key image which is locked on the network. - /// - \p key_image_pub_key The contribution's key image, public key component. - /// - \p amount The amount of BDX that is locked in this contribution. - /// - /// - \p total_contributed The total amount of BDX contributed to this Master Node. - /// - \p total_reserved The total amount of BDX contributed or reserved for this Master Node. + /// - `key_image` -- The contribution's key image which is locked on the network. + /// - `key_image_pub_key` -- The contribution's key image, public key component. + /// - `amount` -- The amount of BDX that is locked in this contribution. + /// - `total_contributed` -- The total amount of BDX contributed to this Master Node. + /// - `total_reserved` -- The total amount of BDX contributed or reserved for this Master Node. /// Only included in the response if there are still unfilled reservations (i.e. if it is /// greater than total_contributed). - /// - \p staking_requirement The total BDX staking requirement in that is/was required to be + /// - `staking_requirement` -- The total BDX staking requirement in that is/was required to be /// contributed for this Master Node. - /// - \p portions_for_operator The operator fee to take from the master node reward, as a + /// - `portions_for_operator` -- The operator fee to take from the master node reward, as a /// fraction of 18446744073709551612 (2^64 - 4) (that is, this number corresponds to 100%). /// Note that some JSON parsers may silently change this value while parsing as typical values /// do not fit into a double without loss of precision. - /// - \p operator_fee The operator fee expressed as thousandths of a percent (and rounded to the + /// - `operator_fee` -- The operator fee expressed as thousandths of a percent (and rounded to the /// nearest integer value). That is, 100000 corresponds to a 100% fee, 5456 corresponds to a /// 5.456% fee. Note that this number is for human consumption; the actual value that matters - /// for the blockchain is the precise \p portions_for_operator value. - /// - \p swarm_id The numeric identifier of the Master Node's current swarm. Note that + /// for the blockchain is the precise `portions_for_operator` value. + /// - `swarm_id` -- The numeric identifier of the Master Node's current swarm. Note that /// returned values can exceed the precision available in a double value, which can result in - /// (changed) incorrect values by some JSON parsers. Consider using \p swarm instead if you + /// (changed) incorrect values by some JSON parsers. Consider using `swarm` instead if you /// are not sure your JSON parser supports 64-bit values. - /// - \p swarm The swarm id, expressed in hexadecimal, such as \c "f4ffffffffffffff". - /// - \p operator_address The wallet address of the Master Node operator. - /// - \p public_ip The public ip address of the master node; omitted if we have not yet + /// - `swarm` -- The swarm id, expressed in hexadecimal, such as `"f4ffffffffffffff"`. + /// - `operator_address` -- The wallet address of the Master Node operator. + /// - `public_ip` -- The public ip address of the master node; omitted if we have not yet /// received a network proof containing this information from the master node. - /// - \p storage_port The port number associated with the storage server; omitted if we have no + /// - `storage_port` -- The port number associated with the storage server; omitted if we have no /// uptime proof yet. - /// - \p storage_lmq_port The port number associated with the storage server (oxenmq interface); + /// - `storage_lmq_port` -- The port number associated with the storage server (oxenmq interface); /// omitted if we have no uptime proof yet. - /// - \p quorumnet_port The port for direct MN-to-MN beldexd communication (oxenmq interface). + /// - `quorumnet_port` -- The port for direct MN-to-MN beldexd communication (oxenmq interface). /// Omitted if we have no uptime proof yet. - /// - \p pubkey_ed25519 The master node's ed25519 public key for auxiliary services. Omitted if + /// - `pubkey_ed25519` -- The master node's ed25519 public key for auxiliary services. Omitted if /// we have no uptime proof yet. Note that for newer registrations this will be the same as - /// the \p master_node_pubkey. - /// - \p pubkey_x25519 The master node's x25519 public key for auxiliary services (mainly used - /// for \p quorumnet_port and the \p storage_lmq_port OxenMQ encrypted connections). - /// - \p last_uptime_proof The last time we received an uptime proof for this master node from + /// the `master_node_pubkey`. + /// - `pubkey_x25519` -- The master node's x25519 public key for auxiliary services (mainly + /// used for `quorumnet_port` and the `storage_lmq_port` OxenMQ encrypted connections). + /// - `last_uptime_proof` -- The last time we received an uptime proof for this master node from /// the network, in unix epoch time. 0 if we have never received one. - /// - \p storage_server_reachable True if this storage server is currently passing tests for the + /// - `storage_server_reachable` -- True if this storage server is currently passing tests for the /// purposes of MN node testing: true if the last test passed, or if it has been unreachable /// for less than an hour; false if it has been failing tests for more than an hour (and thus /// is considered unreachable). This field is omitted if the queried beldexd is not a master /// node. - /// - \p storage_server_first_unreachable If the last test we received was a failure, this field + /// - `storage_server_first_unreachable` -- If the last test we received was a failure, this field /// contains the timestamp when failures started. Will be 0 if the last result was a success, /// and will be omitted if the node has not yet been tested since this beldexd last restarted. - /// - \p storage_server_last_unreachable The last time this master node's storage server failed + /// - `storage_server_last_unreachable` -- The last time this master node's storage server failed /// a ping test (regardless of whether or not it is currently failing). Will be omitted if it /// has never failed a test since startup. - /// - \p storage_server_last_reachable The last time we received a successful ping response for + /// - `storage_server_last_reachable` -- The last time we received a successful ping response for /// this storage server (whether or not it is currently failing). Will be omitted if we have /// never received a successful ping response since startup. - /// - \p belnet_reachable Same as \p storage_server_reachable, but for belnet router testing. - /// - \p belnet_first_unreachable Same as \p storage_server_first_unreachable, but for belnet + /// - `belnet_reachable` -- Same as `storage_server_reachable`, but for belnet router testing. + /// - `belnet_first_unreachable` -- Same as `storage_server_first_unreachable`, but for belnet /// router testing. - /// - \p belnet_last_unreachable Same as \p storage_server_last_unreachable, but for belnet + /// - `belnet_last_unreachable` -- Same as `storage_server_last_unreachable`, but for belnet /// router testing. - /// - \p belnet_last_reachable Same as \p storage_server_last_reachable, but for belnet router + /// - `belnet_last_reachable` -- Same as `storage_server_last_reachable`, but for belnet router /// testing. - /// - \p checkpoint_votes dict containing recent received checkpoint voting information for this + /// - `checkpoint_votes` -- dict containing recent received checkpoint voting information for this /// master node. Master node tests will fail if too many recent POS blocks are missed. /// Contains keys: - /// - \p voted list of blocks heights at which a required vote was received from this + /// - `voted` -- list of blocks heights at which a required vote was received from this /// master node - /// - \p missed list of block heights at which a vote from this master node was required + /// - `missed` -- list of block heights at which a vote from this master node was required /// but not received. - /// - \p POS_votes dict containing recent POS blocks in which this master node was supposed + /// - `POS_votes` -- dict containing recent POS blocks in which this master node was supposed /// to have participated. Master node testing will fail if too many recent POS blocks are /// missed. Contains keys: - /// - \p voted list of [HEIGHT,ROUND] pairs in which an expected POS participation was - /// recorded for this node. ROUND starts at 0 and increments for backup POS quorums if a - /// previous round does not broadcast a POS block for the given height in time. - /// - \p missed list of [HEIGHT,ROUND] pairs in which POS participation by this master node + /// - `voted` -- list of [,] pairs in which an expected POS participation + /// was recorded for this node. starts at 0 and increments for backup POS + /// quorums if a previous round does not broadcast a POS block for the given height in time. + /// - `missed` -- list of [,] pairs in which POS participation by this master node /// was expected but did not occur. - /// - \p quorumnet_tests array containing the results of recent attempts to connect to the + /// - `quorumnet_tests` -- array containing the results of recent attempts to connect to the /// remote node's quorumnet port (while conducting timesync checks). The array contains two - /// values: [SUCCESSES,FAILURES], where SUCCESSES is the number of recent successful - /// connections and FAILURES is the number of recent connection and/or request timeouts. If - /// there are two many failures then the master node will fail testing. - /// - \p timesync_tests array containing the results of recent time synchronization checks of - /// this master node. Contains [SUCCESSES,FAILURES] counts where SUCCESSES is the number of - /// recent checks where the system clock was relatively close and FAILURES is the number of - /// recent checks where we received a significantly out-of-sync timestamp response from the - /// master node. A master node fails tests if there are too many recent out-of-sync - /// responses. + /// values: [,], where is the number of recent + /// successful connections and FAILURES is the number of recent connection and/or request + /// timeouts. If there are two many failures then the master node will fail testing. + /// - `timesync_tests` -- array containing the results of recent time synchronization checks of + /// this master node. Contains [,] counts where is the + /// number of recent checks where the system clock was relatively close and is the + /// number of recent checks where we received a significantly out-of-sync timestamp response + /// from the master node. A master node fails tests if there are too many recent + /// out-of-sync responses. + /// + /// Example input: + /// ```json + /// { "limit": 1 } + /// ``` + /// + /// Example-JSON-Fetch struct GET_MASTER_NODES : PUBLIC { static constexpr auto names() { return NAMES("get_master_nodes", "get_n_master_nodes", "get_all_master_nodes"); } struct request_parameters { - /// Set of fields to return; listed fields apply to both the top level (such as \p "height" or - /// - \p "block_hash") and to keys inside \p master_node_states. Fields should be provided as - /// a list of field names to include. For backwards compatibility when making a json request - /// field names can also be provided as a dictionary of {"field_name": true} pairs, but this - /// usage is deprecated (and not supported for bt-encoded requests). - /// - /// The special field name "all" can be used to request all available fields; this is the - /// default when no fields key are provided at all. Be careful when requesting all fields: - /// the response can be very large. - /// - /// When providing a list you may prefix a field name with a \c - to remove the field from the - /// list; this is mainly useful when following "all" to remove some fields from the returned - /// results. (There is no equivalent mode when using the deprecated dict value). std::unordered_set fields; - - /// Array of public keys of registered master nodes to request information about. Omit to - /// query all master nodes. For a JSON request pubkeys must be specified in hex; for a - /// bt-encoded request pubkeys can be hex or bytes. std::vector master_node_pubkeys; - - /// If true then only return active master nodes. bool active_only = false; - - /// If specified and non-zero then only return a random selection of this number of master - /// nodes (in random order) from the result. If negative then no limiting is performed but - /// the returned result is still shuffled. int limit = 0; - - /// If specified then only return results if the current top block hash is different than the - /// hash given here. This is intended to allow quick polling of results without needing to do - /// anything if the block (and thus MN registrations) have not changed since the last request. crypto::hash poll_block_hash = crypto::hash::null(); } request; }; + /// RPC: master_node/get_master_node_status + /// /// Retrieves information on the current daemon's Master Node state. The returned information is /// the same as what would be returned by "get_master_nodes" when passed this master node's /// public key. @@ -1869,21 +2238,23 @@ namespace cryptonote::rpc { /// Inputs: none. /// /// Outputs: - /// - \p master_node_state - if this is a registered master node then all available fields for - /// this master node. \sa GET_MASTER_NODES for the list of fields. Note that some fields - /// (such as remote testing results) will not be available (through this call or \p - /// "get_master_nodes") because a master node is incapable of testing itself for remote - /// connectivity. If this daemon is running in master node mode but not registered then only - /// MN pubkey, ip, and port fields are returned. - /// - \p height current top block height at the time of the request (note that this is generally + /// - `master_node_state` -- if this is a registered master node then all available fields for + /// this master node. See [`get_master_nodes`](#get_master_nodes) for the list of fields. + /// Note that some fields (such as remote testing results) will not be available (through this + /// call or [`get_master_nodes`](#get_master_nodes)) because a master node is incapable of + /// testing itself for remote connectivity. If this daemon is running in master node mode but + /// not registered then only MN pubkey, ip, and port fields are returned. + /// - `height` -- current top block height at the time of the request (note that this is generally /// one less than the "blockchain height"). - /// - \p block_hash current top block hash at the time of the request - /// - \p status generic RPC error code; "OK" means the request was successful. + /// - `block_hash` -- current top block hash at the time of the request + /// - `status` -- generic RPC error code; "OK" means the request was successful. struct GET_MASTER_NODE_STATUS : NO_ARGS { static constexpr auto names() { return NAMES("get_master_node_status"); } }; + /// Dev-RPC: master_node/storage_server_ping + /// /// Endpoint to receive an uptime ping from the connected storage server. This is used /// to record whether the storage server is ready before the master node starts /// sending uptime proofs. This is usually called internally from the storage server @@ -1891,28 +2262,34 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p version Storage server version - /// - \p https_port Storage server https port to include in uptime proofs. - /// - \p omq_port Storage server oxenmq port to include in uptime proofs. - /// - \p pubkey_ed25519 Master node Ed25519 pubkey for verifying that storage server is using the right one + /// - `version` -- Storage server version + /// - `https_port` -- Storage server https port to include in uptime proofs. + /// - `omq_port` -- Storage server oxenmq port to include in uptime proofs. + /// - `pubkey_ed25519` -- Master node Ed25519 pubkey for verifying that storage server is running + /// with the correct master node keys. + /// - `error` -- If given and non-empty then this is an error message telling beldexd to *not* + /// submit an uptime proof and to report this error in the logs instead. Beldexd won't send + /// proofs until it gets another ping (without an error). /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status generic RPC error code; "OK" means the request was successful. + /// - `status` -- generic RPC error code; "OK" means the request was successful. struct STORAGE_SERVER_PING : RPC_COMMAND { static constexpr auto names() { return NAMES("storage_server_ping"); } struct request_parameters { - std::array version; // Storage server version - uint16_t https_port; // Storage server https port to include in uptime proofs - uint16_t omq_port; // Storage Server oxenmq port to include in uptime proofs - std::string pubkey_ed25519; // Master node Ed25519 pubkey for verifying that storage server is using the right one - std::string error; // If given and non-empty then this is an error message telling beldexd to *not* submit an uptime proof and to report this error in the logs instead. Beldexd won't send proofs until it gets another ping (without an error). + std::array version; + uint16_t https_port; + uint16_t omq_port; + std::string pubkey_ed25519; + std::string error; } request; }; + /// Dev-RPC: master_node/belnet_ping + /// /// Endpoint to receive an uptime ping from the connected belnet server. This is used /// to record whether belnet is ready before the master node starts sending uptime proofs. /// This is usually called internally from belnet and this endpoint is mostly @@ -1920,77 +2297,94 @@ namespace cryptonote::rpc { /// /// Inputs: /// - /// - \p version Belnet version - /// - \p pubkey_ed25519 // Master node Ed25519 pubkey for verifying that belnet is using the right one + /// - `version` -- Belnet version + /// - `pubkey_ed25519` -- Master node Ed25519 pubkey for verifying that belnet is running with + /// the correct master node keys. + /// - `error` -- If given and non-empty then this is an error message telling beldexd to *not* + /// submit an uptime proof and to report this error in the logs instead. Beldexd won't send + /// proofs until it gets another ping (without an error). /// - /// Output values available from a restricted/admin RPC endpoint: + /// Output: /// - /// - \p status generic RPC error code; "OK" means the request was successful. + /// - `status` -- generic RPC error code; "OK" means the request was successful. struct BELNET_PING : RPC_COMMAND { static constexpr auto names() { return NAMES("belnet_ping"); } struct request_parameters { - std::array version; // Belnet version - std::string pubkey_ed25519; // Master node Ed25519 pubkey for verifying that belnet is using the right one - std::string error; // If given and non-empty then this is an error message telling oxend to *not* submit an uptime proof and to report this error in the logs instead. Beldexd won't send proofs until it gets another ping (without an error). + std::array version; + std::string pubkey_ed25519; + std::string error; } request; }; + /// RPC: master_node/get_staking_requirement + /// /// Get the required amount of BDX to become a Master Node at the queried height. /// For devnet and testnet values, ensure the daemon is started with the /// `--devnet` or `--testnet` flags respectively. /// /// Inputs: /// - /// - \p height The height to query the staking requirement for. 0 (or omitting) means current height. + /// - `height` -- The height to query the staking requirement for. 0 (or omitting) means current height. /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status generic RPC error code; "OK" means the request was successful. - /// - \p staking_requirement The staking requirement in Oxen, in atomic units. - /// - \p height The height requested (or current height if 0 was requested) + /// - `status` -- generic RPC error code; "OK" means the request was successful. + /// - `staking_requirement` -- The staking requirement in Beldex, in atomic units. + /// - `height` -- The height requested (or current height if 0 was requested) struct GET_STAKING_REQUIREMENT : PUBLIC { static constexpr auto names() { return NAMES("get_staking_requirement"); } struct request_parameters { - uint64_t height; // The height to query the staking requirement for. 0 (or omitting) means current height. + uint64_t height; } request; }; + /// RPC: blockchain/get_master_node_blacklisted_key_images + /// /// Get information on blacklisted Master Node key images. /// /// Inputs: None /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status generic RPC error code; "OK" means the request was successful. - /// - \p blacklist Array of blacklisted key images, i.e. unspendable transactions. Each entry contains - /// - \p key_image The key image of the transaction that is blacklisted on the network. - /// - \p unlock_height The height at which the key image is removed from the blacklist and becomes spendable. - /// - \p amount The total amount of locked Beldex in atomic units in this blacklisted stake. - struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : PUBLIC, NO_ARGS + /// - `status` -- generic RPC error code; "OK" means the request was successful. + /// - `blacklist` -- Array of blacklisted key images, i.e. unspendable transactions. Each entry contains + /// - `key_image` -- The key image of the transaction that is blacklisted on the network. + /// - `unlock_height` -- The height at which the key image is removed from the blacklist and becomes spendable. + /// - `amount` -- The total amount of locked Beldex in atomic units in this blacklisted stake. + struct GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES : NO_ARGS { static constexpr auto names() { return NAMES("get_master_node_blacklisted_key_images"); } }; + /// RPC: blockchain/get_checkpoints + /// /// Query hardcoded/master node checkpoints stored for the blockchain. Omit all arguments to retrieve the latest "count" checkpoints. /// /// Inputs: /// - /// - \p start_height Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. - /// - \p end_height Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. - /// - \p count Optional: Number of checkpoints to query. + /// - `start_height` -- Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. + /// - `end_height` -- Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. + /// - `count` -- Optional: Number of checkpoints to query. + /// + /// Output: + /// + /// - `status` -- generic RPC error code; "OK" means the request was successful. + /// - `checkpoints` -- Array of requested checkpoints /// - /// Output values available from a public RPC endpoint: + /// Example input: + /// ```json + /// { "count": 2 } + /// ``` /// - /// - \p status generic RPC error code; "OK" means the request was successful. - /// - \p checkpoints Array of requested checkpoints + /// Example-JSON-Fetch struct GET_CHECKPOINTS : PUBLIC { static constexpr auto names() { return NAMES("get_checkpoints"); } @@ -1999,31 +2393,39 @@ namespace cryptonote::rpc { static constexpr uint32_t NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT = 60; struct request_parameters { - std::optional start_height; // Optional: Get the first count checkpoints starting from this height. Specify both start and end to get the checkpoints inbetween. - std::optional end_height; // Optional: Get the first count checkpoints before end height. Specify both start and end to get the checkpoints inbetween. - std::optional count; // Optional: Number of checkpoints to query. + std::optional start_height; + std::optional end_height; + std::optional count; } request; }; - /// Query recent master node state changes processed on the blockchain. + /// RPC: blockchain/get_master_nodes_state_changes + /// + /// Query the number of master node state change transactions contained in a range of blocks. /// /// Inputs: /// - /// - \p start_height Returns counts starting from this block height. Required. - /// - \p end_height Optional: returns count ending at this block height; if omitted, counts to the - /// current height. - /// - /// Output values available from a private/admin RPC endpoint: - /// - /// - \p status Generic RPC error code. "OK" is the success value. - /// - \p untrusted If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. - /// - \p total_deregister - /// - \p total_ip_change_penalty - /// - \p total_decommission - /// - \p total_recommission - /// - \p total_unlock - /// - \p start_height - /// - \p end_height + /// - `start_height` -- The starting block's height. + /// - `end_height` -- The ending block's height. + /// + /// Output: + /// + /// - `status` -- Generic RPC error code. "OK" is the success value. + /// - `untrusted` -- If the result is obtained using bootstrap mode then this will be set to true, otherwise will be omitted. + /// - `total_deregister` -- the total number of master node deregistrations + /// - `total_ip_change_penalty` -- the total number of IP change penalties + /// - `total_decommission` -- the total number of master node decommissions + /// - `total_recommission` -- the total number of master node recommissions + /// - `total_unlock` -- the total number of master node unlock requests + /// - `start_height` -- the start height of the given statistics + /// - `end_height` -- the end height of the given statistics + /// + /// Example input: + /// ```json + /// { "start_height": 1085000, "end_height": 1085191 } + /// ``` + /// + /// Example-JSON-Fetch struct GET_MN_STATE_CHANGES : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_nodes_state_changes"); } @@ -2035,18 +2437,19 @@ namespace cryptonote::rpc { } request; }; - + /// Dev-RPC: master_node/report_peer_status + /// /// Reports master node peer status (success/fail) from belnet and storage server. /// /// Inputs: /// - /// - \p type test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. - /// - \p pubkey master node pubkey - /// - \p passed whether node is passing the test + /// - `type` -- test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. + /// - `pubkey` -- master node pubkey + /// - `passed` -- whether node is passing the test /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status Generic RPC error code. "OK" is the success value. + /// - `status` -- Generic RPC error code. "OK" is the success value. struct REPORT_PEER_STATUS : RPC_COMMAND { // TODO: remove the `report_peer_storage_server_status` once we require a storage server version @@ -2055,33 +2458,37 @@ namespace cryptonote::rpc { struct request_parameters { - std::string type; // test type; currently supported are: "storage" and "belnet" for storage server and belnet tests, respectively. - std::string pubkey; // master node pubkey - bool passed; // whether the node is passing the test + std::string type; + std::string pubkey; + bool passed; } request; }; + /// Dev-RPC: daemon/test_trigger_p2p_resync + /// /// Deliberately undocumented; this RPC call is really only useful for testing purposes to reset /// the resync idle timer (which normally fires every 60s) for the test suite. /// /// Inputs: none /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status Generic RPC error code. "OK" is the success value. + /// - `status` -- Generic RPC error code. "OK" is the success value. struct TEST_TRIGGER_P2P_RESYNC : NO_ARGS { static constexpr auto names() { return NAMES("test_trigger_p2p_resync"); } }; + /// Dev-RPC: master_node/test_trigger_uptime_proof + /// /// Deliberately undocumented; this RPC call is really only useful for testing purposes to /// force send an uptime proof. NOT available on mainnet /// /// Inputs: none /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status Generic RPC error code. "OK" is the success value. + /// - `status` -- Generic RPC error code. "OK" is the success value. struct TEST_TRIGGER_UPTIME_PROOF : NO_ARGS { static constexpr auto names() { return NAMES("test_trigger_uptime_proof"); } @@ -2135,30 +2542,32 @@ namespace cryptonote::rpc { }; }; + /// RPC: bns/bns_owners_to_names + /// /// Get all the name mappings for the queried owner. The owner can be either a ed25519 public key or Monero style /// public key; by default purchases are owned by the spend public key of the purchasing wallet. /// /// Inputs: /// - /// - \p entries List of owner's public keys to find all Oxen Name Service entries for. - /// - \p include_expired Optional: if provided and true, include entries in the results even if they are expired - /// - /// Output values available from a public RPC endpoint: - /// - /// - \p status Generic RPC error code. "OK" is the success value. - /// - \p entries List of BNS names. Each element is structured as follows: - /// - \p request_index (Deprecated) The index in request's `entries` array that was resolved via Beldex Name Service. - /// - \p type The category the Beldex Name Service entry belongs to; currently 0 for Bchat, 1 for Wallet, 2 for Belnet and 3 Eth Address . - /// - \p name_hash The hash of the name that the owner purchased via Beldex Name Service in base64 - /// - \p owner The backup public key specified by the owner that purchased the Beldex Name Service entry. - /// - \p backup_owner The backup public key specified by the owner that purchased the Beldex Name Service entry. Omitted if no backup owner. - /// - \p encrypted_bchat_value The encrypted Bchat value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. - /// - \p encrypted_wallet_value The encrypted Wallet value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. - /// - \p encrypted_belnet_value The encrypted Belnet value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. - /// - \p encrypted_eth_addr_value The encrypted Eth Address value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. - /// - \p update_height The last height that this Beldex Name Service entry was updated on the Blockchain. - /// - \p expiration_height For records that expire, this will be set to the expiration block height. - /// - \p txid The txid of the mapping's most recent update or purchase. + /// - `entries` -- List of owner's public keys to find all Beldex Name Service entries for. + /// - `include_expired` -- Optional: if provided and true, include entries in the results even if they are expired + /// + /// Output: + /// + /// - `status` -- Generic RPC error code. "OK" is the success value. + /// - `entries` -- List of BNS names. Each element is structured as follows: + /// - `request_index` -- (Deprecated) The index in request's `entries` array that was resolved via Beldex Name Service. + /// - `type` -- The category the Beldex Name Service entry belongs to; currently 0 for Bchat, 1 for Wallet, 2 for Belnet and 3 Eth Address . + /// - `name_hash` -- The hash of the name that the owner purchased via Beldex Name Service in base64 + /// - `owner` -- The backup public key specified by the owner that purchased the Beldex Name Service entry. + /// - `backup_owner` -- The backup public key specified by the owner that purchased the Beldex Name Service entry. Omitted if no backup owner. + /// - `encrypted_bchat_value` -- The encrypted Bchat value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - `encrypted_wallet_value` -- The encrypted Wallet value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - `encrypted_belnet_value` -- The encrypted Belnet value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - `encrypted_eth_addr_value` -- The encrypted Eth Address value that the name maps to, in hex. This value is encrypted using the name (not the hash) as the secret. + /// - `update_height` -- The last height that this Beldex Name Service entry was updated on the Blockchain. + /// - `expiration_height` -- For records that expire, this will be set to the expiration block height. + /// - `txid` -- The txid of the mapping's most recent update or purchase. struct BNS_OWNERS_TO_NAMES : PUBLIC { static constexpr auto names() { return NAMES("bns_owners_to_names", "lns_owners_to_names"); } @@ -2166,36 +2575,45 @@ namespace cryptonote::rpc { static constexpr size_t MAX_REQUEST_ENTRIES = 256; struct request_parameters { - std::vector entries; // The owner's public key to find all Beldex Name Service entries for. - bool include_expired; // Optional: if provided and true, include entries in the results even if they are expired + std::vector entries; + bool include_expired; } request; struct response_entry { - uint64_t request_index; // (Deprecated) The index in request's `entries` array that was resolved via Beldex Name Service. - std::string name_hash; // The hash of the name that the owner purchased via Beldex Name Service in base64 - std::string owner; // The backup public key specified by the owner that purchased the Beldex Name Service entry. - std::optional backup_owner; // The backup public key specified by the owner that purchased the Beldex Name Service entry. Omitted if no backup owner. - std::string encrypted_bchat_value; // The bchat encrypted value that the name maps to, in hex. This value of bchat is encrypted using the name (not the hash) as the secret. - std::string encrypted_wallet_value; // The wallet encrypted value that the name maps to, in hex. This value of wallet is encrypted using the name (not the hash) as the secret. - std::string encrypted_belnet_value; // The belnet encrypted value that the name maps to, in hex. This value of belnet is encrypted using the name (not the hash) as the secret. - std::string encrypted_eth_addr_value; // The eth_address encrypted value that the name maps to, in hex. This value of eth_address is encrypted using the name (not the hash) as the secret. - uint64_t update_height; // The last height that this Beldex Name Service entry was updated on the Blockchain. - std::optional expiration_height;// For records that expire, this will be set to the expiration block height. - std::string txid; // The txid of the mapping's most recent update or purchase. + uint64_t request_index; + std::string name_hash; + std::string owner; + std::optional backup_owner; + std::string encrypted_bchat_value; + std::string encrypted_wallet_value; + std::string encrypted_belnet_value; + std::string encrypted_eth_addr_value; + uint64_t update_height; + std::optional expiration_height; + std::string txid; }; }; void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r); + /// RPC: bns/bns_resolve + /// /// Performs a simple BNS lookup of a BLAKE2b-hashed name. This RPC method is meant for simple, /// single-value resolutions that do not care about registration details, etc.; if you need more /// information use BNS_NAMES_TO_OWNERS instead. /// - /// Returned values: + /// Inputs: + /// + /// - `type` -- The BNS type (mandatory); currently supported values are: 0 = bchat, 1 = wallet, 2 = belnet, 6 = eth_addr. + /// - `name_hash` -- The 32-byte BLAKE2b hash of the name to look up, encoded as 64 hex digits or + /// 44/43 base64 characters (with/without padding). For bt-encoded requests this can also be + /// the raw 32 bytes. /// - /// - \p encrypted_value The encrypted BNS value, in hex. Will be omitted from the response if + /// Outputs: + /// + /// - `encrypted_value` -- The encrypted BNS value, in hex. Will be omitted from the response if /// the given name_hash is not registered. - /// - \p nonce The nonce value used for encryption, in hex. Will be omitted if the given name is + /// - `nonce` -- The nonce value used for encryption, in hex. Will be omitted if the given name is /// not registered. /// /// Technical details: the returned value is encrypted using the name itself so that neither this @@ -2244,32 +2662,34 @@ namespace cryptonote::rpc { KV_MAP_SERIALIZABLE }; }; - + + /// RPC: daemon/flush_cache + /// /// Clear TXs from the daemon cache, currently only the cache storing TX hashes that were previously verified bad by the daemon. /// /// Inputs: /// - /// - \p bad_txs Clear the cache storing TXs that failed verification. - /// - \p bad_blocks Clear the cache storing blocks that failed verfication. + /// - `bad_txs` -- Clear the cache storing TXs that failed verification. + /// - `bad_blocks` -- Clear the cache storing blocks that failed verfication. /// - /// Output values available from a public RPC endpoint: + /// Output: /// - /// - \p status Generic RPC error code. "OK" is the success value. + /// - `status` -- Generic RPC error code. "OK" is the success value. struct FLUSH_CACHE : RPC_COMMAND { static constexpr auto names() { return NAMES("flush_cache"); } struct request_parameter { - bool bad_txs; // Clear the cache storing TXs that failed verification. - bool bad_blocks; // Clear the cache storing blocks that failed verfication. + bool bad_txs; + bool bad_blocks; } request; }; - /// List of all supported rpc command structs to allow compile-time enumeration of all supported - /// RPC types. Every type added above that has an RPC endpoint needs to be added here, and needs - /// a core_rpc_server::invoke() overload that takes a ::request and returns a - /// ::response. The ::request has to be unique (for overload resolution); - /// ::response does not. + // List of all supported rpc command structs to allow compile-time enumeration of all supported + // RPC types. Every type added above that has an RPC endpoint needs to be added here, and needs + // a core_rpc_server::invoke() overload that takes a ::request and returns a + // ::response. The ::request has to be unique (for overload resolution); + // ::response does not. using core_rpc_types = tools::type_list< BANNED, FLUSH_CACHE, diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index fca49ac5e62..8b899d6621f 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -2,6 +2,7 @@ #include "http_server.h" #include #include +#include #include #include #include "common/command_line.h" @@ -479,7 +480,7 @@ namespace cryptonote::rpc { { std::shared_ptr data{new call_data{*this, m_server, res, std::string{req.getUrl()}, &call}}; auto& request = data->request; - request.body = ""s; + request.body = std::monostate{}; request.context.admin = !m_restricted; request.context.source = rpc_source::http; request.context.remote = get_remote_address(res); @@ -488,7 +489,12 @@ namespace cryptonote::rpc { res.onAborted([data] { data->aborted = true; }); res.onData([data=std::move(data)](std::string_view d, bool done) mutable { - var::get(data->request.body) += d; + if (!d.empty()) { + if (std::holds_alternative(data->request.body)) + data->request.body = std::string{d}; + else + var::get(data->request.body) += d; + } if (!done) return; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 6f872eddc20..7ffc1b3de4a 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -83,7 +83,7 @@ bool NodeRPCProxy::get_rpc_version(rpc::version_t &rpc_version) const { try { auto res = m_http_client.json_rpc("get_version", {}); - m_rpc_version = rpc::make_version(res["version"].get()); + m_rpc_version = rpc::make_version(res.at("version").get()); } catch (...) { return false; } } rpc_version = m_rpc_version; @@ -106,14 +106,14 @@ bool NodeRPCProxy::get_info() const { try { auto res = m_http_client.json_rpc("get_info", {}); - m_height = res["height"].get(); - m_target_height = res["target_height"].get(); + m_height = res.at("height").get(); + m_target_height = res.at("target_height").get(); auto it_block_weight_limit = res.find("block_weight_limit"); if (it_block_weight_limit != res.end()) - m_block_weight_limit = res["block_weight_limit"]; + m_block_weight_limit = res.at("block_weight_limit"); else - m_block_weight_limit = res["block_size_limit"]; - m_immutable_height = res["immutable_height"].get(); + m_block_weight_limit = res.at("block_size_limit"); + m_immutable_height = res.at("immutable_height").get(); m_get_info_time = now; m_height_time = now; } catch (...) { @@ -168,9 +168,7 @@ bool NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_heigh }; try { auto res = m_http_client.json_rpc("hard_fork_info", req_params); - if (!res["earliest_height"]) - return false; - m_earliest_height[version] = res["earliest_height"].get(); + m_earliest_height[version] = res.at("earliest_height").get(); } catch (...) { return false; } } earliest_height = m_earliest_height[version]; @@ -184,7 +182,7 @@ std::optional NodeRPCProxy::get_hardfork_version() const try { auto res = m_http_client.json_rpc("hard_fork_info", {}); - return res["version"].get(); + return res.at("version").get(); }catch (...) {} return std::nullopt; @@ -203,10 +201,10 @@ bool NodeRPCProxy::refresh_dynamic_base_fee_cache(uint64_t grace_blocks) const }; try { auto res = m_http_client.json_rpc("get_base_fee_estimate", req_params); - m_dynamic_base_fee_estimate = {res["fee_per_byte"].get(), res["fee_per_output"].get()}; + m_dynamic_base_fee_estimate = {res.at("fee_per_byte").get(), res.at("fee_per_output").get()}; m_dynamic_base_fee_estimate_cached_height = height; m_dynamic_base_fee_estimate_grace_blocks = grace_blocks; - m_fee_quantization_mask = res["quantization_mask"].get(); + m_fee_quantization_mask = res.at("quantization_mask").get(); } catch (...) { return false; } } return true; @@ -243,8 +241,8 @@ std::pair NodeRPCProxy::get_master_nodes(std::vector NodeRPCProxy::get_master_node_blacklisted_key_im try { auto res = m_http_client.json_rpc("get_master_node_blacklisted_key_images", {}); m_master_node_blacklisted_key_images_cached_height = height; - m_master_node_blacklisted_key_images = std::move(res["blacklist"]); + m_master_node_blacklisted_key_images = std::move(res.at("blacklist")); } catch (...) { return result; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a078710e423..84fb3cf413f 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -220,7 +220,7 @@ namespace { std::ostringstream os; const auto tvc = res["tvc"]; - if (auto got = tvc.find("m_verbose_error"); got != tvc.end()) os << res["tvc"]["m_verbose_error"].get() << "\n"; + if (auto got = tvc.find("m_verbose_error"); got != tvc.end()) os << res["tvc"]["m_verbose_error"].get() << "\n"; if (auto got = tvc.find("m_verifivation_failed"); got != tvc.end()) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection if (auto got = tvc.find("m_verifivation_impossible"); got != tvc.end()) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain if (auto got = tvc.find("m_should_be_relayed"); got == tvc.end()) os << "TX should NOT be relayed, "; @@ -913,9 +913,9 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry if (entry["pruned_as_hex"] && entry["prunable_hash"]) { crypto::hash ph; - CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["prunable_hash"].get(), ph), false, "Failed to parse prunable hash"); - CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()), false, "Invalid pruned tx entry"); - bd = oxenc::from_hex(entry["pruned"].get()); + CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["prunable_hash"].get(), ph), false, "Failed to parse prunable hash"); + CHECK_AND_ASSERT_MES(oxenc::is_hex(entry["pruned"].get()), false, "Invalid pruned tx entry"); + bd = oxenc::from_hex(entry["pruned"].get()); CHECK_AND_ASSERT_MES(parse_and_validate_tx_base_from_blob(bd, tx), false, "Invalid base tx data"); // only v2 txes can calculate their txid after pruned if (bd[0] > 1) @@ -925,7 +925,7 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry else { // for v1, we trust the dameon - CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["tx_hash"].get(), tx_hash), false, "Failed to parse tx hash"); + CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["tx_hash"].get(), tx_hash), false, "Failed to parse tx hash"); } return true; } @@ -3744,26 +3744,15 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector //---------------------------------------------------------------------------------------------------- bool wallet2::get_output_blacklist(std::vector &blacklist) { - rpc::version_t rpc_version; - if (!m_node_rpc_proxy.get_rpc_version(rpc_version)) - { - THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, "getversion"); - } - if (rpc_version < rpc::version_t{2, 3}) - { - MWARNING("Daemon is too old, not requesting output blacklist"); - return false; - } - MDEBUG("Daemon is recent enough, requesting output blacklist"); + cryptonote::rpc::GET_OUTPUT_BLACKLIST_BIN::response res{}; + bool r = invoke_http({}, res); - try { - auto res = m_http_client.json_rpc("get_output_blacklist", {}); - blacklist = std::move(res["blacklist"].get>()); - } catch (...) { + if (!r) + { MWARNING("Failed to request output blacklist: no connection to daemon"); return false; } - + blacklist = std::move(res.blacklist); return true; } //---------------------------------------------------------------------------------------------------- @@ -5868,7 +5857,7 @@ void wallet2::trim_hashchain() if (res["status"] == rpc::STATUS_OK) { crypto::hash hash; - tools::hex_to_type(res["block_header"]["hash"].get(), hash); + tools::hex_to_type(res["block_header"]["hash"].get(), hash); m_blockchain.refill(hash); } else @@ -6659,7 +6648,6 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, return true; { - std::optional failed; // FIXME: can just check one here by adding a is_key_image_blacklisted auto [success, blacklist] = m_node_rpc_proxy.get_master_node_blacklisted_key_images(); if (!success) @@ -6672,7 +6660,7 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, for (auto const& entry : blacklist) { crypto::key_image check_image; - if(!tools::hex_to_type(entry["key_image"].get(), check_image)) + if(!tools::hex_to_type(entry["key_image"].get(), check_image)) { MERROR("Failed to parse hex representation of key image: " << entry["key_image"]); break; @@ -6685,7 +6673,6 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, { const std::string primary_address = get_address_as_str(); - std::optional failed; auto [success, master_nodes_states] = m_node_rpc_proxy.get_contributed_master_nodes(primary_address); if (!success) { @@ -6695,17 +6682,18 @@ bool wallet2::is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height, for (auto const& entry : master_nodes_states) { - for (auto const& contributor : entry["contributors"]) + for (auto const& contributor : entry.at("contributors")) { - if (primary_address != contributor["address"]) + if (primary_address != contributor.at("address")) continue; - for (auto const &contribution : contributor["locked_contributions"]) + for (auto const &contribution : contributor.at("locked_contributions")) { + auto input_ki = contribution.at("key_image").get(); crypto::key_image check_image; - if(!tools::hex_to_type(contribution["key_image"].get(), check_image)) + if(!tools::hex_to_type(input_ki, check_image)) { - MERROR("Failed to parse hex representation of key image: " << contribution["key_image"]); + MERROR("Failed to parse hex representation of key image: " << input_ki); break; } @@ -8111,7 +8099,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ if (!success) { result.status = stake_result_status::master_node_list_query_failed; - result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; + result.msg = ERR_MSG_MASTER_NODE_LIST_QUERY_FAILED; return result; } @@ -8149,7 +8137,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ for (const auto& contributor : mnode_info["contributors"]) { address_parse_info info; - if (!cryptonote::get_account_address_from_str(info, m_nettype, contributor["address"].get())) + if (!cryptonote::get_account_address_from_str(info, m_nettype, contributor["address"].get())) continue; if (info.address == addr_info.address) @@ -8485,7 +8473,7 @@ wallet2::register_master_node_result wallet2::create_register_master_node_tx(con if (!success) { result.status = register_master_node_result_status::master_node_list_query_failed; - result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED; + result.msg = ERR_MSG_MASTER_NODE_LIST_QUERY_FAILED; return result; } @@ -8583,7 +8571,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry for (auto const &contributor : node_info["contributors"]) { address_parse_info address_info = {}; - cryptonote::get_account_address_from_str(address_info, nettype(), contributor["address"].get()); + cryptonote::get_account_address_from_str(address_info, nettype(), contributor["address"].get()); if (address_info.address != primary_address) continue; @@ -8622,7 +8610,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg.append("Key image: "); result.msg.append(contribution["key_image"]); result.msg.append(" has already been requested to be unlocked, unlocking at height: "); - result.msg.append(node_info["requested_unlock_height"].get()); + result.msg.append(node_info["requested_unlock_height"].get()); result.msg.append(" (about "); result.msg.append(tools::get_human_readable_timespan(std::chrono::seconds((node_info["requested_unlock_height"].get() - curr_height) * TARGET_BLOCK_TIME))); result.msg.append(")"); @@ -8660,7 +8648,7 @@ wallet2::request_stake_unlock_result wallet2::can_request_stake_unlock(const cry result.msg.append(tools::get_human_readable_timespan(std::chrono::seconds((unlock_height - curr_height) * TARGET_BLOCK_TIME))); result.msg.append(")"); - if(!tools::hex_to_type(contribution["key_image"].get(), unlock.key_image)) + if(!tools::hex_to_type(contribution["key_image"].get(), unlock.key_image)) { result.msg = tr("Failed to parse hex representation of key image: ") + contribution["key_image"].get(); return result; @@ -8837,7 +8825,7 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons if ((*response)["entries"].size()) { - if (!tools::hex_to_type((*response)["entries"][0]["txid"].get(), result.prev_txid)) + if (!tools::hex_to_type((*response)["entries"][0]["txid"].get(), result.prev_txid)) { if (reason) *reason = "Failed to convert response txid=" + (*response)["entries"][0]["txid"].get() + " from the daemon into a 32 byte hash, it must be a 64 char hex string"; return result; @@ -8856,7 +8844,7 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons cryptonote::address_parse_info curr_backup_owner_parsed = {}; auto& rowner = (*response)["entries"].front()["owner"]; std::string* rbackup_owner = (*response)["entries"].front().value("backup_owner", nullptr); - bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner.get()); + bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner.get()); bool curr_backup_owner = rbackup_owner && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), *rbackup_owner); if (!try_generate_bns_signature(wallet, rowner, owner, backup_owner, result)) { @@ -12371,8 +12359,10 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s std::string tx_data; crypto::hash tx_prefix_hash{}; const auto& res_tx = res["txs"].front(); + std::string tx_blob_hex = res_tx["pruned"].get(); + if (res_tx["prunable"]) + tx_blob_hex.append(res_tx["prunable"].get()); - std::string tx_blob_hex = res_tx["pruned"].get() + (res_tx["prunable"] ? res_tx["prunable"].get() : ""s); THROW_WALLET_EXCEPTION_IF(not oxenc::is_hex(tx_blob_hex), error::wallet_internal_error, "Failed to parse transaction from daemon"); tx_data = oxenc::from_hex(tx_blob_hex); THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), From 0a2959d24c376cd320f8b3ba8335d41988c77874 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 5 May 2025 17:46:56 +0530 Subject: [PATCH 125/182] RPC: GET_MASTER_NODE_REGISTRATION_CMD updated --- src/rpc/core_rpc_server_command_parser.cpp | 8 ++++---- src/rpc/core_rpc_server_command_parser.h | 2 +- src/rpc/core_rpc_server_commands_defs.h | 3 +-- src/wallet/node_rpc_proxy.cpp | 18 +++++++++++++----- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index d14811d8abd..cee0dcc1472 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -417,9 +417,9 @@ namespace cryptonote::rpc { void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in) { get_values(in, - "contributor_addresses", cmd.request.contributor_addresses, - "contributor_amounts", cmd.request.contributor_amounts, - "operator_cut", cmd.request.operator_cut, - "staking_requirement", cmd.request.staking_requirement); + "contributor_addresses", required{cmd.request.contributor_addresses}, + "contributor_amounts", required{cmd.request.contributor_amounts}, + "operator_cut", required{cmd.request.operator_cut}, + "staking_requirement", required{cmd.request.staking_requirement}); } } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 37ea898c961..af2f145cd98 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -27,7 +27,7 @@ namespace cryptonote::rpc { void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in); void parse_request(GET_PEER_LIST& bh, rpc_input in); void parse_request(GET_MASTER_NODES& sns, rpc_input in); - // void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); + void parse_request(GET_MASTER_NODE_REGISTRATION_CMD& cmd, rpc_input in); void parse_request(GET_MASTER_NODE_REGISTRATION_CMD_RAW& cmd, rpc_input in); void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 3723f1a3b41..1f362d1248b 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1931,7 +1931,6 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT struct GET_MASTER_NODE_REGISTRATION_CMD : RPC_COMMAND { static constexpr auto names() { return NAMES("get_master_node_registration_cmd"); } @@ -2718,6 +2717,7 @@ namespace cryptonote::rpc { GET_MASTER_KEYS, GET_MASTER_NODES, GET_MASTER_NODE_BLACKLISTED_KEY_IMAGES, + GET_MASTER_NODE_REGISTRATION_CMD, GET_MASTER_NODE_REGISTRATION_CMD_RAW, GET_MASTER_NODE_STATUS, GET_MASTER_PRIVKEYS, @@ -2757,7 +2757,6 @@ namespace cryptonote::rpc { using FIXME_old_rpc_types = tools::type_list< RELAY_TX, GET_OUTPUT_DISTRIBUTION, - GET_MASTER_NODE_REGISTRATION_CMD, BNS_LOOKUP, BNS_VALUE_DECRYPT >; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7ffc1b3de4a..d92d7fe11de 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -113,10 +113,13 @@ bool NodeRPCProxy::get_info() const m_block_weight_limit = res.at("block_weight_limit"); else m_block_weight_limit = res.at("block_size_limit"); - m_immutable_height = res.at("immutable_height").get(); - m_get_info_time = now; - m_height_time = now; - } catch (...) { + auto it_immutable_height = res.find("immutable_height"); + if (it_immutable_height != res.end()) + m_immutable_height = res.at("immutable_height").get(); + m_get_info_time = now; + m_height_time = now; + } catch (const std::exception& e) { + // log::error(logcat, "Failed to get info message: {}", e.what()); //TODO return false; } } return true; @@ -126,8 +129,10 @@ bool NodeRPCProxy::get_height(uint64_t &height) const { auto now = std::chrono::steady_clock::now(); if (now >= m_height_time + 30s) // re-cache every 30 seconds + { if (!get_info()) return false; + } height = m_height; return true; } @@ -169,7 +174,10 @@ bool NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_heigh try { auto res = m_http_client.json_rpc("hard_fork_info", req_params); m_earliest_height[version] = res.at("earliest_height").get(); - } catch (...) { return false; } + } catch (const std::exception& e) { + // log::error(logcat, "Failed to get earliest height: {}", e.what()); //TODO + return false; + } } earliest_height = m_earliest_height[version]; return true; From e7fd8f2032b62b3bfb8a84d105a82e0e56d97205 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Mon, 5 May 2025 17:54:05 +0530 Subject: [PATCH 126/182] Fix wallet2 staking for missing reserved fields and enforce safe key access --- src/rpc/core_rpc_server.cpp | 5 +++-- src/wallet/node_rpc_proxy.cpp | 16 +++++++--------- src/wallet/wallet2.cpp | 28 ++++++++++++++++++---------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0d978685fec..b7da8a7c093 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1785,7 +1785,7 @@ namespace cryptonote::rpc { std::vector tx_hashes; tx_hashes.reserve(blk.tx_hashes.size()); std::transform(blk.tx_hashes.begin(), blk.tx_hashes.end(), std::back_inserter(tx_hashes), [](const auto& x) { return tools::type_to_hex(x); }); - get_block.response["tx_hashes"] = tx_hashes; + get_block.response["tx_hashes"] = std::move(tx_hashes); get_block.response["blob"] = oxenc::to_hex(t_serializable_object_to_blob(blk)); get_block.response["json"] = obj_to_json_str(blk); get_block.response["status"] = STATUS_OK; @@ -2853,6 +2853,7 @@ namespace cryptonote::rpc { }); if (requested(reqed, "contributors")) { + bool want_locked_c = requested(reqed, "locked_contributions"); auto& contributors = (entry["contributors"] = json::array()); for (const auto& contributor : info.contributors) { auto& c = contributors.emplace_back(json{ @@ -2860,7 +2861,7 @@ namespace cryptonote::rpc { {"address", cryptonote::get_account_address_as_str(m_core.get_nettype(), false/*subaddress*/, contributor.address)}}); if (contributor.reserved != contributor.amount) c["reserved"] = contributor.reserved; - if (requested(reqed, "locked_contributions")) { + if (want_locked_c) { auto& locked = (c["locked_contributions"] = json::array()); for (const auto& src : contributor.locked_contributions) { auto& lc = locked.emplace_back(json{{"amount", src.amount}}); diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index d92d7fe11de..eedd4961790 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -265,15 +265,13 @@ bool NodeRPCProxy::update_all_master_nodes_cache(uint64_t height) const { nlohmann::json req{}; req["fields"] = nlohmann::json{}; - req["fields"]["active"] = true; - req["fields"]["contributors"] = true; - req["fields"]["funded"] = true; - req["fields"]["locked_contributions"] = true; - req["fields"]["registration_height"] = true; - req["fields"]["requested_unlock_height"] = true; - req["fields"]["master_node_pubkey"] = true; - req["fields"]["staking_requirement"] = true; - req["fields"]["total_reserved"] = true; + for (const auto& field : { + "active", "contributors", "funded", "locked_contributions", "registration_height", + "requested_unlock_height", "master_node_pubkey", "staking_requirement", "total_contributed", + "total_reserved", + }) + req["fields"][field] = true; + try { auto res = m_http_client.json_rpc("get_master_nodes", req); m_all_master_nodes_cached_height = height; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 84fb3cf413f..eea15e50208 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8119,30 +8119,38 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ } const auto& mnode_info = response.front(); + const auto staking_req = mnode_info.at("staking_requirement").get(); + const auto total_res = mnode_info.value("total_reserved", + mnode_info.at("total_contributed").get()); if (amount == 0) - amount = mnode_info["staking_requirement"].get() * fraction; + amount = staking_req * fraction; size_t total_existing_contributions = 0; // Count both contributions and reserved spots - for (auto const &contributor : mnode_info["contributors"]) + const auto& contributors = mnode_info.at("contributors"); + for (auto const &contributor : contributors) { - total_existing_contributions += contributor["locked_contributions"].size(); // contribution - if (contributor["reserved"].get() > contributor["amount"].get()) + total_existing_contributions += contributor.at("locked_contributions").size(); // contribution + if (auto it = contributor.find("reserved"); it != contributor.end() && it->get() > contributor.at("amount").get()) total_existing_contributions++; // reserved contributor spot } - uint64_t max_contrib_total = mnode_info["staking_requirement"].get() - mnode_info["total_reserved"].get(); - uint64_t min_contrib_total = master_nodes::get_min_node_contribution(*hf_version, mnode_info["staking_requirement"], mnode_info["total_reserved"], total_existing_contributions); + uint64_t max_contrib_total = staking_req - total_res; + uint64_t min_contrib_total = master_nodes::get_min_node_contribution(*hf_version, staking_req, total_res, total_existing_contributions); bool is_preexisting_contributor = false; - for (const auto& contributor : mnode_info["contributors"]) + for (const auto& contributor : contributors) { address_parse_info info; - if (!cryptonote::get_account_address_from_str(info, m_nettype, contributor["address"].get())) + if (!cryptonote::get_account_address_from_str(info, m_nettype, contributor.at("address").get())) continue; if (info.address == addr_info.address) { - uint64_t const reserved_amount_not_contributed_yet = contributor["reserved"].get() - contributor["amount"].get(); + const auto amount = contributor.at("amount").get(); + + uint64_t reserved_amount_not_contributed_yet = 0; + if (auto it = contributor.find("reserved"); it != contributor.end()) + reserved_amount_not_contributed_yet = it->get() - contributor.at("amount").get(); max_contrib_total += reserved_amount_not_contributed_yet; is_preexisting_contributor = true; @@ -8159,7 +8167,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& mn_ return result; } - const bool full = mnode_info["contributors"].size() >= beldex::MAX_NUMBER_OF_CONTRIBUTORS; + const bool full = contributors.size() >= beldex::MAX_NUMBER_OF_CONTRIBUTORS; if (full && !is_preexisting_contributor) { result.status = stake_result_status::master_node_contributors_maxed; From e7759279bc7318a59078c0cb615fabaf9bceea7c Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Mon, 5 May 2025 19:35:30 +0530 Subject: [PATCH 127/182] =?UTF-8?q?Fix:=20Redo=20LMQ=20=E2=86=92=20OMQ=20r?= =?UTF-8?q?ename=20and=20update=20json=5Farchiver=20to=20use=20nlohmann::j?= =?UTF-8?q?son?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/CMakeLists.txt | 1 + .../json_binary_proxy.cpp} | 4 +- .../json_binary_proxy.h} | 40 ++-- src/cryptonote_basic/cryptonote_basic.h | 13 +- .../cryptonote_format_utils.h | 8 +- src/cryptonote_core/cryptonote_core.cpp | 4 +- src/cryptonote_core/cryptonote_core.h | 4 +- src/cryptonote_core/pos.cpp | 4 +- src/daemon/daemon.cpp | 2 +- src/daemon/daemon.h | 2 +- src/ringct/rctTypes.h | 24 +-- src/rpc/CMakeLists.txt | 2 +- src/rpc/common/CMakeLists.txt | 1 - src/rpc/common/command_decorators.cpp | 4 +- src/rpc/common/command_decorators.h | 10 +- src/rpc/common/param_parser.hpp | 8 +- src/rpc/core_rpc_server.cpp | 37 +++- src/rpc/core_rpc_server_binary_commands.h | 4 +- src/rpc/core_rpc_server_command_parser.cpp | 9 +- src/rpc/core_rpc_server_commands_defs.cpp | 4 +- src/rpc/core_rpc_server_commands_defs.h | 3 +- src/rpc/http_server.cpp | 6 +- src/rpc/{lmq_server.cpp => omq_server.cpp} | 42 ++-- src/rpc/{lmq_server.h => omq_server.h} | 2 +- src/serialization/binary_archive.h | 51 ++--- src/serialization/container.h | 3 +- src/serialization/json_archive.h | 193 +++++++----------- src/serialization/json_utils.h | 60 ------ src/serialization/pair.h | 5 +- 29 files changed, 228 insertions(+), 322 deletions(-) rename src/{rpc/common/rpc_binary.cpp => common/json_binary_proxy.cpp} (96%) rename src/{rpc/common/rpc_binary.h => common/json_binary_proxy.h} (74%) rename src/rpc/{lmq_server.cpp => omq_server.cpp} (95%) rename src/rpc/{lmq_server.h => omq_server.h} (97%) delete mode 100755 src/serialization/json_utils.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index b4d4a98b311..265399842a5 100755 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -36,6 +36,7 @@ add_library(common expect.cpp file.cpp i18n.cpp + json_binary_proxy.cpp beldex.cpp notify.cpp password.cpp diff --git a/src/rpc/common/rpc_binary.cpp b/src/common/json_binary_proxy.cpp similarity index 96% rename from src/rpc/common/rpc_binary.cpp rename to src/common/json_binary_proxy.cpp index eef636c4737..ac2b1d7fbc3 100644 --- a/src/rpc/common/rpc_binary.cpp +++ b/src/common/json_binary_proxy.cpp @@ -1,8 +1,8 @@ -#include "rpc_binary.h" +#include "json_binary_proxy.h" #include #include -namespace cryptonote::rpc { +namespace tools { void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data) { if (allow_raw && bytes.size() == raw_size) { diff --git a/src/rpc/common/rpc_binary.h b/src/common/json_binary_proxy.h similarity index 74% rename from src/rpc/common/rpc_binary.h rename to src/common/json_binary_proxy.h index f202a6bb6b7..42f8ddc8bd8 100644 --- a/src/rpc/common/rpc_binary.h +++ b/src/common/json_binary_proxy.h @@ -8,37 +8,37 @@ using namespace std::literals; -namespace cryptonote::rpc { +namespace tools { // Binary types that we support for rpc input/output. For json, these must be specified as hex or // base64; for bt-encoded requests these can be accepted as binary, hex, or base64. template - inline constexpr bool is_binary_parameter = false; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; - template <> inline constexpr bool is_binary_parameter = true; + inline constexpr bool json_is_binary = false; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; + template <> inline constexpr bool json_is_binary = true; template - inline constexpr bool is_binary_container = false; + inline constexpr bool json_is_binary_container = false; template - inline constexpr bool is_binary_container> = is_binary_parameter; + inline constexpr bool json_is_binary_container> = json_is_binary; template - inline constexpr bool is_binary_container> = is_binary_parameter; + inline constexpr bool json_is_binary_container> = json_is_binary; // De-referencing wrappers around the above: - template inline constexpr bool is_binary_parameter = is_binary_parameter; - template inline constexpr bool is_binary_parameter = is_binary_parameter; - template inline constexpr bool is_binary_container = is_binary_container; - template inline constexpr bool is_binary_container = is_binary_container; + template inline constexpr bool json_is_binary = json_is_binary; + template inline constexpr bool json_is_binary = json_is_binary; + template inline constexpr bool json_is_binary_container = json_is_binary_container; + template inline constexpr bool json_is_binary_container = json_is_binary_container; void load_binary_parameter_impl(std::string_view bytes, size_t raw_size, bool allow_raw, uint8_t* val_data); // Loads a binary value from a string_view which may contain hex, base64, and (optionally) raw // bytes. - template >> + template >> void load_binary_parameter(std::string_view bytes, bool allow_raw, T& val) { load_binary_parameter_impl(bytes, sizeof(T), allow_raw, reinterpret_cast(&val)); } @@ -82,13 +82,13 @@ namespace cryptonote::rpc { /// Takes a trivial, no-padding data structure (e.g. a crypto::hash) as the value and dumps its /// contents as the binary value. - template , int> = 0> + template , int> = 0> nlohmann::json& operator=(const T& val) { return *this = std::string_view{reinterpret_cast(&val), sizeof(val)}; } /// Takes a vector of some json_binary_proxy-assignable type and builds an array by assigning /// each one into a new array of binary values. - template , int> = 0> + template , int> = 0> nlohmann::json& operator=(const T& vals) { auto a = nlohmann::json::array(); for (auto& val : vals) @@ -122,14 +122,14 @@ namespace cryptonote::rpc { // invoked; for serialization you need to use RPC_COMMAND::response_hex (or _b64) instead. namespace nlohmann { template - struct adl_serializer>> { + struct adl_serializer>> { static_assert(std::is_trivially_copyable_v && std::has_unique_object_representations_v); static void to_json(json& j, const T&) { throw std::logic_error{"Internal error: binary types are not directly serializable"}; } static void from_json(const json& j, T& val) { - cryptonote::rpc::load_binary_parameter(j.get(), false /*no raw*/, val); + tools::load_binary_parameter(j.get(), false /*no raw*/, val); } }; } diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index c5a904288f0..2571d4c89e0 100755 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -36,7 +36,6 @@ #include "serialization/variant.h" #include "serialization/vector.h" #include "serialization/binary_archive.h" -#include "serialization/json_archive.h" #include "serialization/crypto.h" #include "epee/serialization/keyvalue_serialization.h" // eepe named serialization #include "cryptonote_config.h" @@ -275,16 +274,18 @@ namespace cryptonote set_blob_size_valid(false); } - const unsigned int start_pos = Binary ? ar.streampos() : 0; + unsigned int start_pos = 0; + if constexpr (Binary) + start_pos = ar.streampos(); serialization::value(ar, static_cast(*this)); - if (Binary) + if constexpr (Binary) prefix_size = ar.streampos() - start_pos; if (version == txversion::v1) { - if (Binary) + if constexpr (Binary) unprunable_size = ar.streampos() - start_pos; ar.tag("signatures"); @@ -311,7 +312,7 @@ namespace cryptonote else if (signature_size != signatures[i].size()) throw std::invalid_argument{"Invalid signature size (expected " + std::to_string(signature_size) + ", have " + std::to_string(signatures[i].size()) + ")"}; - value(arr.element(), signatures[i]); + value(ar, signatures[i]); } } else @@ -324,7 +325,7 @@ namespace cryptonote rct_signatures.serialize_rctsig_base(ar, vin.size(), vout.size()); } - if (Binary) + if constexpr (Binary) unprunable_size = ar.streampos() - start_pos; if (!pruned && rct_signatures.type != rct::RCTType::Null) diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 6ae77967351..8318711455a 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -39,6 +39,7 @@ #include "common/meta.h" #include "common/string_util.h" #include "serialization/binary_utils.h" +#include "serialization/json_archive.h" #include namespace epee @@ -319,15 +320,12 @@ namespace cryptonote template std::string obj_to_json_str(T&& obj, bool indent = false) { - std::ostringstream ss; - serialization::json_archiver ar{ss, indent}; try { - serialize(ar, obj); + return serialization::dump_json(obj, indent ? 2 : -1); } catch (const std::exception& e) { LOG_ERROR("obj_to_json_str failed: serialization failed: " << e.what()); - return ""s; } - return ss.str(); + return ""s; } //--------------------------------------------------------------- blobdata block_to_blob(const block& b); diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 14000e941e1..55716b4f665 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -981,7 +981,7 @@ namespace cryptonote MGINFO_YELLOW("- x25519: " << tools::type_to_hex(keys.pub_x25519)); } else { // Only print the x25519 version because it's the only thing useful for a non-MN (for - // encrypted LMQ RPC connections). + // encrypted OMQ RPC connections). MGINFO_YELLOW("x25519 public key: " << tools::type_to_hex(keys.pub_x25519)); } @@ -2334,7 +2334,7 @@ namespace cryptonote MGINFO_RED( "Another master node (" << pk << ") is broadcasting the same public IP and ports as this master node (" << epee::string_tools::get_ip_string_from_int32(m_mn_public_ip) << ":" << proof.proof->qnet_port << "[qnet], :" << - proof.proof->storage_https_port << "[SS-HTTP], :" << proof.proof->storage_omq_port << "[SS-LMQ]). " + proof.proof->storage_https_port << "[SS-HTTP], :" << proof.proof->storage_omq_port << "[SS-OMQ]). " "This will lead to deregistration of one or both master nodes if not corrected. " "(Do both master nodes have the correct IP for the master-node-public-ip setting?)"); }); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 29eaa2b373a..ccddda5d495 100755 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -1205,8 +1205,8 @@ namespace cryptonote // avoid linking issues (protocol does not link against core). void* m_quorumnet_state = nullptr; - /// Stores x25519 -> access level for LMQ authentication. - /// Not to be modified after the LMQ listener starts. + /// Stores x25519 -> access level for OMQ authentication. + /// Not to be modified after the OMQ listener starts. std::unordered_map m_omq_auth; size_t block_sync_size; diff --git a/src/cryptonote_core/pos.cpp b/src/cryptonote_core/pos.cpp index 3c8c6bf1292..8c8e8133fe4 100755 --- a/src/cryptonote_core/pos.cpp +++ b/src/cryptonote_core/pos.cpp @@ -762,13 +762,13 @@ bool POS::get_round_timings(cryptonote::Blockchain const &blockchain, uint64_t b /* POS progresses via a state-machine that is iterated through job submissions - to 1 dedicated POS thread, started by LMQ. + to 1 dedicated POS thread, started by OMQ. Iterating the state-machine is done by a periodic invocation of POS::main(...) and messages received via Quorumnet for POS, which are queued in the thread's job queue. - Using 1 dedicated thread via LMQ avoids any synchronization required in the + Using 1 dedicated thread via OMQ avoids any synchronization required in the user code when implementing POS. Skip control flow graph for textual description of stages. diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index b8ae389b4c8..b64b1aade00 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -43,7 +43,7 @@ #endif #include "rpc/common/rpc_args.h" #include "rpc/http_server.h" -#include "rpc/lmq_server.h" +#include "rpc/omq_server.h" // #include "rpc/bootstrap_daemon.h" #include "cryptonote_protocol/quorumnet.h" #include "cryptonote_core/uptime_proof.h" diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h index ed3d8cf8a52..6838c6c4eaa 100755 --- a/src/daemon/daemon.h +++ b/src/daemon/daemon.h @@ -34,7 +34,7 @@ #include "p2p/net_node.h" #include "rpc/core_rpc_server.h" #include "rpc/http_server.h" -#include "rpc/lmq_server.h" +#include "rpc/omq_server.h" #include "blocks/blocks.h" #include "rpc/core_rpc_server.h" diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index aee1c2cea4f..8dd42a6ab44 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -303,7 +303,7 @@ namespace rct { { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& e : pseudoOuts) - value(arr.element(), e); + value(ar, e); } { @@ -311,21 +311,21 @@ namespace rct { if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG)) { for (auto& e : ecdhInfo) { - auto obj = arr.element().begin_object(); + auto obj = ar.begin_object(); if (Archive::is_deserializer) memset(e.amount.bytes, 0, sizeof(e.amount.bytes)); field(ar, "amount", reinterpret_cast(e.amount)); } } else { for (auto& e : ecdhInfo) - value(arr.element(), e); + value(ar, e); } } { auto arr = start_array(ar, "outPk", outPk, outputs); for (auto& e : outPk) - value(arr.element(), e.mask); + value(ar, e.mask); } } }; @@ -356,7 +356,7 @@ namespace rct { auto arr = start_array(ar, "bp", bulletproofs, nbp); for (auto& b : bulletproofs) - value(arr.element(), b); + value(ar, b); if (auto n_max = n_bulletproof_max_amounts(bulletproofs); n_max < outputs) throw std::invalid_argument{"invalid bulletproofs: n_max (" + std::to_string(n_max) + ") < outputs (" + std::to_string(outputs) + ")"}; @@ -365,7 +365,7 @@ namespace rct { { auto arr = start_array(ar, "rangeSigs", rangeSigs, outputs); for (auto& s : rangeSigs) - value(arr.element(), s); + value(ar, s); } if (type == RCTType::CLSAG) @@ -377,11 +377,11 @@ namespace rct { // we save the CLSAGs contents directly, because we want it to save its // arrays without the size prefixes, and the load can't know what size // to expect if it's not in the data - auto obj = arr.element().begin_object(); + auto obj = ar.begin_object(); { auto arr_s = start_array(ar, "s", clsag.s, mixin + 1); for (auto& x : clsag.s) - value(arr_s.element(), x); + value(ar, x); } field(ar, "c1", clsag.c1); field(ar, "D", clsag.D); @@ -402,7 +402,7 @@ namespace rct { auto arr = start_array(ar, "MGs", MGs, mg_elements); for (auto& mg : MGs) { - auto obj = arr.element().begin_object(); + auto obj = ar.begin_object(); // we save the MGs contents directly, because we want it to save its // arrays and matrices without the size prefixes, and the load can't @@ -411,14 +411,14 @@ namespace rct { auto arr_ss = start_array(ar, "ss", mg.ss, mixin + 1); for (auto& ss : mg.ss) { - auto arr_ss2 = arr_ss.element().begin_array(); + auto arr_ss2 = ar.begin_array(); if constexpr (Archive::is_deserializer) ss.resize(mg_ss2_elements); else if (ss.size() != mg_ss2_elements) throw std::invalid_argument{"invalid mg_ss2 size: have " + std::to_string(ss.size()) + ", expected " + std::to_string(mg_ss2_elements)}; for (auto& x : ss) - value(arr_ss2.element(), x); + value(ar, x); } } field(ar, "cc", mg.cc); @@ -430,7 +430,7 @@ namespace rct { { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& o : pseudoOuts) - value(arr.element(), o); + value(ar, o); } } diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 1d7b1afb09e..c7b6c30a71c 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -42,7 +42,7 @@ add_library(rpc add_library(daemon_rpc_server http_server.cpp - lmq_server.cpp + omq_server.cpp ) add_library(rpc_http_client diff --git a/src/rpc/common/CMakeLists.txt b/src/rpc/common/CMakeLists.txt index e15a959f002..6e90f577954 100644 --- a/src/rpc/common/CMakeLists.txt +++ b/src/rpc/common/CMakeLists.txt @@ -1,5 +1,4 @@ add_library(rpc_common - rpc_binary.cpp command_decorators.cpp json_bt.cpp rpc_args.cpp diff --git a/src/rpc/common/command_decorators.cpp b/src/rpc/common/command_decorators.cpp index 7d4d1b45662..9bee00c966a 100644 --- a/src/rpc/common/command_decorators.cpp +++ b/src/rpc/common/command_decorators.cpp @@ -5,8 +5,8 @@ namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { bt = true; - response_b64.format = json_binary_proxy::fmt::bt; - response_hex.format = json_binary_proxy::fmt::bt; + response_b64.format = tools::json_binary_proxy::fmt::bt; + response_hex.format = tools::json_binary_proxy::fmt::bt; } } // namespace cryptonote::rpc \ No newline at end of file diff --git a/src/rpc/common/command_decorators.h b/src/rpc/common/command_decorators.h index 83ac433fc05..72d8837d1dd 100644 --- a/src/rpc/common/command_decorators.h +++ b/src/rpc/common/command_decorators.h @@ -1,6 +1,6 @@ #pragma once -#include "rpc_binary.h" +#include "common/json_binary_proxy.h" #include @@ -17,7 +17,7 @@ namespace cryptonote::rpc { /// Base class that all RPC commands must inherit from (either directly or via one or more of the /// below tags). Inheriting from this (and no others) gives you a private, json, non-legacy RPC - /// command. For LMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be + /// command. For OMQ RPC the command will be available at `admin.whatever`; for HTTP RPC it'll be /// at `whatever`. This base class is also where response objects are stored. struct RPC_COMMAND { private: @@ -58,7 +58,7 @@ namespace cryptonote::rpc { /// Usage: /// std::string data = "abc"; /// rpc.response_hex["foo"]["bar"] = data; // json: "616263", bt: "abc" - json_binary_proxy response_hex{response, json_binary_proxy::fmt::hex}; + tools::json_binary_proxy response_hex{response, tools::json_binary_proxy::fmt::hex}; /// Proxy object that encodes binary data as base64 for json, leaving it as binary for /// bt-encoded responses. @@ -66,13 +66,13 @@ namespace cryptonote::rpc { /// Usage: /// std::string data = "abc"; /// rpc.response_b64["foo"]["bar"] = data; // json: "YWJj", bt: "abc" - json_binary_proxy response_b64{response, json_binary_proxy::fmt::base64}; + tools::json_binary_proxy response_b64{response, tools::json_binary_proxy::fmt::base64}; }; /// Tag types that are used (via inheritance) to set rpc endpoint properties /// Specifies that the RPC call is public (i.e. available through restricted rpc). If this is - /// *not* inherited from then the command is restricted (i.e. only available to admins). For LMQ, + /// *not* inherited from then the command is restricted (i.e. only available to admins). For OMQ, /// PUBLIC commands are available at `rpc.command` (versus non-PUBLIC ones at `admin.command`). struct PUBLIC : virtual RPC_COMMAND {}; diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index dbf08651c38..da4d61dbe01 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -1,6 +1,6 @@ #pragma once -#include "rpc/common/rpc_binary.h" +#include "common/json_binary_proxy.h" #include #include @@ -127,8 +127,8 @@ namespace cryptonote::rpc { val = c.template consume_integer(); else if constexpr (std::is_same_v || std::is_same_v) val = c.consume_string_view(); - else if constexpr (is_binary_parameter) - load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); + else if constexpr (tools::json_is_binary) + tools::load_binary_parameter(c.consume_string_view(), true /*allow raw*/, val); else if constexpr (is_expandable_list) { auto lc = c.consume_list_consumer(); val.clear(); @@ -182,7 +182,7 @@ namespace cryptonote::rpc { val = i; } else if constexpr (std::is_same_v || std::is_same_v) { val = e.get(); - } else if constexpr (is_binary_parameter || is_expandable_list || is_tuple_like) { + } else if constexpr (tools::json_is_binary || is_expandable_list || is_tuple_like) { try { e.get_to(val); } catch (const std::exception& e) { throw std::domain_error{"Invalid values in '" + key + "'"}; } } else { diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b7da8a7c093..fd0d3f917da 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -37,6 +37,7 @@ #include #include #include +#include "common/json_binary_proxy.h" #include #include "epee/net/network_throttle.hpp" #include "common/string_util.h" @@ -70,6 +71,7 @@ #include "net/parse.h" #include "crypto/hash.h" #include "p2p/net_node.h" +#include "serialization/json_archive.h" #include "version.h" #include @@ -80,6 +82,7 @@ namespace cryptonote::rpc { using nlohmann::json; + using tools::json_binary_proxy; namespace { template @@ -660,13 +663,16 @@ namespace cryptonote::rpc { // a single one we want just the value itself; this does that. Returns a reference to the // assigned value (whether as a top-level value or array element). template - json& set(const std::string& key, T&& value, bool binary = is_binary_parameter || is_binary_container) { + json& set( + const std::string& key, + T&& value, + /*[[maybe_unused]]*/ bool binary = tools::json_is_binary || tools::json_is_binary_container) { auto* x = &entry[key]; if (!x->is_null() && !x->is_array()) x = &(entry[key] = json::array({std::move(*x)})); if (x->is_array()) x = &x->emplace_back(); - if constexpr (is_binary_parameter || is_binary_container || std::is_convertible_v) { + if constexpr (tools::json_is_binary || tools::json_is_binary_container || std::is_convertible_v) { if (binary) return json_binary_proxy{*x, format} = std::forward(value); } @@ -1021,12 +1027,34 @@ namespace cryptonote::rpc { return; } } + std::optional extra; if (get.request.tx_extra) - load_tx_extra_data(e["extra"], tx, nettype(), hf_version, get.is_bt()); + load_tx_extra_data(extra.emplace(), tx, nettype(), hf_version, get.is_bt()); + if (get.request.tx_extra_raw) + e_bin["tx_extra_raw"] = std::string_view{reinterpret_cast(tx.extra.data()), tx.extra.size()}; + // Clear it because we don't want/care about it in the RPC output (we already got it more + // usefully from the above). + tx.extra.clear(); + + { + serialization::json_archiver ja{ + get.is_bt() ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex}; + + serialize(ja, tx); + auto dumped = std::move(ja).json(); + for (const auto& [k, v] : dumped.items()) { + LOG_PRINT_L0("tx details has k= " << k); + } + e.update(dumped); + } + + if (extra) + e["extra"] = std::move(*extra); + else + e.erase("extra"); auto ptx_it = found_in_pool.find(tx_hash); bool in_pool = ptx_it != found_in_pool.end(); - e["in_pool"] = in_pool; auto height = std::numeric_limits::max(); if (uint64_t fee, burned; get_tx_miner_fee(tx, fee, hf_version >= feature::FEE_BURNING, &burned)) { @@ -1037,6 +1065,7 @@ namespace cryptonote::rpc { if (in_pool) { const auto& meta = ptx_it->second.meta; + e["in_pool"] = true; e["weight"] = meta.weight; e["relayed"] = (bool) ptx_it->second.meta.relayed; e["received_timestamp"] = ptx_it->second.meta.receive_time; diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index b62be9b75a8..eb117f49de7 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -241,8 +241,8 @@ namespace cryptonote::rpc { struct request { bool flashed_txs_only; // Optional: If true only transactions that were sent via flash and approved are queried. - bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using LMQ RPC. - crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using LMQ RPC. + bool long_poll; // Optional: If true, this call is blocking until timeout OR tx pool has changed since the last query. TX pool change is detected by comparing the hash of all the hashes in the tx pool. Ignored when using OMQ RPC. + crypto::hash tx_pool_checksum; // Optional: If `long_poll` is true the caller must pass the hashes of all their known tx pool hashes, XOR'ed together. Ignored when using OMQ RPC. KV_MAP_SERIALIZABLE }; diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index cee0dcc1472..236c130d7f2 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -114,20 +114,15 @@ namespace cryptonote::rpc { if (auto it = json_in->find("txs_hashes"); it != json_in->end()) (*json_in)["tx_hashes"] = std::move(*it); - std::optional data; get_values(in, - "data", data, + "data", get.request.data, "memory_pool", get.request.memory_pool, "prune", get.request.prune, "split", get.request.split, "tx_extra", get.request.tx_extra, + "tx_extra_raw", get.request.tx_extra_raw, "tx_hashes", get.request.tx_hashes); - if (data) - get.request.data = *data; - else - get.request.data = !(get.request.prune || get.request.split); - if (get.request.memory_pool && !get.request.tx_hashes.empty()) throw std::runtime_error{"Error: 'memory_pool' and 'tx_hashes' are mutually exclusive"}; } diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index c384f2e3835..60a39b88bb5 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -56,8 +56,8 @@ namespace cryptonote::rpc { void RPC_COMMAND::set_bt() { bt = true; - response_b64.format = json_binary_proxy::fmt::bt; - response_hex.format = json_binary_proxy::fmt::bt; + response_b64.format = tools::json_binary_proxy::fmt::bt; + response_hex.format = tools::json_binary_proxy::fmt::bt; } void to_json(nlohmann::json& j, const block_header_response& h) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1f362d1248b..06fba552459 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -45,7 +45,6 @@ // setlocal comments+=:// #include "rpc/common/rpc_version.h" -#include "rpc/common/rpc_binary.h" #include "rpc/common/command_decorators.h" #include "crypto/crypto.h" @@ -194,7 +193,7 @@ namespace cryptonote::rpc { /// - `double_spend_seen` -- set to true if one or more outputs in this mempool transaction /// have already been spent (and thus the tx cannot currently be added to the blockchain). /// - `data` -- Full, unpruned transaction data. For a json request this is hex-encoded; for a - /// bt-encoded request this is raw bytes. This field is omitted if any of `decode_as_json`, + /// bt-encoded request this is raw bytes. This field is omitted if any of `tx_extra`, /// `split`, or `prune` is requested; or if the transaction has been pruned in the database. /// - `pruned` -- The non-prunable part of the transaction, encoded as hex (for json requests). /// Always included if `split` or `prune` are specified; without those options it will be diff --git a/src/rpc/http_server.cpp b/src/rpc/http_server.cpp index 8b899d6621f..8abe7d24816 100755 --- a/src/rpc/http_server.cpp +++ b/src/rpc/http_server.cpp @@ -90,7 +90,7 @@ namespace cryptonote::rpc { : m_server{server}, m_restricted{restricted} { // uWS is designed to work from a single thread, which is good (we pull off the requests and - // then stick them into the LMQ job queue to be scheduled along with other jobs). But as a + // then stick them into the OMQ job queue to be scheduled along with other jobs). But as a // consequence, we need to create everything inside that thread. We *also* need to get the // (thread local) event loop pointer back from the thread so that we can shut it down later // (injecting a callback into it is one of the few thread-safe things we can do across threads). @@ -271,7 +271,7 @@ namespace cryptonote::rpc { void invoke_txpool_hashes_bin(std::shared_ptr data); - // Invokes the actual RPC request; this is called (via lokimq) from some random LMQ worker thread, + // Invokes the actual RPC request; this is called (via lokimq) from some random OMQ worker thread, // which means we can't just write our reply; instead we have to post it to the uWS loop. void invoke_rpc(std::shared_ptr dataptr) { @@ -566,7 +566,7 @@ namespace cryptonote::rpc { auto& omq = data->core_rpc.get_core().get_omq(); std::string cat{data->call->is_public ? "rpc" : "admin"}; - std::string cmd{"jsonrpc:" + *method}; // Used for LMQ job logging; prefixed with jsonrpc: so we can distinguish it + std::string cmd{"jsonrpc:" + *method}; // Used for OMQ job logging; prefixed with jsonrpc: so we can distinguish it std::string remote{data->request.context.remote}; omq.inject_task(std::move(cat), std::move(cmd), std::move(remote), [data=std::move(data)] { invoke_rpc(std::move(data)); }); }); diff --git a/src/rpc/lmq_server.cpp b/src/rpc/omq_server.cpp similarity index 95% rename from src/rpc/lmq_server.cpp rename to src/rpc/omq_server.cpp index d0c9ff44525..2e84dcdfb39 100755 --- a/src/rpc/lmq_server.cpp +++ b/src/rpc/omq_server.cpp @@ -1,12 +1,11 @@ -#include "lmq_server.h" +#include "omq_server.h" #include "rpc/common/param_parser.hpp" #include "cryptonote_config.h" #include "oxenmq/oxenmq.h" #include "oxenc/bt.h" #include -// FIXME: Rename this to omq_server.{h,cpp} #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc" @@ -52,7 +51,7 @@ const command_line::arg_descriptor> arg_omq_local_contr #ifndef _WIN32 const command_line::arg_descriptor arg_omq_umask{ "lmq-umask", - "Sets the umask to apply to any listening ipc:///path/to/sock LMQ sockets, in octal.", + "Sets the umask to apply to any listening ipc:///path/to/sock OMQ sockets, in octal.", "0007"}; #endif @@ -70,21 +69,20 @@ auto as_x_pubkeys(const std::vector& pk_strings) { pks.reserve(pk_strings.size()); for (const auto& pkstr : pk_strings) { if (pkstr.size() != 64 || !oxenc::is_hex(pkstr)) - throw std::runtime_error("Invalid LMQ login pubkey: '" + pkstr + "'; expected 64-char hex pubkey"); + throw std::runtime_error("Invalid OMQ login pubkey: '" + pkstr + "'; expected 64-char hex pubkey"); pks.emplace_back(); oxenc::to_hex(pkstr.begin(), pkstr.end(), reinterpret_cast(&pks.back())); } return pks; } -// LMQ RPC responses consist of [CODE, DATA] for code we (partially) mimic HTTP error codes: 200 +// OMQ RPC responses consist of [CODE, DATA] for code we (partially) mimic HTTP error codes: 200 // means success, anything else means failure. (We don't have codes for Forbidden or Not Found -// because those happen at the LMQ protocol layer). +// because those happen at the OMQ protocol layer). constexpr std::string_view - LMQ_OK{"200"sv}, - LMQ_BAD_REQUEST{"400"sv}, - LMQ_ERROR{"500"sv}; - + OMQ_OK{"200"sv}, + OMQ_BAD_REQUEST{"400"sv}, + OMQ_ERROR{"500"sv}; } // end anonymous namespace @@ -111,21 +109,21 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // the quorumnet listener set up in cryptonote_core). for (const auto &addr : command_line::get_arg(vm, arg_omq_public)) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (public unencrypted)"); + MGINFO("OMQ listening on " << addr << " (public unencrypted)"); omq.listen_plain(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::basic); }); } for (const auto &addr : command_line::get_arg(vm, arg_omq_curve_public)) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (public curve)"); + MGINFO("OMQ listening on " << addr << " (public curve)"); omq.listen_curve(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::basic); }); } for (const auto &addr : command_line::get_arg(vm, arg_omq_curve)) { check_omq_listen_addr(addr); - MGINFO("LMQ listening on " << addr << " (curve restricted)"); + MGINFO("OMQ listening on " << addr << " (curve restricted)"); omq.listen_curve(addr, [&core](std::string_view ip, std::string_view pk, bool /*mn*/) { return core.omq_allow(ip, pk, AuthLevel::denied); }); } @@ -195,7 +193,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog omq.add_request_command(cmd.second->is_public ? "rpc" : "admin", cmd.first, [name=std::string_view{cmd.first}, &call=*cmd.second, this](oxenmq::Message& m) { if (m.data.size() > 1) - m.send_reply(LMQ_BAD_REQUEST, "Bad request: RPC commands must have at most one data part " + m.send_reply(OMQ_BAD_REQUEST, "Bad request: RPC commands must have at most one data part " "(received " + std::to_string(m.data.size()) + ")"); rpc_request request{}; @@ -217,7 +215,7 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog return std::move(v); } }, call.invoke(std::move(request), rpc_)); - m.send_reply(LMQ_OK, std::move(result)); + m.send_reply(OMQ_OK, std::move(result)); return; } catch (const parse_error& e) { // This isn't really WARNable as it's the client fault; log at info level instead. @@ -226,25 +224,25 @@ omq_rpc::omq_rpc(cryptonote::core& core, core_rpc_server& rpc, const boost::prog // warnings that get generated deep inside epee, for example when passing a string or // number instead of a JSON object. If you want to find some, `grep number2 epee` (for // real). - MINFO("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); + MINFO("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' called with invalid/unparseable data: " << e.what()); MDEBUG("Bad request body:" << m.data.empty() ? "(empty)" : m.data[0]); - m.send_reply(LMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); + m.send_reply(OMQ_BAD_REQUEST, "Unable to parse request: "s + e.what()); return; } catch (const rpc_error& e) { - MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' failed with: " << e.what()); - m.send_reply(LMQ_ERROR, e.what()); + MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' failed with: " << e.what()); + m.send_reply(OMQ_ERROR, e.what()); return; } catch (const std::exception& e) { - MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " + MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " "raised an exception: " << e.what()); } catch (...) { - MWARNING("LMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " + MWARNING("OMQ RPC request '" << (call.is_public ? "rpc." : "admin.") << name << "' " "raised an unknown exception"); } // Don't include the exception message in case it contains something that we don't want go // back to the user. If we want to support it eventually we could add some sort of // `rpc::user_visible_exception` that carries a message to send back to the user. - m.send_reply(LMQ_ERROR, "An exception occured while processing your request"); + m.send_reply(OMQ_ERROR, "An exception occured while processing your request"); }); } diff --git a/src/rpc/lmq_server.h b/src/rpc/omq_server.h similarity index 97% rename from src/rpc/lmq_server.h rename to src/rpc/omq_server.h index 498f1e5e4c4..f21838fe835 100755 --- a/src/rpc/lmq_server.h +++ b/src/rpc/omq_server.h @@ -40,7 +40,7 @@ namespace cryptonote::rpc { void init_omq_options(boost::program_options::options_description& desc); /** - * LMQ RPC server class. This doesn't actually hold the OxenMQ instance--that's in + * OMQ RPC server class. This doesn't actually hold the OxenMQ instance--that's in * cryptonote_core--but it works with it to add RPC endpoints, make it listen on RPC ports, and * handles RPC requests. */ diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 0b205114d3c..726fc2799ef 100755 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -57,27 +57,6 @@ static_assert(-1 == ~0, "Non 2s-complement architecture not supported!"); using binary_variant_tag_type = uint8_t; -// RAII class for `begin_array()`. This particular implementation is a no-op. -template -struct binary_archive_nested_array { - Archive& ar; - - // Call before writing an element to add a delimiter. (For binary_archive this is a no-op). - // Returns the archive itself, allowing you to write: - // - // auto arr = ar.begin_array(); - // for (auto& val : whatever) - // value(arr.element(), val); - // - Archive& element() { return ar; } - ~binary_archive_nested_array() {} // Explicitly empty constructor to silent unused variable warnings -}; - -// Do-nothing object for the RAII `begin_object` interface. -struct binary_archive_nested_object { - ~binary_archive_nested_object() {} // As above. -}; - /* \struct binary_unarchiver * * \brief the deserializer class for a binary archive @@ -132,23 +111,28 @@ class binary_unarchiver : public deserializer throw std::runtime_error{"deserialization of varint failed"}; } + // RAII class for `begin_array()`/`begin_object()`. This particular implementation is a no-op. + struct nested { + ~nested() {}; // Avoids unused variable warnings + }; + // Reads array size into s and returns an RAII object to help delimit and end it. - [[nodiscard]] binary_archive_nested_array begin_array(size_t& s) + [[nodiscard]] nested begin_array(size_t& s) { serialize_varint(s); - return {*this}; + return {}; } // Begins a sizeless array (this requires that the size is provided by some other means). - [[nodiscard]] binary_archive_nested_array begin_array() + [[nodiscard]] nested begin_array() { - return {*this}; + return {}; } // Does nothing. (This is used for tag annotations for archivers such as json) void tag(std::string_view) { } - [[nodiscard]] binary_archive_nested_object begin_object() { return {}; } + [[nodiscard]] nested begin_object() { return {}; } void read_variant_tag(binary_variant_tag_type &t) { serialize_int(t); @@ -232,24 +216,29 @@ class binary_archiver : public serializer tools::write_varint(std::ostreambuf_iterator{stream_}, v); } + // RAII class for `begin_array()`/`begin_object()`. This particular implementation is a no-op. + struct nested { + ~nested() {}; // Avoids unused variable warnings + }; + // Begins an array and returns an RAII object that is used to delimit array elements. For // binary_archiver the size is written when the array begins, and the RAII is a no-op. - [[nodiscard]] binary_archive_nested_array begin_array(size_t& s) + [[nodiscard]] nested begin_array(size_t& s) { serialize_varint(s); - return {*this}; + return {}; } // Begins a sizeless array. (Typically requires that size be stored some other way). - [[nodiscard]] binary_archive_nested_array begin_array() + [[nodiscard]] nested begin_array() { - return {*this}; + return {}; } // Does nothing. (This is used for tag annotations for archivers such as json) void tag(std::string_view) { } - [[nodiscard]] binary_archive_nested_object begin_object() { return {}; } + [[nodiscard]] nested begin_object() { return {}; } void write_variant_tag(binary_variant_tag_type t) { serialize_int(t); } diff --git a/src/serialization/container.h b/src/serialization/container.h index 4d32f903c7b..68abf394595 100755 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -101,7 +101,6 @@ void serialize_container(Archive& ar, C& v) static_assert(detail::has_emplace_back || detail::has_value_insert, "Unsupported container type"); for (size_t i = 0; i < cnt; i++) { - arr.element(); if constexpr (detail::has_emplace_back) detail::serialize_container_element(ar, v.emplace_back()); else { @@ -119,7 +118,7 @@ void serialize_container(Archive& ar, C& v) size_t cnt = v.size(); auto arr = ar.begin_array(cnt); for (auto& e : v) - serialize_container_element(arr.element(), e); + serialize_container_element(ar, e); } } // namespace detail diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index ae05435878d..b51d57ff505 100755 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -1,5 +1,5 @@ // Copyright (c) 2018-2020, The Beldex Project -// Copyright (c) 2014-2019, The Monero Project +// Copyright (c) 2014-2022, The Monero Project // // All rights reserved. // @@ -38,19 +38,17 @@ #include "serialization.h" #include "base.h" +#include "common/json_binary_proxy.h" #include -#include -#include #include -#include +#include +#include namespace serialization { -using json_variant_tag_type = std::string_view; - /*! \struct json_archiver * - * \brief a archive using the JSON standard + * \brief serialize data to JSON via nlohmann::json * * \detailed there is no deserializing counterpart; we only support JSON serializing here. */ @@ -58,151 +56,112 @@ struct json_archiver : public serializer { using variant_tag_type = std::string_view; - json_archiver(std::ostream& s, bool indent = false) - : stream_{s}, indent_{indent} - { - exc_restore_ = stream_.exceptions(); - stream_.exceptions(std::istream::badbit | std::istream::failbit | std::istream::eofbit); - } + explicit json_archiver(tools::json_binary_proxy::fmt bin_format = tools::json_binary_proxy::fmt::hex) + : bin_format_{bin_format} {} - ~json_archiver() { stream_.exceptions(exc_restore_); } + /// Returns the current nlohmann::json. + const nlohmann::json& json() const& { return top_; } + nlohmann::json&& json() && { return std::move(top_); } - void tag(std::string_view tag) { - if (!object_begin) - stream_ << (indent_ ? ", "sv : ","sv); - make_indent(); - stream_ << '"' << tag << (indent_ ? "\": "sv : "\":"); + /// Dumps the current nlohmann::json; arguments are forwarded to nlohmann::json::dump() + template + auto dump(T&&... args) const { + return top_.dump(std::forward(args)...); + } - object_begin = false; + // Sets the tag for the next object value we will write. + void tag(std::string_view tag) { + tag_ = tag; } - struct nested_object { + struct nested_value { json_archiver& ar; - ~nested_object() { - --ar.depth_; - ar.make_indent(); - ar.stream_ << '}'; + ~nested_value() { + assert(ar.stack_.size() >= 2); + ar.stack_.pop_back(); } - nested_object(const nested_object&) = delete; - nested_object& operator=(const nested_object&) = delete; - nested_object(nested_object&&) = delete; - nested_object& operator=(nested_object&&) = delete; + nested_value(const nested_value&) = delete; + nested_value& operator=(const nested_value&) = delete; + nested_value(nested_value&&) = delete; + nested_value& operator=(nested_value&&) = delete; }; - [[nodiscard]] nested_object begin_object() + [[nodiscard]] nested_value begin_object() { - stream_ << '{'; - ++depth_; - object_begin = true; - return nested_object{*this}; + stack_.emplace_back(set(nlohmann::json::object())); + return {*this}; } - template - static auto promote_to_printable_integer_type(T v) + [[nodiscard]] nested_value begin_array(size_t s=0) { - // Unary operator '+' performs integral promotion on type T [expr.unary.op]. - // If T is signed or unsigned char, it's promoted to int and printed as number. - return +v; + stack_.emplace_back(set(nlohmann::json::array())); + return {*this}; } template void serialize_int(T v) { - stream_ << std::dec << promote_to_printable_integer_type(v); - } - - void serialize_blob(void *buf, size_t len, std::string_view delimiter="\""sv) { - stream_ << delimiter; - auto* begin = static_cast(buf); - oxenc::to_hex(begin, begin + len, std::ostreambuf_iterator{stream_}); - stream_ << delimiter; - } - - template - void serialize_blobs(const std::vector& blobs, std::string_view delimiter="\""sv) { - serialize_blob(blobs.data(), blobs.size()*sizeof(T), delimiter); + set(v); } template void serialize_varint(T &v) { - stream_ << std::dec << promote_to_printable_integer_type(v); + serialize_int(v); } - struct nested_array { - json_archiver& ar; - int exc_count = std::uncaught_exceptions(); - bool first = true; - - // Call before writing an element to add a delimiter. The first element() call adds no - // delimiter. Returns the archive itself, allowing you to write: - // - // auto arr = ar.begin_array(); - // for (auto& val : whatever) - // value(arr.element(), val); - // - json_archiver& element() { - if (first) first = false; - else ar.delimit_array(); - return ar; - } - - ~nested_array() noexcept(false) { - if (std::uncaught_exceptions() == exc_count) { // Normal destruction - --ar.depth_; - if (ar.inner_array_contents_) - ar.make_indent(); - ar.stream_ << ']'; - } - // else we're destructing during a stack unwind so some other serialization failed, thus don't - // try terminating the array (since it might *also* throw if an IO error occurs). - } - - // Non-copyable, non-moveable - nested_array(const nested_array&) = delete; - nested_array& operator=(const nested_array&) = delete; - nested_array(nested_array&&) = delete; - nested_array& operator=(nested_array&&) = delete; - }; - - // Begins an array and returns an RAII object that is used to delimit array elements and - // terminates the array on destruction. - [[nodiscard]] nested_array begin_array(size_t s=0) - { - inner_array_contents_ = s > 0; - ++depth_; - stream_ << '['; - return {*this}; + void serialize_blob(const void *buf, size_t len) { + nlohmann::json val; + tools::json_binary_proxy{val, bin_format_} = std::string_view{static_cast(buf), len}; + set(std::move(val)); } - void delimit_array() { stream_ << (indent_ ? ", "sv : ","sv); } + template + void serialize_blobs(const std::vector& blobs) { + serialize_blob(blobs.data(), blobs.size()*sizeof(T)); + } void write_variant_tag(std::string_view t) { tag(t); } - // Returns the current position (i.e. stream.tellp()) of the output stream. - unsigned int streampos() { return static_cast(stream_.tellp()); } - private: - static constexpr std::string_view indents{" "}; - void make_indent() - { - if (indent_) - { - stream_ << '\n'; - auto in = 2 * depth_; - for (; in > indents.size(); in -= indents.size()) - stream_ << indents; - stream_ << indents.substr(0, in); + nlohmann::json& curr() { + if (stack_.empty()) + return top_; + else + return stack_.back(); + } + + template + nlohmann::json& set(T&& val) { + auto& c = curr(); + if (stack_.empty()) { + c = std::forward(val); + return c; + } + if (c.is_array()) { + c.push_back(std::forward(val)); + return c.back(); } + return (c[tag_] = std::forward(val)); } - std::ostream& stream_; - std::ios_base::iostate exc_restore_; - bool indent_ = false; - bool object_begin = false; - bool inner_array_contents_ = false; - size_t depth_ = 0; + nlohmann::json top_; + std::vector> stack_{}; + tools::json_binary_proxy::fmt bin_format_; + std::string tag_; }; + +/*! serializes the data in v to a string. Throws on error. +*/ +template +std::string dump_json(T& v, int indent = -1) +{ + json_archiver oar; + serialize(oar, v); + return oar.dump(indent); +} + + } // namespace serialization diff --git a/src/serialization/json_utils.h b/src/serialization/json_utils.h deleted file mode 100755 index 82b4eb26141..00000000000 --- a/src/serialization/json_utils.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2014-2019, The Monero Project -// -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are -// permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other -// materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL -// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - -#include -#include "json_archive.h" - -namespace serialization { - -/// Subclass of json_archiver that writes to a std::ostringstream and returns the string on -/// demand. -class json_string_archiver : public json_archiver { - std::ostringstream oss; -public: - /// Constructor; takes no arguments. - json_string_archiver() : json_archiver{oss} {} - - /// Returns the string from the std::ostringstream - std::string str() { return oss.str(); } -}; - -/*! serializes the data in v to a string. Throws on error. -*/ -template -std::string dump_json(T& v) -{ - json_string_archiver oar; - serialize(oar, v); - return oar.str(); -} - -} // namespace serialization diff --git a/src/serialization/pair.h b/src/serialization/pair.h index 8f02e97a0e4..9c63c70110e 100755 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -58,10 +58,9 @@ void serialize_value(Archive& ar, std::pair& p) if (!Archive::is_serializer && cnt != 2) throw std::runtime_error("Serialization failed: expected pair, found " + std::to_string(cnt) + " values"); - detail::serialize_pair_element(arr.element(), p.first); - detail::serialize_pair_element(arr.element(), p.second); + detail::serialize_pair_element(ar, p.first); + detail::serialize_pair_element(ar, p.second); - ar.end_array(); } } From 2d2839e2b00b7d91462303edbef66e6bf24252d9 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 6 May 2025 13:07:52 +0530 Subject: [PATCH 128/182] wallet_api updated to use new json_rpc --- src/wallet/api/wallet.cpp | 146 ++++++++++++++---------------- src/wallet/api/wallet_manager.cpp | 31 +++---- 2 files changed, 77 insertions(+), 100 deletions(-) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 6f9af7a1195..d4396f6b6c6 100755 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1066,28 +1066,24 @@ int WalletImpl::countBns() { clearStatus(); auto w = wallet(); - std::vector requests(1); - int count=0; + + nlohmann::json req_params{ + {"entries", {}} + }; for (uint32_t index = 0; index < w->get_num_subaddresses(0); ++index) { - if (requests.back().entries.size() >= cryptonote::rpc::BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES) - requests.emplace_back(); - requests.back().entries.push_back(w->get_subaddress_as_str({0, index})); + req_params["entries"].push_back(w->get_subaddress_as_str({0, index})); } - for (auto const &request : requests) + auto [success, result] = w->bns_owners_to_names(req_params); + if (!success) { - auto [success, result] = w->bns_owners_to_names(request); - if (!success) - { - LOG_PRINT_L1(__FUNCTION__ << "Connection to daemon failed when requesting BNS names"); - setStatusError(tr("Connection to daemon failed when requesting BNS names")); - break; - } - count += result.size(); - } - return count; + LOG_PRINT_L1(__FUNCTION__ << "Connection to daemon failed when requesting BNS names"); + setStatusError(tr("Connection to daemon failed when requesting BNS names")); + } + + return result["entries"].size(); } EXPORT @@ -2285,89 +2281,79 @@ std::vector* WalletImpl::MyBns() const auto w = wallet(); - std::vector> rpc_results; - std::vector requests(1); + nlohmann::json req_params{ + {"entries", {}} + }; std::unordered_map cache = w->get_bns_cache(); for (uint32_t index = 0; index < w->get_num_subaddresses(0); ++index) { - if (requests.back().entries.size() >= cryptonote::rpc::BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES) - requests.emplace_back(); - requests.back().entries.push_back(w->get_subaddress_as_str({0, index})); + req_params["entries"].push_back(w->get_subaddress_as_str({0, index})); } - rpc_results.reserve(requests.size()); - for (auto const &request : requests) + auto [success, result] = w->bns_owners_to_names(req_params); + if (!success) { - auto [success, result] = w->bns_owners_to_names(request); - if (!success) - { - setStatusError(tr("Connection to daemon failed when requesting BNS names")); - } - rpc_results.emplace_back(std::move(result)); + setStatusError(tr("Connection to daemon failed when requesting BNS names")); } auto nettype = w->nettype(); - for (size_t i = 0; i < rpc_results.size(); i++) + + for (auto const &entry : result["entries"]) { - auto const &rpc = rpc_results[i]; - for (auto const &entry : rpc) + std::string_view name; + std::string value_bchat, value_wallet, value_belnet, value_eth_addr; + if (auto got = cache.find(entry["name_hash"]); got != cache.end()) { - std::string_view name; - std::string value_bchat, value_wallet, value_belnet, value_eth_addr; - if (auto got = cache.find(entry.name_hash); got != cache.end()) + name = got->second.name; + // BCHAT { - name = got->second.name; - //BCHAT - { - bns::mapping_value mv; - const auto type = bns::mapping_type::bchat; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_bchat_value), &mv) - && mv.decrypt(name, type)) + bns::mapping_value mv; + const auto type = bns::mapping_type::bchat; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_bchat_value"].get()), &mv) && mv.decrypt(name, type)) value_bchat = mv.to_readable_value(nettype, type); - } - //ETH_ADDRESS - { - bns::mapping_value mv; - const auto type = bns::mapping_type::eth_addr; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_eth_addr_value), &mv) - && mv.decrypt(name, type)) + } + // ETH_ADDRESS + { + bns::mapping_value mv; + const auto type = bns::mapping_type::eth_addr; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_eth_addr_value"].get()), &mv) && mv.decrypt(name, type)) value_eth_addr = mv.to_readable_value(nettype, type); - } - //WALLET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::wallet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_wallet_value), &mv) - && mv.decrypt(name, type)) - value_wallet = mv.to_readable_value(nettype,type); - } - //BELNET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::belnet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry.encrypted_belnet_value), &mv) - && mv.decrypt(name, type)) + } + // WALLET + { + bns::mapping_value mv; + const auto type = bns::mapping_type::wallet; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_wallet_value"].get()), &mv) && mv.decrypt(name, type)) + value_wallet = mv.to_readable_value(nettype, type); + } + // BELNET + { + bns::mapping_value mv; + const auto type = bns::mapping_type::belnet; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_belnet_value"].get()), &mv) && mv.decrypt(name, type)) value_belnet = mv.to_readable_value(nettype, type); - } } - auto &info = my_bns->emplace_back(); - info.name_hash = entry.name_hash; - info.name = name.empty() ? "(none)" : std::string(name); - info.value_bchat = value_bchat.empty() ? "(none)" : value_bchat; - info.value_wallet = value_wallet.empty() ? "(none)" : value_wallet; - info.value_belnet = value_belnet.empty() ? "(none)" : value_belnet; - info.value_eth_addr = value_eth_addr.empty() ? "(none)" : value_eth_addr; - info.owner = entry.owner; - info.backup_owner = entry.backup_owner? *entry.backup_owner : "(none)"; - info.update_height = entry.update_height; - info.expiration_height = entry.expiration_height ? *entry.expiration_height : 0; - info.encrypted_bchat_value = entry.encrypted_bchat_value.empty() ? "(none)" : entry.encrypted_bchat_value; - info.encrypted_wallet_value = entry.encrypted_wallet_value.empty() ? "(none)" : entry.encrypted_wallet_value; - info.encrypted_belnet_value = entry.encrypted_belnet_value.empty() ? "(none)" : entry.encrypted_belnet_value; - info.encrypted_eth_addr_value = entry.encrypted_eth_addr_value.empty() ? "(none)" : entry.encrypted_eth_addr_value; } + auto &info = my_bns->emplace_back(); + info.name_hash = entry["name_hash"]; + info.name = name.empty() ? "(none)" : std::string(name); + info.value_bchat = value_bchat.empty() ? "(none)" : value_bchat; + info.value_wallet = value_wallet.empty() ? "(none)" : value_wallet; + info.value_belnet = value_belnet.empty() ? "(none)" : value_belnet; + info.value_eth_addr = value_eth_addr.empty() ? "(none)" : value_eth_addr; + info.owner = entry["owner"]; + if (auto got = entry.find("backup_owner"); got != entry.end()) + info.backup_owner = entry["backup_owner"]; + else + info.backup_owner = "(none)"; + info.update_height = entry["update_height"]; + info.expiration_height = entry["expiration_height"]; + info.encrypted_bchat_value = entry["encrypted_bchat_value"].empty() ? "(none)" : entry["encrypted_bchat_value"]; + info.encrypted_wallet_value = entry["encrypted_wallet_value"].empty() ? "(none)" : entry["encrypted_wallet_value"]; + info.encrypted_belnet_value = entry["encrypted_belnet_value"].empty() ? "(none)" : entry["encrypted_belnet_value"]; + info.encrypted_eth_addr_value = entry["encrypted_eth_addr_value"].empty() ? "(none)" : entry["encrypted_eth_addr_value"]; } return my_bns; } diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 010782eff98..66b623d3c49 100755 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -220,30 +220,21 @@ void WalletManagerImpl::setDaemonAddress(std::string address) } EXPORT -bool WalletManagerImpl::connected(uint32_t *version) -{ +bool WalletManagerImpl::connected(uint32_t* version) { using namespace cryptonote::rpc; try { - auto res = m_http_client.json_rpc(GET_VERSION::names()[0], {}); - if (version) *version = res.version; + auto res = m_http_client.json_rpc("get_version"); + if (version) + *version = res["version"]; return true; - } catch (...) {} + } catch (...) { + } return false; } -template -static std::optional json_rpc(cryptonote::rpc::http_client& http, const typename RPC::request& req = {}) -{ - using namespace cryptonote::rpc; - try { return http.json_rpc(RPC::names()[0], req); } - catch (...) {} - return std::nullopt; -} - -static std::optional get_info(cryptonote::rpc::http_client& http) -{ - return json_rpc(http); +static nlohmann::json get_info(cryptonote::rpc::http_client& http) { + return http.json_rpc("get_info"); } @@ -251,7 +242,7 @@ EXPORT uint64_t WalletManagerImpl::blockchainHeight() { auto res = get_info(m_http_client); - return res ? res->height : 0; + return res ? res["height"].get() : 0; } EXPORT @@ -260,14 +251,14 @@ uint64_t WalletManagerImpl::blockchainTargetHeight() auto res = get_info(m_http_client); if (!res) return 0; - return std::max(res->target_height, res->height); + return std::max(res["target_height"].get(), res["height"].get()); } EXPORT uint64_t WalletManagerImpl::blockTarget() { auto res = get_info(m_http_client); - return res ? res->target : 0; + return res ? res["target"].get() : 0; } ///////////////////// WalletManagerFactory implementation ////////////////////// From 03075114982bb2de6d9acb31f6e242164d0ed736 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Tue, 6 May 2025 16:00:26 +0530 Subject: [PATCH 129/182] RPC: BNS_LOOKUP, BNS_VALUE_DECRYPT are updated to new format --- src/rpc/core_rpc_server.cpp | 133 ++++++++++----------- src/rpc/core_rpc_server.h | 8 +- src/rpc/core_rpc_server_command_parser.cpp | 36 ++++-- src/rpc/core_rpc_server_command_parser.h | 8 +- src/rpc/core_rpc_server_commands_defs.cpp | 48 ++++---- src/rpc/core_rpc_server_commands_defs.h | 95 +++++++-------- 6 files changed, 167 insertions(+), 161 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index fd0d3f917da..82c99dc5526 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -460,6 +460,7 @@ namespace cryptonote::rpc { res.status = STATUS_OK; return res; } + //------------------------------------------------------------------------------------------------------------------------------ GET_ALT_BLOCKS_HASHES_BIN::response core_rpc_server::invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context) { GET_ALT_BLOCKS_HASHES_BIN::response res{}; @@ -3221,32 +3222,32 @@ namespace cryptonote::rpc { test_trigger_uptime_proof.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_context context) + void core_rpc_server::invoke(BNS_NAMES_TO_OWNERS& names_to_owners, rpc_context context) { if (!context.admin) { - check_quantity_limit(bns_names_to_owners.request.name_hash.size(), BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES); + check_quantity_limit(names_to_owners.request.name_hash.size(), BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES); } std::optional height = m_core.get_current_blockchain_height(); auto hf_version = get_network_version(nettype(), *height); - if (bns_names_to_owners.request.include_expired) height = std::nullopt; + if (names_to_owners.request.include_expired) height = std::nullopt; bns::name_system_db &db = m_core.get_blockchain_storage().name_system_db(); - for (size_t request_index = 0; request_index < bns_names_to_owners.request.name_hash.size(); request_index++) + for (size_t request_index = 0; request_index < names_to_owners.request.name_hash.size(); request_index++) { - const auto& request = bns_names_to_owners.request.name_hash[request_index]; + const auto& request = names_to_owners.request.name_hash[request_index]; // This also takes 32 raw bytes, but that is undocumented (because it is painful to pass // through json). - auto name_hash = bns::name_hash_input_to_base64(bns_names_to_owners.request.name_hash[request_index]); + auto name_hash = bns::name_hash_input_to_base64(names_to_owners.request.name_hash[request_index]); if (!name_hash) throw rpc_error{ERROR_WRONG_PARAM, "Invalid name_hash: expected hash as 64 hex digits or 43/44 base64 characters"}; std::vector records = db.get_mappings(*name_hash, height); for (auto const &record : records) { - auto& elem = bns_names_to_owners.response["result"].emplace_back(); + auto& elem = names_to_owners.response["result"].emplace_back(); elem["name_hash"] = record.name_hash; elem["owner"] = record.owner.to_string(nettype()); if (record.backup_owner) elem["backup_owner"] = record.backup_owner.to_string(nettype()); @@ -3259,72 +3260,22 @@ namespace cryptonote::rpc { elem["txid"] = tools::type_to_hex(record.txid); } } - bns_names_to_owners.response["status"] = STATUS_OK; - } - - //------------------------------------------------------------------------------------------------------------------------------ - BNS_LOOKUP::response core_rpc_server::invoke(BNS_LOOKUP::request&& req, rpc_context context) - { - BNS_LOOKUP::response res{}; - - std::string name = tools::lowercase_ascii_string(std::move(req.name)); - - BNS_NAMES_TO_OWNERS name_to_owner{}; - name_to_owner.request.name_hash.push_back(bns::name_to_base64_hash(name)); - invoke(name_to_owner, context); - - if(name_to_owner.response["result"].size() != 1){ - throw rpc_error{ERROR_INVALID_RESULT, "Invalid data returned from BNS_NAMES_TO_OWNERS"}; - } - - auto entries = name_to_owner.response["result"].back(); - { - res.name_hash = entries["name_hash"]; - res.owner = entries["owner"]; - if (!entries["backup_owner"].empty()) res.backup_owner = entries["backup_owner"]; - res.expiration_height = entries["expiration_height"]; - res.update_height = entries["update_height"]; - res.txid = entries["txid"]; - - if(!entries["encrypted_bchat_value"].empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "bchat", entries["encrypted_bchat_value"]}; - auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); - res.bchat_value = bns_value_decrypt_res.value; - } - if(!entries["encrypted_belnet_value"].empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "belnet", entries["encrypted_belnet_value"]}; - auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); - res.belnet_value = bns_value_decrypt_res.value; - } - if(!entries["encrypted_wallet_value"].empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "wallet", entries["encrypted_wallet_value"]}; - auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); - res.wallet_value = bns_value_decrypt_res.value; - } - if(!entries["encrypted_eth_addr_value"].empty()){ - BNS_VALUE_DECRYPT::request bns_value_decrypt_req{name, "eth_addr", entries["encrypted_eth_addr_value"]}; - auto bns_value_decrypt_res = invoke(std::move(bns_value_decrypt_req), context); - res.eth_addr_value = bns_value_decrypt_res.value; - } - } - - res.status = STATUS_OK; - return res; + names_to_owners.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_context context) + void core_rpc_server::invoke(BNS_OWNERS_TO_NAMES& owners_to_names, rpc_context context) { if (!context.admin) - check_quantity_limit(bns_owners_to_names.request.entries.size(), BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES); + check_quantity_limit(owners_to_names.request.entries.size(), BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES); std::unordered_map owner_to_request_index; std::vector owners; - owners.reserve(bns_owners_to_names.request.entries.size()); - for (size_t request_index = 0; request_index < bns_owners_to_names.request.entries.size(); request_index++) + owners.reserve(owners_to_names.request.entries.size()); + for (size_t request_index = 0; request_index < owners_to_names.request.entries.size(); request_index++) { - std::string const &owner = bns_owners_to_names.request.entries[request_index]; + std::string const &owner = owners_to_names.request.entries[request_index]; bns::generic_owner bns_owner = {}; std::string errmsg; if (!bns::parse_owner_to_generic_owner(m_core.get_nettype(), owner, bns_owner, &errmsg)) @@ -3340,7 +3291,7 @@ namespace cryptonote::rpc { bns::name_system_db &db = m_core.get_blockchain_storage().name_system_db(); std::optional height; - if (!bns_owners_to_names.request.include_expired) height = m_core.get_current_blockchain_height(); + if (!owners_to_names.request.include_expired) height = m_core.get_current_blockchain_height(); std::vector entries; std::vector records = db.get_mappings_by_owners(owners, height); @@ -3371,8 +3322,8 @@ namespace cryptonote::rpc { entry.txid = tools::type_to_hex(record.txid); } - bns_owners_to_names.response["entries"] = entries; - bns_owners_to_names.response["status"] = STATUS_OK; + owners_to_names.response["entries"] = entries; + owners_to_names.response["status"] = STATUS_OK; return; } @@ -3400,11 +3351,53 @@ namespace cryptonote::rpc { resolve.response_hex["nonce"] = nonce; } } + //------------------------------------------------------------------------------------------------------------------------------ - BNS_VALUE_DECRYPT::response core_rpc_server::invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context) + void core_rpc_server::invoke(BNS_LOOKUP& lookup, rpc_context context) { - BNS_VALUE_DECRYPT::response res{}; + std::string name = tools::lowercase_ascii_string(std::move(lookup.request.name)); + + BNS_NAMES_TO_OWNERS name_to_owner{}; + name_to_owner.request.name_hash.push_back(bns::name_to_base64_hash(name)); + invoke(name_to_owner, context); + + if(name_to_owner.response["result"].size() != 1){ + throw rpc_error{ERROR_INVALID_RESULT, "Invalid data returned from BNS_NAMES_TO_OWNERS"}; + } + + auto entries = name_to_owner.response["result"].back(); + { + lookup.response["name_hash"] = entries["name_hash"]; + lookup.response["owner"] = entries["owner"]; + if (!entries["backup_owner"].empty()) + lookup.response["backup_owner"] = entries["backup_owner"]; + lookup.response["expiration_height"] = entries["expiration_height"]; + lookup.response["update_height"] = entries["update_height"]; + lookup.response["txid"] = entries["txid"]; + + for (const auto& [type, key] : std::vector>{ + {"bchat", "encrypted_bchat_value"}, + {"belnet", "encrypted_belnet_value"}, + {"wallet", "encrypted_wallet_value"}, + {"eth_addr", "encrypted_eth_addr_value"}}) + { + if (!entries[key].empty()) { + BNS_VALUE_DECRYPT value_decrypt; + value_decrypt.request = {name, type, entries[key]}; + invoke(value_decrypt, context); + lookup.response[type + "_value"] = value_decrypt.response["value"]; + } + } + } + + lookup.response["status"] = STATUS_OK; + } + + //------------------------------------------------------------------------------------------------------------------------------ + void core_rpc_server::invoke(BNS_VALUE_DECRYPT& value_decrypt, rpc_context context) + { + auto& req = value_decrypt.request; // --------------------------------------------------------------------------------------------- // // Validate encrypted value @@ -3447,7 +3440,7 @@ namespace cryptonote::rpc { if (!value.decrypt(req.name, type)) throw rpc_error{ERROR_INTERNAL, "Value decryption failure"}; - res.value = value.to_readable_value(nettype(), type); - return res; + value_decrypt.response["value"] = value.to_readable_value(nettype(), type); + value_decrypt.response["status"] = STATUS_OK; } } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 3f55a820d3a..7844b09aaff 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -132,7 +132,6 @@ namespace cryptonote::rpc { // JSON & bt-encoded RPC endpoints void invoke(GET_HEIGHT& req, rpc_context context); void invoke(GET_INFO& info, rpc_context context); - void invoke(BNS_RESOLVE& resolve, rpc_context context); void invoke(GET_NET_STATS& get_net_stats, rpc_context context); void invoke(GET_OUTPUTS& get_outputs, rpc_context context); void invoke(HARD_FORK_INFO& hfinfo, rpc_context context); @@ -193,6 +192,11 @@ namespace cryptonote::rpc { void invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context); void invoke(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_context context); void invoke(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_context context); + void invoke(BNS_RESOLVE& resolve, rpc_context context); + void invoke(BNS_LOOKUP& lookup, rpc_context context); + void invoke(BNS_VALUE_DECRYPT& value_decrypt, rpc_context context); + + // Deprecated Monero NIH binary endpoints: GET_ALT_BLOCKS_HASHES_BIN::response invoke(GET_ALT_BLOCKS_HASHES_BIN::request&& req, rpc_context context); @@ -208,8 +212,6 @@ namespace cryptonote::rpc { // FIXME: unconverted JSON RPC endpoints: // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - BNS_LOOKUP::response invoke(BNS_LOOKUP::request&& req, rpc_context context); - BNS_VALUE_DECRYPT::response invoke(BNS_VALUE_DECRYPT::request&& req, rpc_context context); #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) void on_relay_uptime_and_votes() diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 236c130d7f2..20d824308e2 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -5,12 +5,6 @@ namespace cryptonote::rpc { using nlohmann::json; - void parse_request(BNS_RESOLVE& bns, rpc_input in) { - get_values(in, - "name_hash", required{bns.request.name_hash}, - "type", required{bns.request.type}); - } - void parse_request(GET_MASTER_NODES& mns, rpc_input in) { // Remember: key access must be in sorted order (even across get_values() calls). get_values(in, @@ -369,16 +363,34 @@ namespace cryptonote::rpc { "recent_cutoff", get_output_histogram.request.recent_cutoff); } - void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in) { + void parse_request(BNS_OWNERS_TO_NAMES& owners_to_names, rpc_input in) { + get_values(in, + "entries", required{owners_to_names.request.entries}, + "include_expired", owners_to_names.request.include_expired); + } + + void parse_request(BNS_NAMES_TO_OWNERS& names_to_owners, rpc_input in) { + get_values(in, + "name_hash", required{names_to_owners.request.name_hash}, + "include_expired", names_to_owners.request.include_expired); + } + + void parse_request(BNS_RESOLVE& resolve, rpc_input in) { + get_values(in, + "name_hash", required{resolve.request.name_hash}, + "type", required{resolve.request.type}); + } + + void parse_request(BNS_LOOKUP& lookup, rpc_input in) { get_values(in, - "entries", required{bns_owners_to_names.request.entries}, - "include_expired", bns_owners_to_names.request.include_expired); + "name", required{lookup.request.name}); } - void parse_request(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_input in) { + void parse_request(BNS_VALUE_DECRYPT& value_decrypt, rpc_input in) { get_values(in, - "name_hash", required{bns_names_to_owners.request.name_hash}, - "include_expired", bns_names_to_owners.request.include_expired); + "name", required{value_decrypt.request.name}, + "type", required{value_decrypt.request.type}, + "encrypted_value", required{value_decrypt.request.encrypted_value}); } void parse_request(GET_QUORUM_STATE& qs, rpc_input in) { diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index af2f145cd98..98f213be5e7 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -37,9 +37,11 @@ namespace cryptonote::rpc { void parse_request(IN_PEERS& in_peers, rpc_input in); void parse_request(IS_KEY_IMAGE_SPENT& spent, rpc_input in); void parse_request(BELNET_PING& belnet_ping, rpc_input in); - void parse_request(BNS_OWNERS_TO_NAMES& bns_owners_to_names, rpc_input in); - void parse_request(BNS_NAMES_TO_OWNERS& bns_names_to_owners, rpc_input in); - void parse_request(BNS_RESOLVE& bns, rpc_input in); + void parse_request(BNS_OWNERS_TO_NAMES& owners_to_names, rpc_input in); + void parse_request(BNS_NAMES_TO_OWNERS& names_to_owners, rpc_input in); + void parse_request(BNS_RESOLVE& resolve, rpc_input in); + void parse_request(BNS_LOOKUP& lookup, rpc_input in); + void parse_request(BNS_VALUE_DECRYPT& value_decrypt, rpc_input in); void parse_request(OUT_PEERS& out_peers, rpc_input in); void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 60a39b88bb5..daf6b8ffa77 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -1342,23 +1342,23 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE(status) // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_LOOKUP::request) - KV_SERIALIZE(name) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_LOOKUP::request) +// KV_SERIALIZE(name) +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_LOOKUP::response) - KV_SERIALIZE(name_hash) - KV_SERIALIZE(owner) - KV_SERIALIZE(backup_owner) - KV_SERIALIZE(bchat_value) - KV_SERIALIZE(wallet_value) - KV_SERIALIZE(belnet_value) - KV_SERIALIZE(eth_addr_value) - KV_SERIALIZE(update_height) - KV_SERIALIZE(expiration_height) - KV_SERIALIZE(txid) - KV_SERIALIZE(status) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_LOOKUP::response) +// KV_SERIALIZE(name_hash) +// KV_SERIALIZE(owner) +// KV_SERIALIZE(backup_owner) +// KV_SERIALIZE(bchat_value) +// KV_SERIALIZE(wallet_value) +// KV_SERIALIZE(belnet_value) +// KV_SERIALIZE(eth_addr_value) +// KV_SERIALIZE(update_height) +// KV_SERIALIZE(expiration_height) +// KV_SERIALIZE(txid) +// KV_SERIALIZE(status) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(BNS_OWNERS_TO_NAMES::request) // KV_SERIALIZE(entries) @@ -1398,15 +1398,15 @@ KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE(nonce) // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_VALUE_DECRYPT::request) - KV_SERIALIZE(name); - KV_SERIALIZE(type); - KV_SERIALIZE(encrypted_value); -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_VALUE_DECRYPT::request) +// KV_SERIALIZE(name); +// KV_SERIALIZE(type); +// KV_SERIALIZE(encrypted_value); +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(BNS_VALUE_DECRYPT::response) - KV_SERIALIZE(value) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(BNS_VALUE_DECRYPT::response) +// KV_SERIALIZE(value) +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(FLUSH_CACHE::request) // KV_SERIALIZE_OPT(bad_txs, false) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 06fba552459..157d06c1300 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2507,39 +2507,6 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Get the name mapping for a Beldex Name Service entry. Beldex currently supports mappings - // for Bchat and Belnet and wallet. - struct BNS_LOOKUP : PUBLIC - { - static constexpr auto names() { return NAMES("bns_lookup"); } - - static constexpr size_t MAX_REQUEST_ENTRIES = 256; - struct request - { - std::string name; // Entries to look up - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string name_hash; // The hash of the name that was queried, in base64 - std::string owner; // The public key that purchased the Beldex Name Service entry. - std::optional backup_owner; // The backup public key that the owner specified when purchasing the Beldex Name Service entry. Omitted if no backup owner. - std::optional bchat_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - std::optional wallet_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - std::optional belnet_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - std::optional eth_addr_value; // The encrypted value that the name maps to. See the `BNS_RESOLVE` description for information on how this value can be decrypted. - uint64_t update_height; // The last height that this Beldex Name Service entry was updated on the Blockchain. - std::optional expiration_height;// For records that expire, this will be set to the expiration block height. - std::string txid; // The txid of the mapping's most recent update or purchase. - - std::string status; // Generic RPC error code. "OK" is the success value. - - KV_MAP_SERIALIZABLE - }; - }; - /// RPC: bns/bns_owners_to_names /// /// Get all the name mappings for the queried owner. The owner can be either a ed25519 public key or Monero style @@ -2638,27 +2605,57 @@ namespace cryptonote::rpc { } request; }; - BELDEX_RPC_DOC_INTROSPECT - // Takes a BNS encrypted value and decrypts the mapping value using the BNS name. + /// Get the name mapping for a Beldex Name Service entry. Beldex currently supports mappings + /// for Bchat, Belnet, eth-address and wallet. + /// Inputs: + /// + /// - `name` -- bns-name for the lookup + /// + /// Output: + /// + /// - `status` -- Generic RPC error code. "OK" is the success value. + /// - `name_hash` -- The hash of the name that the owner purchased via Beldex Name Service in base64 + /// - `owner` -- The backup public key specified by the owner that purchased the Beldex Name Service entry. + /// - `backup_owner` -- The backup public key specified by the owner that purchased the Beldex Name Service entry. Omitted if no backup owner. + /// - `bchat_value` -- The decrypted Bchat value that the name maps to, in string. + /// - `wallet_value` -- The decrypted Wallet value that the name maps to, in string. + /// - `belnet_value` -- The decrypted Belnet value that the name maps to, in string. + /// - `eth_addr_value` -- The decrypted Eth Address value that the name maps to, in string. + /// - `update_height` -- The last height that this Beldex Name Service entry was updated on the Blockchain. + /// - `expiration_height` -- For records that expire, this will be set to the expiration block height. + /// - `txid` -- The txid of the mapping's most recent update or purchase. + struct BNS_LOOKUP : PUBLIC + { + static constexpr auto names() { return NAMES("bns_lookup"); } + + struct request_parameters + { + std::string name; // Entries to look up + }request; + + }; + + /// Takes a BNS encrypted value and decrypts the mapping value using the BNS name. + /// Inputs: + /// + /// - `name` -- The BNS name of the given encrypted value. + /// - `type` -- The mapping type: "bchat" or "belnet" or "wallet" or "eth_addr". + /// - `encrypted_value` -- // The encrypted value represented in hex. + /// Output: + /// + /// - `status` -- Generic RPC error code. "OK" is the success value. + /// - `value` -- The decrypted value that the name maps to, in string. + struct BNS_VALUE_DECRYPT : PUBLIC { static constexpr auto names() { return NAMES("bns_value_decrypt"); } - struct request + struct request_parameters { std::string name; // The BNS name of the given encrypted value. std::string type; // The mapping type: "bchat" or "belnet" or "wallet". std::string encrypted_value; // The encrypted value represented in hex. - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string value; // The value decrypted - - KV_MAP_SERIALIZABLE - }; + }request; }; /// RPC: daemon/flush_cache @@ -2735,6 +2732,8 @@ namespace cryptonote::rpc { BNS_OWNERS_TO_NAMES, BNS_NAMES_TO_OWNERS, BNS_RESOLVE, + BNS_LOOKUP, + BNS_VALUE_DECRYPT, OUT_PEERS, POP_BLOCKS, PRUNE_BLOCKCHAIN, @@ -2755,9 +2754,7 @@ namespace cryptonote::rpc { >; using FIXME_old_rpc_types = tools::type_list< RELAY_TX, - GET_OUTPUT_DISTRIBUTION, - BNS_LOOKUP, - BNS_VALUE_DECRYPT + GET_OUTPUT_DISTRIBUTION >; } // namespace cryptonote::rpc From 413b9f71e6d24a8b9ee8bfa050b3162b0cb05c61 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 7 May 2025 18:36:35 +0530 Subject: [PATCH 130/182] use fork rules fix This appears incorrect when applying fork rules not defined in the hardfork list. --- src/cryptonote_config.h | 4 ++-- src/cryptonote_core/blockchain.cpp | 6 +++--- src/cryptonote_core/cryptonote_tx_utils.cpp | 4 ++-- src/cryptonote_core/master_node_list.cpp | 9 +++++---- src/cryptonote_core/master_node_rules.h | 1 - src/cryptonote_core/master_node_voting.cpp | 4 ++-- src/rpc/http_client.cpp | 2 ++ src/wallet/tx_construction_data.h | 2 +- tests/core_tests/beldex_tests.cpp | 4 ++-- tests/core_tests/rct.cpp | 2 +- 10 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index dee5969bada..7e7969beb2b 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -219,8 +219,8 @@ namespace feature { constexpr auto PER_BYTE_FEE = hf::hf10_bulletproofs; constexpr auto SMALLER_BP = hf::hf11_infinite_staking; constexpr auto LONG_TERM_BLOCK_WEIGHT = hf::hf11_infinite_staking; - constexpr auto PER_OUTPUT_FEE = hf::hf14_enforce_checkpoints; - constexpr auto ED25519_KEY = hf::hf14_enforce_checkpoints; + constexpr auto PER_OUTPUT_FEE = hf::hf15_flash; + constexpr auto ED25519_KEY = hf::hf15_flash; constexpr auto FEE_BURNING = hf::hf15_flash; constexpr auto FLASH = hf::hf15_flash; constexpr auto REDUCE_FEE = hf::hf17_POS; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index c9cee347442..e0cec5ef068 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -370,7 +370,7 @@ bool Blockchain::load_missing_blocks_into_beldex_subsystems() checkpoint_t *checkpoint_ptr = nullptr; checkpoint_t checkpoint; - if (blk.major_version >= hf::hf14_enforce_checkpoints && get_checkpoint(block_height, checkpoint)) + if (blk.major_version >= hf::hf15_flash && get_checkpoint(block_height, checkpoint)) checkpoint_ptr = &checkpoint; try { @@ -2125,7 +2125,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id difficulty_type const main_chain_cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1); bool const alt_chain_has_greater_pow = alt_data.cumulative_difficulty > main_chain_cumulative_difficulty; - if (b.major_version >= hf::hf14_enforce_checkpoints) + if (b.major_version >= hf::hf15_flash) { if (alt_chain_has_more_checkpoints || (alt_chain_has_greater_pow && alt_chain_has_equal_checkpoints)) { @@ -4207,7 +4207,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block bool master_node_checkpoint = false; if(!m_checkpoints.check_block(chain_height, blk_hash, nullptr, &master_node_checkpoint)) { - if (!master_node_checkpoint || (master_node_checkpoint && blk.major_version >= hf::hf14_enforce_checkpoints)) + if (!master_node_checkpoint || (master_node_checkpoint && blk.major_version >= hf::hf15_flash)) { MGINFO_RED("CHECKPOINT VALIDATION FAILED"); return false; diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 7c4606fa9a4..d874d89a8d7 100755 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -508,7 +508,7 @@ namespace cryptonote // We base governance fees and MN rewards based on the block reward formula. (Prior to HF13, // however, they were accidentally based on the block reward formula *after* subtracting a // potential penalty if the block producer includes txes beyond the median size limit). - //result.original_base_reward = hard_fork_version >= hf::hf14_enforce_checkpoints ? base_reward_unpenalized : base_reward; + //result.original_base_reward = hard_fork_version >= hf::hf15_flash ? base_reward_unpenalized : base_reward; result.original_base_reward = base_reward; // There is a goverance fee due every block. Beginning in hardfork 10 this is still subtracted @@ -880,7 +880,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(key_image_proofs.proofs.size() >= 1, false, "No key image proofs were generated for staking tx"); add_tx_key_image_proofs_to_tx_extra(tx.extra, key_image_proofs); - if (tx_params.hf_version <= hf::hf14_enforce_checkpoints) + if (tx_params.hf_version <= hf::hf15_flash) tx.type = txtype::standard; } diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index f498378c43f..856ae513e43 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -713,7 +713,7 @@ namespace master_nodes info.last_decommission_reason_consensus_any = state_change.reason_consensus_any; info.decommission_count++; - if (hf_version >= hf::hf14_enforce_checkpoints) { + if (hf_version >= hf::hf15_flash) { // Assigning invalid swarm id effectively kicks the node off // its current swarm; it will be assigned a new swarm id when it // gets recommissioned. Prior to HF13 this step was incorrectly @@ -1487,7 +1487,7 @@ namespace master_nodes // // NOTE: Verify the checkpoint given on this height that locks in a block in the past. // - if (block.major_version >= hf::hf14_enforce_checkpoints && checkpoint) + if (block.major_version >= hf::hf15_flash && checkpoint) { std::vector> alt_quorums; std::shared_ptr quorum = get_quorum(quorum_type::checkpointing, checkpoint->height, false, alt_block ? &alt_quorums : nullptr); @@ -1550,7 +1550,7 @@ namespace master_nodes std::shared_ptr POS_quorum; std::vector> alt_POS_quorums; bool POS_hf = block.major_version >= hf::hf17_POS; - + if (POS_hf) { POS_quorum = get_quorum(quorum_type::POS, @@ -1588,6 +1588,7 @@ namespace master_nodes // NOTE: No POS quorums are generated when the network has insufficient nodes to generate quorums // Or, block specifies time after all the rounds have timed out bool miner_block = !POS_hf || !POS_quorum; + // std::cout << "miner_block : " << miner_block << std::endl; result = verify_block_components(m_blockchain.nettype(), block, @@ -3795,7 +3796,7 @@ namespace master_nodes bool master_node_info::can_transition_to_state(hf hf_version, uint64_t height, new_state proposed_state) const { - if (hf_version >= hf::hf14_enforce_checkpoints) { + if (hf_version >= hf::hf15_flash) { if (!can_be_voted_on(height)) { MDEBUG("MN state transition invalid: " << height << " is not a valid vote height"); return false; diff --git a/src/cryptonote_core/master_node_rules.h b/src/cryptonote_core/master_node_rules.h index 261efa915aa..a8712f6b9cd 100755 --- a/src/cryptonote_core/master_node_rules.h +++ b/src/cryptonote_core/master_node_rules.h @@ -241,7 +241,6 @@ namespace master_nodes { proof_version{{cryptonote::hf::hf17_POS, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, proof_version{{cryptonote::hf::hf16, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, proof_version{{cryptonote::hf::hf15_flash, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, - proof_version{{cryptonote::hf::hf14_enforce_checkpoints,0}, {4,0,0}, {0,9,5}, {2,2,0}}, proof_version{{cryptonote::hf::hf13_checkpointing, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, }; diff --git a/src/cryptonote_core/master_node_voting.cpp b/src/cryptonote_core/master_node_voting.cpp index 9b3993716f3..76d9919851f 100755 --- a/src/cryptonote_core/master_node_voting.cpp +++ b/src/cryptonote_core/master_node_voting.cpp @@ -199,7 +199,7 @@ namespace master_nodes int validator_index_tracker = -1; for (const auto &vote : state_change.votes) { - if (hf_version >= hf::hf14_enforce_checkpoints) // NOTE: After HF13, votes must be stored in ascending order + if (hf_version >= hf::hf15_flash) // NOTE: After HF13, votes must be stored in ascending order { if (validator_index_tracker >= static_cast(vote.validator_index)) { @@ -267,7 +267,7 @@ namespace master_nodes return false; } - enforce_vote_ordering = hf_version >= hf::hf14_enforce_checkpoints; + enforce_vote_ordering = hf_version >= hf::hf15_flash; } break; diff --git a/src/rpc/http_client.cpp b/src/rpc/http_client.cpp index d24e379a20d..d65b34a4baa 100755 --- a/src/rpc/http_client.cpp +++ b/src/rpc/http_client.cpp @@ -140,6 +140,7 @@ nlohmann::json http_client::json_rpc(std::string_view method, std::optional(), "JSON RPC returned an error response: " + (*err)["message"].get()}; + // std::cout << "response : " << response << std::endl; //TODO will remove if (auto res = response.find("result"); res != response.end()) result = std::move(*res); diff --git a/src/wallet/tx_construction_data.h b/src/wallet/tx_construction_data.h index ce3cce4cc9d..134d63d0164 100755 --- a/src/wallet/tx_construction_data.h +++ b/src/wallet/tx_construction_data.h @@ -106,7 +106,7 @@ void serialize(Archive &a, wallet::tx_construction_data &x, const unsigned int v if (ver < 6) { x.tx_type = cryptonote::txtype::standard; - x.hf_version = cryptonote::hf::hf14_enforce_checkpoints; + x.hf_version = cryptonote::hf::hf15_flash; } } diff --git a/tests/core_tests/beldex_tests.cpp b/tests/core_tests/beldex_tests.cpp index c9b19db1dc2..5e1f25ffcf9 100755 --- a/tests/core_tests/beldex_tests.cpp +++ b/tests/core_tests/beldex_tests.cpp @@ -443,7 +443,7 @@ bool beldex_core_block_reward_unpenalized_pre_POS::generate(std::vector= cryptonote::hf::hf14_enforce_checkpoints); + assert(newest_hf >= cryptonote::hf::hf15_flash); gen.add_mined_money_unlock_blocks(); @@ -481,7 +481,7 @@ bool beldex_core_block_reward_unpenalized_post_POS::generate(std::vector= cryptonote::hf::hf14_enforce_checkpoints); + assert(newest_hf >= cryptonote::hf::hf15_flash); gen.add_blocks_until_version(hard_forks.back().version); gen.add_mined_money_unlock_blocks(); diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 0b9ced119bb..89f17f19669 100755 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -249,7 +249,7 @@ bool gen_rct_tx_validation_base::generate_with(std::vector& ev const std::function &post_tx) const { const rct::RCTConfig rct_config { rct::RangeProofType::Borromean, 0 }; - return generate_with_full(events, out_idx, mixin, amount_paid, cryptonote::old::DEFAULT_TX_SPENDABLE_AGE, hf::hf14_enforce_checkpoints, rct_config, valid, pre_tx, post_tx); + return generate_with_full(events, out_idx, mixin, amount_paid, cryptonote::old::DEFAULT_TX_SPENDABLE_AGE, hf::hf15_flash, rct_config, valid, pre_tx, post_tx); } bool gen_rct_tx_valid_from_pre_rct::generate(std::vector& events) const From 51d0962eb0bb6b9d05c262afdddffd411bfd3d83 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 8 May 2025 12:04:04 +0530 Subject: [PATCH 131/182] parser: sort inputs in ascending order Some operations require the input parameters to be processed in ascending order. This change ensures that the parser arranges them correctly to avoid unexpected behavior or invalid parameter errors. --- src/rpc/common/param_parser.hpp | 2 + src/rpc/core_rpc_server.cpp | 3 - src/rpc/core_rpc_server_command_parser.cpp | 89 ++++++++++------------ src/wallet/wallet2.cpp | 1 - 4 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index da4d61dbe01..59f248bd4d2 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -20,6 +20,8 @@ namespace cryptonote::rpc { // Checks that key names are given in ascending order template void check_ascending_names(std::string_view name1, std::string_view name2, const Ignore&...) { + std::cout << "name1" << name1 << std::endl; + std::cout << "name2" << name2 << std::endl; if (!(name2 > name1)) throw std::runtime_error{"Internal error: request values must be retrieved in ascending order"}; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 82c99dc5526..ea0dc6114e3 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1044,9 +1044,6 @@ namespace cryptonote::rpc { serialize(ja, tx); auto dumped = std::move(ja).json(); - for (const auto& [k, v] : dumped.items()) { - LOG_PRINT_L0("tx details has k= " << k); - } e.update(dumped); } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 20d824308e2..df061557ecd 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -40,8 +40,8 @@ namespace cryptonote::rpc { get_values(in, "limit", mns.request.limit, - "poll_block_hash", ignore_empty_string{mns.request.poll_block_hash}, - "master_node_pubkeys", mns.request.master_node_pubkeys); + "master_node_pubkeys", mns.request.master_node_pubkeys, + "poll_block_hash", ignore_empty_string{mns.request.poll_block_hash}); } void parse_request(START_MINING& start_mining, rpc_input in) { @@ -51,18 +51,7 @@ namespace cryptonote::rpc { "slow_mining", start_mining.request.slow_mining, "threads_count", start_mining.request.threads_count); } - void parse_request(STOP_MINING& stop_mining, rpc_input in) { - } - void parse_request(MINING_STATUS& mining_status, rpc_input in) { - } - void parse_request(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_input in) { - } - void parse_request(GET_BLOCK_COUNT& getblockcount, rpc_input in) { - } - void parse_request(STOP_DAEMON& stop_daemon, rpc_input in) { - } - void parse_request(SAVE_BC& save_bc, rpc_input in) { - } + void parse_request(GET_OUTPUTS& get_outputs, rpc_input in) { get_values(in, "as_tuple", get_outputs.request.as_tuple, @@ -234,8 +223,8 @@ namespace cryptonote::rpc { void parse_request(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_input in) { get_values(in, - "height", get_coinbase_tx_sum.request.height, - "count", get_coinbase_tx_sum.request.count); + "count", get_coinbase_tx_sum.request.count, + "height", get_coinbase_tx_sum.request.height); } void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in) { @@ -244,15 +233,15 @@ namespace cryptonote::rpc { } void parse_request(OUT_PEERS& out_peers, rpc_input in){ - get_values(in, - "set", out_peers.request.set, - "out_peers", out_peers.request.out_peers); + get_values(in, + "out_peers", out_peers.request.out_peers, + "set", out_peers.request.set); } void parse_request(IN_PEERS& in_peers, rpc_input in){ get_values(in, - "set", in_peers.request.set, - "in_peers", in_peers.request.in_peers); + "in_peers", in_peers.request.in_peers, + "set", in_peers.request.set); } void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ @@ -269,10 +258,10 @@ namespace cryptonote::rpc { void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ get_values(in, + "ed25519_pubkey", required{storage_server_ping.request.pubkey_ed25519}, "error", storage_server_ping.request.error, "https_port", required{storage_server_ping.request.https_port}, "omq_port", required{storage_server_ping.request.omq_port}, - "ed25519_pubkey", required{storage_server_ping.request.pubkey_ed25519}, "version", required{storage_server_ping.request.version}); } @@ -283,21 +272,21 @@ namespace cryptonote::rpc { void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in) { get_values(in, - "type", report_peer_status.request.type, + "passed", report_peer_status.request.passed, "pubkey", report_peer_status.request.pubkey, - "passed", report_peer_status.request.passed); + "type", report_peer_status.request.type); } void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in) { get_values(in, - "start_height", get_mn_state_changes.request.start_height, - "end_height", required{get_mn_state_changes.request.end_height}); + "end_height", get_mn_state_changes.request.end_height, + "start_height", required{get_mn_state_changes.request.start_height}); } void parse_request(FLUSH_CACHE& flush_cache, rpc_input in) { get_values(in, - "bad_txs", flush_cache.request.bad_txs, - "bad_blocks", flush_cache.request.bad_blocks); + "bad_blocks", flush_cache.request.bad_blocks, + "bad_txs", flush_cache.request.bad_txs); } void parse_request(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_input in) { @@ -308,10 +297,10 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_input in) { get_values(in, - "hash", get_block_header_by_hash.request.hash, - "hashes", get_block_header_by_hash.request.hashes, "fill_pow_hash", get_block_header_by_hash.request.fill_pow_hash, - "get_tx_hashes", get_block_header_by_hash.request.get_tx_hashes); + "get_tx_hashes", get_block_header_by_hash.request.get_tx_hashes, + "hash", get_block_header_by_hash.request.hash, + "hashes", get_block_header_by_hash.request.hashes); } void parse_request(SET_BANS& set_bans, rpc_input in) { @@ -333,46 +322,46 @@ namespace cryptonote::rpc { void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in) { get_values(in, - "start_height", get_block_headers_range.request.start_height, - "end_height", get_block_headers_range.request.end_height, + "end_height", get_block_headers_range.request.end_height, "fill_pow_hash", get_block_headers_range.request.fill_pow_hash, - "get_tx_hashes", get_block_headers_range.request.get_tx_hashes); + "get_tx_hashes", get_block_headers_range.request.get_tx_hashes, + "start_height", get_block_headers_range.request.start_height); } void parse_request(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_input in) { get_values(in, - "height", get_block_header_by_height.request.height, - "heights", get_block_header_by_height.request.heights, "fill_pow_hash", get_block_header_by_height.request.fill_pow_hash, - "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes); + "get_tx_hashes", get_block_header_by_height.request.get_tx_hashes, + "height", get_block_header_by_height.request.height, + "heights", get_block_header_by_height.request.heights); } void parse_request(GET_BLOCK& get_block, rpc_input in) { get_values(in, - "hash", get_block.request.hash, - "height", get_block.request.height, - "fill_pow_hash", get_block.request.fill_pow_hash); + "fill_pow_hash", get_block.request.fill_pow_hash, + "hash", get_block.request.hash, + "height", get_block.request.height); } void parse_request(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_input in) { get_values(in, - "amounts", get_output_histogram.request.amounts, - "min_count", get_output_histogram.request.min_count, - "max_count", get_output_histogram.request.max_count, - "unlocked", get_output_histogram.request.unlocked, - "recent_cutoff", get_output_histogram.request.recent_cutoff); + "amounts", get_output_histogram.request.amounts, + "max_count", get_output_histogram.request.max_count, + "min_count", get_output_histogram.request.min_count, + "recent_cutoff", get_output_histogram.request.recent_cutoff, + "unlocked", get_output_histogram.request.unlocked); } void parse_request(BNS_OWNERS_TO_NAMES& owners_to_names, rpc_input in) { get_values(in, - "entries", required{owners_to_names.request.entries}, + "entries", required{owners_to_names.request.entries}, "include_expired", owners_to_names.request.include_expired); } void parse_request(BNS_NAMES_TO_OWNERS& names_to_owners, rpc_input in) { get_values(in, - "name_hash", required{names_to_owners.request.name_hash}, - "include_expired", names_to_owners.request.include_expired); + "include_expired", names_to_owners.request.include_expired, + "name_hash", required{names_to_owners.request.name_hash}); } void parse_request(BNS_RESOLVE& resolve, rpc_input in) { @@ -388,9 +377,9 @@ namespace cryptonote::rpc { void parse_request(BNS_VALUE_DECRYPT& value_decrypt, rpc_input in) { get_values(in, + "encrypted_value", required{value_decrypt.request.encrypted_value}, "name", required{value_decrypt.request.name}, - "type", required{value_decrypt.request.type}, - "encrypted_value", required{value_decrypt.request.encrypted_value}); + "type", required{value_decrypt.request.type}); } void parse_request(GET_QUORUM_STATE& qs, rpc_input in) { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index eea15e50208..323b3c9a8c9 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -6947,7 +6947,6 @@ void wallet2::commit_tx(pending_tx& ptx, bool flash) nlohmann::json send_transaction_params{ {"tx_as_hex", oxenc::to_hex(tx_to_blob(ptx.tx))}, {"do_not_relay", false}, - {"do_sanity_checks", true}, {"flash", flash}, }; From 8e47ab05c894d4de51420cb7c67672cd3e2cdc5b Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Thu, 8 May 2025 15:17:06 +0530 Subject: [PATCH 132/182] Refactor: Replace boost::uuids::uuid with type-safe alternative --- .../include/epee/net/abstract_tcp_server2.inl | 7 +-- .../include/epee/net/levin_protocol_handler.h | 1 - .../epee/net/levin_protocol_handler_async.h | 50 ++++++++----------- .../epee/include/epee/net/net_utils_base.h | 50 +++++++++++++++---- .../epee/storages/levin_abstract_invoke2.h | 6 +-- contrib/epee/src/net_utils_base.cpp | 37 +++++++++++++- contrib/epee/tests/src/net/test_net.h | 6 +-- src/cryptonote_config.h | 12 ++--- src/cryptonote_protocol/block_queue.cpp | 46 +++++++---------- src/cryptonote_protocol/block_queue.h | 38 +++++++------- .../cryptonote_protocol_handler.h | 2 +- .../cryptonote_protocol_handler.inl | 22 ++++---- src/cryptonote_protocol/levin_notify.cpp | 33 ++++++------ src/cryptonote_protocol/levin_notify.h | 4 +- src/net/dandelionpp.cpp | 20 ++++---- src/net/dandelionpp.h | 27 +++++----- src/p2p/net_node.h | 15 +++--- src/p2p/net_node.inl | 50 +++++++++++-------- src/p2p/net_node_common.h | 16 +++--- src/p2p/p2p_protocol_defs.h | 4 +- 20 files changed, 244 insertions(+), 202 deletions(-) diff --git a/contrib/epee/include/epee/net/abstract_tcp_server2.inl b/contrib/epee/include/epee/net/abstract_tcp_server2.inl index 8b3fc80c118..233ee0169b1 100755 --- a/contrib/epee/include/epee/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/epee/net/abstract_tcp_server2.inl @@ -33,7 +33,6 @@ #include -#include #include #include "../warnings.h" #include "../string_tools.h" @@ -170,11 +169,9 @@ PRAGMA_WARNING_DISABLE_VS(4355) m_is_multithreaded = is_multithreaded; m_local = real_remote.is_loopback() || real_remote.is_local(); - // create a random uuid, we don't need crypto strength here - const boost::uuids::uuid random_uuid = boost::uuids::random_generator()(); - + // create a random id, we don't need crypto strength here context = t_connection_context{}; - context.set_details(random_uuid, std::move(real_remote), is_income); + context.set_details(connection_id_t::random(), std::move(real_remote), is_income); boost::system::error_code ec; auto local_ep = socket().local_endpoint(ec); diff --git a/contrib/epee/include/epee/net/levin_protocol_handler.h b/contrib/epee/include/epee/net/levin_protocol_handler.h index 38fbae9f01a..5ffaa46ccd9 100755 --- a/contrib/epee/include/epee/net/levin_protocol_handler.h +++ b/contrib/epee/include/epee/net/levin_protocol_handler.h @@ -29,7 +29,6 @@ #ifndef _LEVIN_PROTOCOL_HANDLER_H_ #define _LEVIN_PROTOCOL_HANDLER_H_ -#include #include "levin_base.h" #include "../int-util.h" diff --git a/contrib/epee/include/epee/net/levin_protocol_handler_async.h b/contrib/epee/include/epee/net/levin_protocol_handler_async.h index 2d10e60dece..33885ab2e93 100755 --- a/contrib/epee/include/epee/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/epee/net/levin_protocol_handler_async.h @@ -26,7 +26,6 @@ #pragma once #include -#include #include #include @@ -54,13 +53,6 @@ namespace epee namespace levin { -struct uuid_hasher { - size_t operator()(const boost::uuids::uuid& uid) const - { - return boost::uuids::hash_value(uid); - } -}; - /************************************************************************/ /* */ /************************************************************************/ @@ -70,15 +62,15 @@ class async_protocol_handler; template class async_protocol_handler_config { - typedef std::unordered_map*, uuid_hasher > connections_map; + using connections_map = std::unordered_map*>; std::recursive_mutex m_connects_lock; connections_map m_connects; void add_connection(async_protocol_handler* pc); void del_connection(async_protocol_handler* pc); - async_protocol_handler* find_connection(boost::uuids::uuid connection_id) const; - int find_and_lock_connection(boost::uuids::uuid connection_id, async_protocol_handler*& aph); + async_protocol_handler *find_connection(connection_id_t connection_id) const; + int find_and_lock_connection(connection_id_t connection_id, async_protocol_handler *&aph); friend class async_protocol_handler; @@ -92,19 +84,19 @@ class async_protocol_handler_config uint64_t m_max_packet_size; std::chrono::nanoseconds m_invoke_timeout; - int invoke(int command, const epee::span in_buff, std::string& buff_out, boost::uuids::uuid connection_id); + int invoke(int command, const epee::span in_buff, std::string& buff_out, connection_id_t connection_id); template - int invoke_async(int command, const epee::span in_buff, boost::uuids::uuid connection_id, const callback_t &cb, std::chrono::nanoseconds timeout = 0s); + int invoke_async(int command, const epee::span in_buff, connection_id_t connection_id, const callback_t &cb, std::chrono::nanoseconds timeout = 0s); - int notify(int command, const epee::span in_buff, boost::uuids::uuid connection_id); - int send(epee::shared_sv message, const boost::uuids::uuid& connection_id); - bool close(boost::uuids::uuid connection_id); + int notify(int command, const epee::span in_buff, connection_id_t connection_id); + int send(epee::shared_sv message, const connection_id_t &connection_id); + bool close(connection_id_t connection_id); bool update_connection_context(const t_connection_context& contxt); - bool request_callback(boost::uuids::uuid connection_id); + bool request_callback(connection_id_t connection_id); template bool foreach_connection(const callback_t &cb); template - bool for_connection(const boost::uuids::uuid &connection_id, const callback_t &cb); + bool for_connection(const connection_id_t &connection_id, const callback_t &cb); size_t get_connections_count(); size_t get_out_connections_count(); size_t get_in_connections_count(); @@ -721,7 +713,7 @@ class async_protocol_handler return 1; } //------------------------------------------------------------------------------------------ - boost::uuids::uuid get_connection_id() {return m_connection_context.m_connection_id;} + connection_id_t get_connection_id() {return m_connection_context.m_connection_id;} //------------------------------------------------------------------------------------------ t_connection_context& get_context_ref() {return m_connection_context;} }; @@ -739,7 +731,7 @@ void async_protocol_handler_config::del_connection(async_p template void async_protocol_handler_config::delete_connections(size_t count, bool incoming) { - std::vector connections; + std::vector connections; std::lock_guard lock{m_connects_lock}; for (auto& c: m_connects) { @@ -791,14 +783,14 @@ void async_protocol_handler_config::add_connection(async_p } //------------------------------------------------------------------------------------------ template -async_protocol_handler* async_protocol_handler_config::find_connection(boost::uuids::uuid connection_id) const +async_protocol_handler* async_protocol_handler_config::find_connection(connection_id_t connection_id) const { auto it = m_connects.find(connection_id); return it == m_connects.end() ? 0 : it->second; } //------------------------------------------------------------------------------------------ template -int async_protocol_handler_config::find_and_lock_connection(boost::uuids::uuid connection_id, async_protocol_handler*& aph) +int async_protocol_handler_config::find_and_lock_connection(connection_id_t connection_id, async_protocol_handler*& aph) { std::lock_guard lock{m_connects_lock}; aph = find_connection(connection_id); @@ -810,7 +802,7 @@ int async_protocol_handler_config::find_and_lock_connectio } //------------------------------------------------------------------------------------------ template -int async_protocol_handler_config::invoke(int command, const epee::span in_buff, std::string& buff_out, boost::uuids::uuid connection_id) +int async_protocol_handler_config::invoke(int command, const epee::span in_buff, std::string& buff_out, connection_id_t connection_id) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); @@ -818,7 +810,7 @@ int async_protocol_handler_config::invoke(int command, con } //------------------------------------------------------------------------------------------ template template -int async_protocol_handler_config::invoke_async(int command, const epee::span in_buff, boost::uuids::uuid connection_id, const callback_t &cb, std::chrono::nanoseconds timeout) +int async_protocol_handler_config::invoke_async(int command, const epee::span in_buff, connection_id_t connection_id, const callback_t &cb, std::chrono::nanoseconds timeout) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); @@ -839,7 +831,7 @@ bool async_protocol_handler_config::foreach_connection(con } //------------------------------------------------------------------------------------------ template template -bool async_protocol_handler_config::for_connection(const boost::uuids::uuid &connection_id, const callback_t &cb) +bool async_protocol_handler_config::for_connection(const connection_id_t &connection_id, const callback_t &cb) { std::lock_guard lock{m_connects_lock}; async_protocol_handler* aph = find_connection(connection_id); @@ -889,7 +881,7 @@ void async_protocol_handler_config::set_handler(levin_comm } //------------------------------------------------------------------------------------------ template -int async_protocol_handler_config::notify(int command, const epee::span in_buff, boost::uuids::uuid connection_id) +int async_protocol_handler_config::notify(int command, const epee::span in_buff, connection_id_t connection_id) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); @@ -897,7 +889,7 @@ int async_protocol_handler_config::notify(int command, con } //------------------------------------------------------------------------------------------ template -int async_protocol_handler_config::send(shared_sv message, const boost::uuids::uuid& connection_id) +int async_protocol_handler_config::send(shared_sv message, const connection_id_t& connection_id) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); @@ -905,7 +897,7 @@ int async_protocol_handler_config::send(shared_sv message, } //------------------------------------------------------------------------------------------ template -bool async_protocol_handler_config::close(boost::uuids::uuid connection_id) +bool async_protocol_handler_config::close(connection_id_t connection_id) { std::lock_guard lock{m_connects_lock}; async_protocol_handler* aph = find_connection(connection_id); @@ -928,7 +920,7 @@ bool async_protocol_handler_config::update_connection_cont } //------------------------------------------------------------------------------------------ template -bool async_protocol_handler_config::request_callback(boost::uuids::uuid connection_id) +bool async_protocol_handler_config::request_callback(connection_id_t connection_id) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); diff --git a/contrib/epee/include/epee/net/net_utils_base.h b/contrib/epee/include/epee/net/net_utils_base.h index c1a10900e17..a1df7cfa042 100755 --- a/contrib/epee/include/epee/net/net_utils_base.h +++ b/contrib/epee/include/epee/net/net_utils_base.h @@ -29,8 +29,7 @@ #ifndef _NET_UTILS_BASE_H_ #define _NET_UTILS_BASE_H_ -#include -#include +#include #include #include #include @@ -47,6 +46,10 @@ #define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(((uint32_t)a4)<<24)) #endif +namespace boost::asio { + using io_service = io_context; +} + #if BOOST_VERSION >= 107000 #define GET_IO_SERVICE(s) ((boost::asio::io_context&)(s).get_executor().context()) #else @@ -55,6 +58,18 @@ namespace epee { + +struct connection_id_t : std::array { + // Makes a random connection id. *NOT* cryptographically secure random. + static connection_id_t random(); + + bool is_nil() const { //TODO have to add constexpr in c++20 + return std::all_of(begin(), end(), [](auto x) { return x == 0; }); + } +}; + +std::ostream& operator<<(std::ostream& out, const connection_id_t& c); + namespace net_utils { class ipv4_network_address @@ -349,7 +364,7 @@ namespace net_utils /************************************************************************/ struct connection_context_base { - const boost::uuids::uuid m_connection_id; + const connection_id_t m_connection_id; const network_address m_remote_address; const bool m_is_income; std::chrono::steady_clock::time_point m_started; @@ -362,7 +377,7 @@ namespace net_utils double m_max_speed_down; double m_max_speed_up; - connection_context_base(boost::uuids::uuid connection_id, + connection_context_base(connection_id_t connection_id, const network_address &remote_address, bool is_income, std::chrono::steady_clock::time_point last_recv = std::chrono::steady_clock::time_point::min(), std::chrono::steady_clock::time_point last_send = std::chrono::steady_clock::time_point::min(), @@ -409,7 +424,7 @@ namespace net_utils private: template friend class connection; - void set_details(boost::uuids::uuid connection_id, const network_address &remote_address, bool is_income) + void set_details(connection_id_t connection_id, const network_address &remote_address, bool is_income) { this->~connection_context_base(); new(this) connection_context_base(connection_id, remote_address, is_income); @@ -437,16 +452,14 @@ namespace net_utils //some helpers - - std::string print_connection_context(const connection_context_base& ctx); std::string print_connection_context_short(const connection_context_base& ctx); inline MAKE_LOGGABLE(connection_context_base, ct, os) -{ - os << "[" << epee::net_utils::print_connection_context_short(ct) << "] "; - return os; -} + { + os << "[" << epee::net_utils::print_connection_context_short(ct) << "] "; + return os; + } #define LOG_ERROR_CC(ct, message) MERROR(ct << message) #define LOG_WARNING_CC(ct, message) MWARNING(ct << message) @@ -472,4 +485,19 @@ inline MAKE_LOGGABLE(connection_context_base, ct, os) } } +namespace std { +template <> +struct hash { + size_t operator()(const epee::connection_id_t& id) const { + constexpr size_t inverse_golden_ratio = sizeof(size_t) >= 8 ? 0x9e37'79b9'7f4a'7c15 : 0x9e37'79b9; + + uint64_t a, b; + std::memcpy(&a, id.data(), 8); + std::memcpy(&b, id.data() + 8, 8); + auto h = hash{}(a); + return hash{}(b) + inverse_golden_ratio + (h << 6) + (h >> 2); + } +}; +} // namespace std + #endif //_NET_UTILS_BASE_H_ diff --git a/contrib/epee/include/epee/storages/levin_abstract_invoke2.h b/contrib/epee/include/epee/storages/levin_abstract_invoke2.h index c3bfa228e2d..efdcbbea57a 100755 --- a/contrib/epee/include/epee/storages/levin_abstract_invoke2.h +++ b/contrib/epee/include/epee/storages/levin_abstract_invoke2.h @@ -84,7 +84,7 @@ namespace epee } template - bool invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_result& result_struct, t_transport& transport) + bool invoke_remote_command2(connection_id_t conn_id, int command, const t_arg& out_struct, t_result& result_struct, t_transport& transport) { typename serialization::portable_storage stg; @@ -108,7 +108,7 @@ namespace epee } template - bool async_invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, std::chrono::nanoseconds inv_timeout = 0ns) + bool async_invoke_remote_command2(connection_id_t conn_id, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, std::chrono::nanoseconds inv_timeout = 0ns) { typename serialization::portable_storage stg; const_cast(out_struct).store(stg);//TODO: add true const support to searilzation @@ -148,7 +148,7 @@ namespace epee } template - bool notify_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport) + bool notify_remote_command2(connection_id_t conn_id, int command, const t_arg& out_struct, t_transport& transport) { serialization::portable_storage stg; diff --git a/contrib/epee/src/net_utils_base.cpp b/contrib/epee/src/net_utils_base.cpp index e264b8c0278..9fb169b0d46 100755 --- a/contrib/epee/src/net_utils_base.cpp +++ b/contrib/epee/src/net_utils_base.cpp @@ -1,7 +1,8 @@ #include "epee/net/net_utils_base.h" -#include +#include +#include #include "epee/string_tools.h" #include "epee/net/local_ip.h" @@ -115,3 +116,37 @@ namespace epee::net_utils return o << to_string(z); } } // namespace epee::net_utils + + +namespace epee { + + static std::mt19937_64 seed_rng() { + std::random_device dev; + // each dev() gives us 32 bits of random data; 256 bits ought to be plenty for what we need: + std::seed_seq seed{{dev(), dev(), dev(), dev(), dev(), dev(), dev(), dev()}}; + std::mt19937_64 rng{seed}; + return rng; + } + + connection_id_t connection_id_t::random() { + static thread_local auto rng = seed_rng(); + uint64_t x[2]; + x[0] = rng(); + x[1] = rng(); + connection_id_t conn_id; + static_assert(sizeof(conn_id) == sizeof(x)); + std::memcpy(conn_id.data(), &x, sizeof(x)); + return conn_id; + } + + std::ostream& operator<<(std::ostream& out, const connection_id_t& c) { + // Output in uuid form: + // 00112233-4455-6677-8899-101112131415 + return out << oxenc::to_hex(c.begin(), c.begin() + 4) << '-' + << oxenc::to_hex(c.begin() + 4, c.begin() + 6) << '-' + << oxenc::to_hex(c.begin() + 6, c.begin() + 8) << '-' + << oxenc::to_hex(c.begin() + 8, c.begin() + 10) << '-' + << oxenc::to_hex(c.begin() + 10, c.end()); + } + + } // namespace epee \ No newline at end of file diff --git a/contrib/epee/tests/src/net/test_net.h b/contrib/epee/tests/src/net/test_net.h index b4edcff90f2..e8f673560bc 100755 --- a/contrib/epee/tests/src/net/test_net.h +++ b/contrib/epee/tests/src/net/test_net.h @@ -119,7 +119,7 @@ namespace tests }; typedef epee::misc_utils::struct_init response; }; - typedef boost::uuids::uuid uuid; + class test_levin_server: public levin::levin_commands_handler<> { @@ -146,13 +146,13 @@ namespace tests } template - bool invoke(uuid con_id, int command, t_request& req, t_response& resp) + bool invoke(connection_id_t con_id, int command, t_request& req, t_response& resp) { return invoke_remote_command(con_id, command, req, resp, m_net_server.get_config_object()); } template< class t_response, class t_request, class callback_t> - bool invoke_async(uuid con_id, int command, t_request& req, callback_t cb) + bool invoke_async(connection_id_t con_id, int command, t_request& req, callback_t cb) { return async_invoke_remote_command(con_id, command, req, m_net_server.get_config_object(), cb); } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 7e7969beb2b..21c041c9bb6 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -30,16 +30,16 @@ #pragma once -#include + #include #include #include #include -#include #include #include #include #include +#include using namespace std::literals; namespace cryptonote { @@ -314,7 +314,7 @@ namespace config inline constexpr uint16_t RPC_DEFAULT_PORT = 19091; inline constexpr uint16_t ZMQ_RPC_DEFAULT_PORT = 19092; inline constexpr uint16_t QNET_DEFAULT_PORT = 19095; - inline constexpr boost::uuids::uuid const NETWORK_ID = { { + inline constexpr std::array const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x17, 0xA1, 0xB5, 0x90 } }; // Bender's nightmare inline constexpr std::string_view GENESIS_TX = "013c01ff0005978c390224a302c019c844f7141f35bf7f0fc5b02ada055e4ba897557b17ac6ccf88f0a2c09fab030276d443549feee11fe325048eeea083fcb7535312572d255ede1ecb58f84253b480e89226023b7d7c5e6eff4da699393abf12b6e3d04eae7909ae21932520fb3166b8575bb180cab5ee0102e93beb645ce7d5574d6a5ed5d9b8aadec7368342d08a7ca7b342a428353a10df80e497d01202b6e6844c1e9a478d0e4f7f34e455b26077a51f0005357aa19a49ca16eb373f622101f7c2a3a2ed7011b61998b1cd4f45b4d3c1daaa82908a10ca191342297eef1cf8"sv; @@ -346,7 +346,7 @@ namespace config inline constexpr uint16_t RPC_DEFAULT_PORT = 29091; inline constexpr uint16_t ZMQ_RPC_DEFAULT_PORT = 29092; inline constexpr uint16_t QNET_DEFAULT_PORT = 29095; - inline constexpr boost::uuids::uuid const NETWORK_ID = { { + inline constexpr std::array const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x17, 0xA1, 0xB6, 0x91 } }; // Bender's daydream inline constexpr std::string_view GENESIS_TX = "023c01ff0001d7c1c4e81402a4b3be74714906edf0d798d22083d36983e80086d62436302684ca5bea0f312b420195937f9cb7005504052c96bf73d65d55f611c141876e5e519cef59fcb041d90872000000000000000000000000000000000000000000000000000000000000000000"sv; @@ -375,7 +375,7 @@ namespace config inline constexpr uint16_t RPC_DEFAULT_PORT = 39091; inline constexpr uint16_t ZMQ_RPC_DEFAULT_PORT = 39092; inline constexpr uint16_t QNET_DEFAULT_PORT = 39095; - inline constexpr boost::uuids::uuid const NETWORK_ID = { { + inline constexpr std::array const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x17, 0xA1, 0xB7, 0x92 } }; inline constexpr std::string_view GENESIS_TX = "023c01ff0001d7c1c4e81402a25ba172ed7bca3b35e0be2f097b743973cf3c26777342032bed1036b19ab7a4420145706ec71eec5d57962c225b0615c172f8429984ec4954ba8b05bdad3f454f0472000000000000000000000000000000000000000000000000000000000000000000"sv; @@ -412,7 +412,7 @@ namespace config uint16_t RPC_DEFAULT_PORT; uint16_t ZMQ_RPC_DEFAULT_PORT; uint16_t QNET_DEFAULT_PORT; - boost::uuids::uuid NETWORK_ID; + const std::array NETWORK_ID; std::string_view GENESIS_TX; uint32_t GENESIS_NONCE; uint64_t GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS; diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index ca4e0639e48..ee0383568c7 100755 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -31,8 +31,7 @@ #include #include -#include -#include + #include "epee/string_tools.h" #include "cryptonote_protocol_defs.h" #include "common/pruning.h" @@ -41,21 +40,10 @@ #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "cn.block_queue" -namespace std { -template<> -struct hash -{ - size_t operator()(const boost::uuids::uuid& uid) const - { - return boost::uuids::hash_value(uid); - } -}; -} - namespace cryptonote { -void block_queue::add_blocks(uint64_t height, std::vector bcel, const boost::uuids::uuid &connection_id, float rate, size_t size) +void block_queue::add_blocks(uint64_t height, std::vector bcel, const connection_id_t& connection_id, float rate, size_t size) { std::unique_lock lock{mutex}; std::vector hashes; @@ -72,14 +60,14 @@ void block_queue::add_blocks(uint64_t height, std::vector 0, "Empty span"); std::unique_lock lock{mutex}; blocks.insert(span(height, nblocks, connection_id, time)); } -void block_queue::flush_spans(const boost::uuids::uuid &connection_id, bool all) +void block_queue::flush_spans(const connection_id_t& connection_id, bool all) { std::unique_lock lock{mutex}; block_map::iterator i = blocks.begin(); @@ -104,7 +92,7 @@ void block_queue::erase_block(block_map::iterator j) blocks.erase(j); } -void block_queue::flush_stale_spans(const std::set &live_connections) +void block_queue::flush_stale_spans(const std::set& live_connections) { std::unique_lock lock{mutex}; block_map::iterator i = blocks.begin(); @@ -134,7 +122,7 @@ bool block_queue::remove_span(uint64_t start_block_height, std::vector block_queue::reserve_span( uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, - const boost::uuids::uuid &connection_id, + const connection_id_t& connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector &block_hashes) @@ -309,7 +297,7 @@ std::pair block_queue::reserve_span( return std::make_pair(span_start_height, span_length); } -std::pair block_queue::get_next_span_if_scheduled(std::vector &hashes, boost::uuids::uuid &connection_id) const +std::pair block_queue::get_next_span_if_scheduled(std::vector &hashes, connection_id_t& connection_id) const { std::unique_lock lock{mutex}; if (blocks.empty()) @@ -336,7 +324,7 @@ void block_queue::reset_next_span_time() } -void block_queue::set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::vector hashes) +void block_queue::set_span_hashes(uint64_t start_height, const connection_id_t& connection_id, std::vector hashes) { std::unique_lock lock{mutex}; for (block_map::iterator i = blocks.begin(); i != blocks.end(); ++i) @@ -354,7 +342,7 @@ void block_queue::set_span_hashes(uint64_t start_height, const boost::uuids::uui } } -bool block_queue::get_next_span(uint64_t &height, std::vector &bcel, boost::uuids::uuid &connection_id, bool filled) const +bool block_queue::get_next_span(uint64_t &height, std::vector &bcel, connection_id_t& connection_id, bool filled) const { std::unique_lock lock{mutex}; if (blocks.empty()) @@ -373,7 +361,7 @@ bool block_queue::get_next_span(uint64_t &height, std::vector speeds; + std::unordered_map speeds; for (const auto &span: blocks) { if (span.blocks.empty()) @@ -448,7 +436,7 @@ float block_queue::get_speed(const boost::uuids::uuid &connection_id) const // note that the average below does not average over the whole set, but over the // previous pseudo average and the latest rate: this gives much more importance // to the latest measurements, which is fine here - std::unordered_map::iterator i = speeds.find(span.connection_id); + std::unordered_map::iterator i = speeds.find(span.connection_id); if (i == speeds.end()) speeds.insert(std::make_pair(span.connection_id, span.rate)); else @@ -473,7 +461,7 @@ float block_queue::get_speed(const boost::uuids::uuid &connection_id) const return speed; } -float block_queue::get_download_rate(const boost::uuids::uuid &connection_id) const +float block_queue::get_download_rate(const connection_id_t& connection_id) const { std::unique_lock lock{mutex}; float conn_rate = -1.f; diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h index ba23b56cbdc..d3a7b105544 100755 --- a/src/cryptonote_protocol/block_queue.h +++ b/src/cryptonote_protocol/block_queue.h @@ -36,8 +36,8 @@ #include #include #include -#include #include "crypto/hash.h" +#include "epee/net/net_utils_base.h" #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "cn.block_queue" @@ -46,6 +46,8 @@ namespace cryptonote { struct block_complete_entry; + using epee::connection_id_t; + class block_queue { public: @@ -54,16 +56,16 @@ namespace cryptonote uint64_t start_block_height; std::vector hashes; std::vector blocks; - boost::uuids::uuid connection_id; + connection_id_t connection_id; uint64_t nblocks; float rate; size_t size; std::chrono::steady_clock::time_point time; - span(uint64_t start_block_height, std::vector blocks, const boost::uuids::uuid &connection_id, float rate, size_t size): + span(uint64_t start_block_height, std::vector blocks, const connection_id_t& connection_id, float rate, size_t size): start_block_height(start_block_height), blocks(std::move(blocks)), connection_id(connection_id), nblocks(this->blocks.size()), rate(rate), size(size), time{std::chrono::steady_clock::now()} {} - span(uint64_t start_block_height, uint64_t nblocks, const boost::uuids::uuid &connection_id, std::chrono::steady_clock::time_point time): + span(uint64_t start_block_height, uint64_t nblocks, const connection_id_t& connection_id, std::chrono::steady_clock::time_point time): start_block_height(start_block_height), connection_id(connection_id), nblocks(nblocks), rate(0.0f), size(0), time(time) {} bool operator<(const span &s) const { return start_block_height < s.start_block_height; } @@ -71,29 +73,29 @@ namespace cryptonote typedef std::set block_map; public: - void add_blocks(uint64_t height, std::vector bcel, const boost::uuids::uuid &connection_id, float rate, size_t size); - void add_blocks(uint64_t height, uint64_t nblocks, const boost::uuids::uuid &connection_id, std::chrono::steady_clock::time_point time); - void flush_spans(const boost::uuids::uuid &connection_id, bool all = false); - void flush_stale_spans(const std::set &live_connections); + void add_blocks(uint64_t height, std::vector bcel, const connection_id_t& connection_id, float rate, size_t size); + void add_blocks(uint64_t height, uint64_t nblocks, const connection_id_t& connection_id, std::chrono::steady_clock::time_point time); + void flush_spans(const connection_id_t& connection_id, bool all = false); + void flush_stale_spans(const std::set& live_connections); bool remove_span(uint64_t start_block_height, std::vector *hashes = nullptr); - void remove_spans(const boost::uuids::uuid &connection_id, uint64_t start_block_height); + void remove_spans(const connection_id_t& connection_id, uint64_t start_block_height); uint64_t get_max_block_height() const; void print() const; std::string get_overview(uint64_t blockchain_height) const; bool has_unpruned_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) const; - std::pair reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector &block_hashes); + std::pair reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const connection_id_t& connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector &block_hashes); uint64_t get_next_needed_height(uint64_t blockchain_height) const; - std::pair get_next_span_if_scheduled(std::vector &hashes, boost::uuids::uuid &connection_id) const; + std::pair get_next_span_if_scheduled(std::vector &hashes, connection_id_t& connection_id) const; void reset_next_span_time(); - void set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::vector hashes); - bool get_next_span(uint64_t &height, std::vector &bcel, boost::uuids::uuid &connection_id, bool filled = true) const; - bool has_next_span(uint64_t height, bool &filled, std::chrono::steady_clock::time_point& time, boost::uuids::uuid &connection_id) const; + void set_span_hashes(uint64_t start_height, const connection_id_t& connection_id, std::vector hashes); + bool get_next_span(uint64_t &height, std::vector &bcel, connection_id_t& connection_id, bool filled = true) const; + bool has_next_span(uint64_t height, bool &filled, std::chrono::steady_clock::time_point& time, connection_id_t& connection_id) const; size_t get_data_size() const; size_t get_num_filled_spans() const; - crypto::hash get_last_known_hash(const boost::uuids::uuid &connection_id) const; - bool has_spans(const boost::uuids::uuid &connection_id) const; - float get_speed(const boost::uuids::uuid &connection_id) const; - float get_download_rate(const boost::uuids::uuid &connection_id) const; + crypto::hash get_last_known_hash(const connection_id_t &connection_id) const; + bool has_spans(const connection_id_t &connection_id) const; + float get_speed(const connection_id_t &connection_id) const; + float get_download_rate(const connection_id_t &connection_id) const; bool foreach(std::function f) const; bool requested(const crypto::hash &hash) const; bool have(const crypto::hash &hash) const; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 03318c5cd6c..e9c846bf414 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -128,7 +128,7 @@ namespace cryptonote bool relay_to_synchronized_peers(typename T::request& arg, cryptonote_connection_context& exclude_context) { LOG_PRINT_L3("[" << epee::net_utils::print_connection_context_short(exclude_context) << "] post relay " << tools::type_name() << " -->"); - std::vector> connections; + std::vector> connections; m_p2p->for_each_connection([&exclude_context, &connections](connection_context& context, nodetool::peerid_type peer_id) { if (context.m_state > cryptonote_connection_context::state_synchronizing) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 2e8b16d11d4..06306fd6e0d 100755 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -36,8 +36,6 @@ // (may contain code and/or modifications by other developers) // developer rfree: this code is caller of our new network code, and is modded; e.g. for rate limiting -#include -#include #include #include #include @@ -1428,7 +1426,7 @@ namespace cryptonote const uint64_t previous_height = m_core.get_current_blockchain_height(); uint64_t start_height; std::vector blocks; - boost::uuids::uuid span_connection_id; + connection_id_t span_connection_id; if (!m_block_queue.get_next_span(start_height, blocks, span_connection_id)) { MDEBUG(context << " no next span found, going back to download"); @@ -1800,7 +1798,7 @@ skip: MTRACE("Checking for outgoing syncing peers..."); unsigned n_syncing = 0, n_synced = 0; - boost::uuids::uuid last_synced_peer_id(boost::uuids::nil_uuid()); + connection_id_t last_synced_peer_id{}; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool { if (!peer_id || context.m_is_income) // only consider connected outgoing peers @@ -1818,7 +1816,7 @@ skip: MTRACE(n_syncing << " syncing, " << n_synced << " synced"); // if we're at max out peers, and not enough are syncing - if (n_synced + n_syncing >= m_max_out_peers && n_syncing < p2p::DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && last_synced_peer_id != boost::uuids::nil_uuid()) + if (n_synced + n_syncing >= m_max_out_peers && n_syncing < p2p::DEFAULT_SYNC_SEARCH_CONNECTIONS_COUNT && !last_synced_peer_id.is_nil()) { if (!m_p2p->for_connection(last_synced_peer_id, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id)->bool{ MINFO(ctx << "dropping synced peer, " << n_syncing << " syncing, " << n_synced << " synced"); @@ -1867,7 +1865,7 @@ skip: bool t_cryptonote_protocol_handler::should_download_next_span(cryptonote_connection_context& context, bool standby) { std::chrono::steady_clock::time_point request_time; - boost::uuids::uuid connection_id; + connection_id_t connection_id{}; std::pair span; bool filled; @@ -2033,7 +2031,7 @@ skip: bool t_cryptonote_protocol_handler::request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span) { // flush stale spans - std::set live_connections; + std::set live_connections; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id)->bool{ live_connections.insert(context.m_connection_id); return true; @@ -2106,7 +2104,7 @@ skip: { bool filled = false; std::chrono::steady_clock::time_point time; - boost::uuids::uuid connection_id; + connection_id_t connection_id; if (m_block_queue.has_next_span(m_core.get_current_blockchain_height(), filled, time, connection_id) && filled) { LOG_DEBUG_CC(context, "No other thread is adding blocks, and next span needed is ready, resuming"); @@ -2164,7 +2162,7 @@ skip: if (span.second == 0) { std::vector hashes; - boost::uuids::uuid span_connection_id; + connection_id_t span_connection_id; span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id); if (span.second > 0) { @@ -2207,7 +2205,7 @@ skip: { MDEBUG(context << " still no span reserved, we may be in the corner case of next span scheduled and everything else scheduled/filled"); std::vector hashes; - boost::uuids::uuid span_connection_id; + connection_id_t span_connection_id; span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id); if (span.second > 0 && !tools::has_unpruned_block(span.first, context.m_remote_blockchain_height, context.m_pruning_seed)) span = std::make_pair(0, 0); @@ -2302,7 +2300,7 @@ skip: { uint64_t start_height; std::vector blocks; - boost::uuids::uuid span_connection_id; + connection_id_t span_connection_id; bool filled = false; if (m_block_queue.get_next_span(start_height, blocks, span_connection_id, filled) && filled) { @@ -2545,7 +2543,7 @@ skip: bool t_cryptonote_protocol_handler::relay_block(NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& exclude_context) { // sort peers between fluffy ones and others - std::vector> fluffyConnections; + std::vector> fluffyConnections; m_p2p->for_each_connection([&exclude_context, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id) { if (peer_id && exclude_context.m_connection_id != context.m_connection_id && context.m_remote_address.get_zone() == epee::net_utils::zone::public_) diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 634870afb09..a70c0e764dc 100755 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -48,6 +48,7 @@ namespace cryptonote::levin { + using epee::connection_id_t; namespace { constexpr std::size_t connection_id_reserve_size = 100; @@ -66,20 +67,20 @@ namespace cryptonote::levin } //! \return All outgoing connections supporting fragments in `connections`. - std::vector get_out_connections(connections& p2p) + + std::vector get_out_connections(connections &p2p) { - std::vector outs; - outs.reserve(connection_id_reserve_size); + std::vector outs; /* The foreach call is serialized with a lock, but should be quick due to the reserve call so a strand is not used. Investigate if there is lots of waiting in here. */ - p2p.foreach_connection([&outs] (detail::p2p_context& context) { + p2p.foreach_connection([&outs](detail::p2p_context &context) + { if (!context.m_is_income) outs.emplace_back(context.m_connection_id); - return true; - }); + return true; }); return outs; } @@ -160,7 +161,7 @@ namespace cryptonote::levin : queue(), strand(io_service), next_noise(io_service), - connection(boost::uuids::nil_uuid()) + connection{} {} // `asio::io_service::strand` cannot be copied or moved @@ -173,7 +174,7 @@ namespace cryptonote::levin std::deque queue; boost::asio::io_service::strand strand; boost::asio::steady_timer next_noise; - boost::uuids::uuid connection; + connection_id_t connection; }; } // anonymous @@ -246,10 +247,10 @@ namespace cryptonote::levin { std::shared_ptr zone_; epee::shared_sv message_; // Requires manual copy - boost::uuids::uuid source_; + connection_id_t source_; public: - explicit flood_notify(std::shared_ptr zone, epee::shared_sv message, const boost::uuids::uuid& source) + explicit flood_notify(std::shared_ptr zone, epee::shared_sv message, const connection_id_t& source) : zone_(std::move(zone)), message_(message), source_(source) {} @@ -271,7 +272,7 @@ namespace cryptonote::levin algorithm changes or the locking strategy within the levin config class changes. */ - std::vector connections; + std::vector connections; connections.reserve(connection_id_reserve_size); zone_->p2p->foreach_connection([this, &connections] (detail::p2p_context& context) { /* Only send to outgoing connections when "flooding" over i2p/tor. @@ -282,7 +283,7 @@ namespace cryptonote::levin return true; }); - for (const boost::uuids::uuid& connection : connections) + for (const connection_id_t& connection : connections) zone_->p2p->send(message_, connection); } }; @@ -292,7 +293,7 @@ namespace cryptonote::levin { std::shared_ptr zone_; const std::size_t channel_; - const boost::uuids::uuid connection_; + const connection_id_t connection_; //! \pre Called within `stem_.strand`. void operator()() const @@ -324,7 +325,7 @@ namespace cryptonote::levin struct update_channels { std::shared_ptr zone_; - std::vector out_connections_; + std::vector out_connections_; //! \pre Called within `zone->strand`. static void post(std::shared_ptr zone) @@ -436,7 +437,7 @@ namespace cryptonote::levin else { channel.active = {}; - channel.connection = boost::uuids::nil_uuid(); + channel.connection = {}; auto connections = get_out_connections(*zone_->p2p); if (connections.empty()) @@ -532,7 +533,7 @@ namespace cryptonote::levin channel.next_noise.cancel(); } - bool notify::send_txs(std::vector txs, const boost::uuids::uuid& source, const bool pad_txs) + bool notify::send_txs(std::vector txs, const connection_id_t& source, const bool pad_txs) { diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index 67a115b4bd1..2d42bf1d73f 100755 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -29,7 +29,6 @@ #pragma once #include -#include #include #include @@ -37,6 +36,7 @@ #include "cryptonote_basic/blobdatatype.h" #include "epee/net/enums.h" #include "epee/span.h" +#include "epee/net/net_utils_base.h" namespace epee { @@ -126,7 +126,7 @@ namespace levin construction. \return True iff the notification is queued for sending. */ - bool send_txs(std::vector txs, const boost::uuids::uuid& source, bool pad_txs); + bool send_txs(std::vector txs, const epee::connection_id_t& source, bool pad_txs); }; } // levin } // net diff --git a/src/net/dandelionpp.cpp b/src/net/dandelionpp.cpp index f5bd3f932e7..78b9bba2431 100755 --- a/src/net/dandelionpp.cpp +++ b/src/net/dandelionpp.cpp @@ -29,7 +29,6 @@ #include "dandelionpp.h" #include -#include #include #include "common/expect.h" @@ -60,7 +59,8 @@ namespace dandelionpp } }; - std::size_t select_stem(epee::span usage, epee::span out_map) + std::size_t select_stem( + epee::span usage, epee::span out_map) { assert(usage.size() < std::numeric_limits::max()); // prevented in constructor if (usage.size() < out_map.size()) @@ -71,7 +71,7 @@ namespace dandelionpp boost::container::small_vector choices; static_assert(sizeof(choices) < 256, "choices is too large based on current configuration"); - for (const boost::uuids::uuid& out : out_map) + for (const connection_id_t& out : out_map) { if (!out.is_nil()) { @@ -100,7 +100,7 @@ namespace dandelionpp } } // anonymous - connection_map::connection_map(std::vector out_connections, const std::size_t stems) + connection_map::connection_map(std::vector out_connections, const std::size_t stems) : out_mapping_(std::move(out_connections)), in_mapping_(), usage_count_() @@ -131,7 +131,7 @@ namespace dandelionpp return {*this}; } - bool connection_map::update(std::vector current) + bool connection_map::update(std::vector current) { std::sort(current.begin(), current.end()); @@ -141,7 +141,7 @@ namespace dandelionpp const auto elem = std::lower_bound(current.begin(), current.end(), existing_out); if (elem == current.end() || *elem != existing_out) { - existing_out = boost::uuids::nil_uuid(); + existing_out = {}; replace = true; } else // already using connection, remove it from candidate list @@ -172,7 +172,7 @@ namespace dandelionpp std::size_t connection_map::size() const noexcept { std::size_t count = 0; - for (const boost::uuids::uuid& connection : out_mapping_) + for (const connection_id_t& connection : out_mapping_) { if (!connection.is_nil()) ++count; @@ -180,14 +180,14 @@ namespace dandelionpp return count; } - boost::uuids::uuid connection_map::get_stem(const boost::uuids::uuid& source) + connection_id_t connection_map::get_stem(const connection_id_t& source) { auto elem = std::lower_bound(in_mapping_.begin(), in_mapping_.end(), source, key_less{}); if (elem == in_mapping_.end() || elem->first != source) { const std::size_t index = select_stem(epee::to_span(usage_count_), epee::to_span(out_mapping_)); if (out_mapping_.size() < index) - return boost::uuids::nil_uuid(); + return {}; elem = in_mapping_.emplace(elem, source, index); usage_count_[index]++; @@ -199,7 +199,7 @@ namespace dandelionpp if (out_mapping_.size() < index) { in_mapping_.erase(elem); - return boost::uuids::nil_uuid(); + return {}; } elem->second = index; diff --git a/src/net/dandelionpp.h b/src/net/dandelionpp.h index e156b5857c1..5b43698d2b8 100755 --- a/src/net/dandelionpp.h +++ b/src/net/dandelionpp.h @@ -28,45 +28,44 @@ #pragma once -#include #include #include #include #include +#include "epee/net/net_utils_base.h" #include "epee/span.h" namespace net { namespace dandelionpp { + using epee::connection_id_t; //! Assists with mapping source -> stem and tracking connections for stem. class connection_map { // Make sure to update clone method if changing members - std::vector out_mapping_; //> in_mapping_; // out_mapping_; //> in_mapping_; // usage_count_; // Use clone method to prevent "hidden" copies. connection_map(const connection_map&) = default; public: - using value_type = boost::uuids::uuid; - using size_type = std::vector::size_type; - using difference_type = std::vector::difference_type; - using reference = const boost::uuids::uuid&; + using value_type = connection_id_t; + using size_type = std::vector::size_type; + using difference_type = std::vector::difference_type; + using reference = const connection_id_t &; using const_reference = reference; - using iterator = std::vector::const_iterator; + using iterator = std::vector::const_iterator; using const_iterator = iterator; //! Initialized with zero stem connections. - explicit connection_map() - : connection_map(std::vector{}, 0) - {} + explicit connection_map() : connection_map(std::vector{}, 0) {} //! Initialized with `out_connections` and `stem_count`. - explicit connection_map(std::vector out_connections, std::size_t stems); + explicit connection_map(std::vector out_connections, std::size_t stems); connection_map(connection_map&&) = default; ~connection_map() noexcept; @@ -94,13 +93,13 @@ namespace dandelionpp \param connections Current outbound connection ids. \return True if any updates to `get_connections()` was made. */ - bool update(std::vector current); + bool update(std::vector current); //! \return Number of outgoing connections in use. std::size_t size() const noexcept; //! \return Current stem mapping for `source` or `nil_uuid()` if none is possible. - boost::uuids::uuid get_stem(const boost::uuids::uuid& source); + connection_id_t get_stem(const connection_id_t& source); }; } // dandelionpp } // net diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 01945ada6f0..7a2f88c896c 100755 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -216,9 +215,7 @@ namespace nodetool m_allow_local_ip(false), m_hide_my_port(false), m_offline(false), - is_closing(false), - m_network_id() - {} + is_closing(false) {} virtual ~node_server(); static void init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden); @@ -298,14 +295,14 @@ namespace nodetool virtual void on_connection_close(p2p_connection_context& context); virtual void callback(p2p_connection_context& context); //----------------- i_p2p_endpoint ------------------------------------------------------------- - virtual bool relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections); - virtual epee::net_utils::zone send_txs(std::vector txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs); + virtual bool relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections); + virtual epee::net_utils::zone send_txs(std::vector txs, const epee::net_utils::zone origin, const connection_id_t& source, const bool pad_txs); virtual bool invoke_command_to_peer(int command, const epee::span req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context); virtual bool invoke_notify_to_peer(int command, const epee::span req_buff, const epee::net_utils::connection_context_base& context); virtual bool drop_connection(const epee::net_utils::connection_context_base& context); virtual void request_callback(const epee::net_utils::connection_context_base& context); virtual void for_each_connection(std::function f); - virtual bool for_connection(const boost::uuids::uuid&, std::function f); + virtual bool for_connection(const connection_id_t&, std::function f); virtual bool add_host_fail(const epee::net_utils::network_address &address); //----------------- i_connection_filter -------------------------------------------------------- virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL); @@ -329,7 +326,7 @@ namespace nodetool bool make_new_connection_from_anchor_peerlist(const std::vector& anchor_peerlist); bool make_new_connection_from_peerlist(network_zone& zone, bool use_white_list); bool try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, PeerType peer_type = white, uint64_t first_seen_stamp = 0); - size_t get_random_index_with_fixed_probability(size_t max_index); + size_t get_random_exp_index(size_t size, double rate = 0.13862943611198906); bool is_peer_used(const peerlist_entry& peer); bool is_peer_used(const anchor_peerlist_entry& peer); bool is_addr_connected(const epee::net_utils::network_address& peer); @@ -447,7 +444,7 @@ namespace nodetool std::mutex m_used_stripe_peers_mutex; std::array, 1 << cryptonote::PRUNING_LOG_STRIPES> m_used_stripe_peers; - boost::uuids::uuid m_network_id; + epee::connection_id_t m_network_id{}; cryptonote::network_type m_nettype; }; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 5f88879c55e..96088ee4604 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -52,6 +51,7 @@ #include "epee/misc_log_ex.h" #include "p2p_protocol_defs.h" #include "epee/net/local_ip.h" +#include "epee/net/net_utils_base.h" #include "crypto/crypto.h" #include "epee/storages/levin_abstract_invoke2.h" #include "cryptonote_core/cryptonote_core.h" @@ -68,6 +68,9 @@ namespace nodetool { + + using epee::connection_id_t; + template node_server::~node_server() { @@ -139,7 +142,7 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - bool node_server::for_connection(const boost::uuids::uuid &connection_id, std::function f) + bool node_server::for_connection(const connection_id_t& connection_id, std::function f) { for(auto& zone : m_network_zones) { @@ -223,7 +226,7 @@ namespace nodetool // drop any connection to that address. This should only have to look into // the zone related to the connection, but really make sure everything is // swept ... - std::vector conns; + std::vector conns; for(auto& zone : m_network_zones) { zone.second.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) @@ -272,7 +275,7 @@ namespace nodetool // drop any connection to that subnet. This should only have to look into // the zone related to the connection, but really make sure everything is // swept ... - std::vector conns; + std::vector conns; for(auto& zone : m_network_zones) { zone.second.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) @@ -635,11 +638,7 @@ namespace nodetool bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); - memcpy(&m_network_id, - m_nettype == cryptonote::network_type::TESTNET ? &cryptonote::config::testnet::NETWORK_ID : - m_nettype == cryptonote::network_type::DEVNET ? &cryptonote::config::devnet::NETWORK_ID : - &cryptonote::config::NETWORK_ID, - 16); + static_cast&>(m_network_id) = get_config(m_nettype).NETWORK_ID; m_config_folder = fs::u8path(command_line::get_arg(vm, cryptonote::arg_data_dir)); network_zone& public_zone = m_network_zones.at(epee::net_utils::zone::public_); @@ -850,7 +849,7 @@ namespace nodetool for (auto& zone : m_network_zones) { - std::list connection_ids; + std::list connection_ids; zone.second.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { connection_ids.push_back(cntxt.m_connection_id); return true; @@ -992,15 +991,25 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - size_t node_server::get_random_index_with_fixed_probability(size_t max_index) + size_t node_server::get_random_exp_index( + const size_t size, const double rate) { - //divide by zero workaround - if(!max_index) + if (size <= 1) return 0; - size_t x = crypto::rand()%(max_index+1); - size_t res = (x*x*x)/(max_index*max_index); //parabola \/ - MDEBUG("Random connection index=" << res << "(x="<< x << ", max_index=" << max_index << ")"); + // (See net_node.h) + + crypto::random_device rng; + const double u = std::uniform_real_distribution{}(rng); + // For non-truncated exponential we could use: -1/rate * log(1-u), (or + // std::exponential_distribution) but then we'd have to repeat until we got a value < size, + // which is technically unbounded computational time. Instead we mutate the calculation like + // this, which gives us exponential, but truncated to [0, size), without loop + + const size_t res = + static_cast(-1.0 / rate * std::log(1.0 - u * (1.0 - std::exp(-rate * size)))); + + MDEBUG("Random connection index= "<(filtered.size() - 1, 20)); + random_index = get_random_exp_index(std::min(filtered.size() - 1, 20)); std::lock_guard lock{m_used_stripe_peers_mutex}; if (next_needed_pruning_stripe > 0 && next_needed_pruning_stripe <= (1ul << cryptonote::PRUNING_LOG_STRIPES) && !m_used_stripe_peers[next_needed_pruning_stripe-1].empty()) { @@ -1345,7 +1354,8 @@ namespace nodetool for (size_t i = 0; i < filtered.size(); ++i) { peerlist_entry pe; - if (zone.m_peerlist.get_white_peer_by_index(pe, filtered[i]) && pe.adr == na) + if (zone.m_peerlist.get_white_peer_by_index(pe, filtered[i]) && + pe.adr == na) { MDEBUG("Reusing stripe " << next_needed_pruning_stripe << " peer " << pe.adr.str()); random_index = i; @@ -1830,7 +1840,7 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - bool node_server::relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections) + bool node_server::relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections) { std::sort(connections.begin(), connections.end()); auto zone = m_network_zones.begin(); @@ -1855,7 +1865,7 @@ namespace nodetool } //----------------------------------------------------------------------------------- template - epee::net_utils::zone node_server::send_txs(std::vector txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs) + epee::net_utils::zone node_server::send_txs(std::vector txs, const epee::net_utils::zone origin, const connection_id_t& source, const bool pad_txs) { namespace enet = epee::net_utils; diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index f1ce9d5b2e2..e7b061860cb 100755 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -30,7 +30,6 @@ #pragma once -#include #include #include #include "cryptonote_basic/blobdatatype.h" @@ -41,21 +40,20 @@ namespace nodetool { - typedef boost::uuids::uuid uuid; - typedef boost::uuids::uuid net_connection_id; + using epee::connection_id_t; template struct i_p2p_endpoint { - virtual bool relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections)=0; - virtual epee::net_utils::zone send_txs(std::vector txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)=0; + virtual bool relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections) = 0; + virtual epee::net_utils::zone send_txs(std::vector txs, const epee::net_utils::zone origin, const connection_id_t& source, const bool pad_txs)=0; virtual bool invoke_command_to_peer(int command, const epee::span req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool invoke_notify_to_peer(int command, const epee::span req_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0; virtual void request_callback(const epee::net_utils::connection_context_base& context)=0; virtual uint64_t get_public_connections_count()=0; virtual void for_each_connection(std::function f)=0; - virtual bool for_connection(const boost::uuids::uuid&, std::function f)=0; + virtual bool for_connection(const connection_id_t&, std::function f)=0; virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0; virtual bool unblock_host(const epee::net_utils::network_address &address)=0; virtual std::map get_blocked_hosts()=0; @@ -69,11 +67,11 @@ namespace nodetool template struct p2p_endpoint_stub: public i_p2p_endpoint { - virtual bool relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections) + virtual bool relay_notify_to_list(int command, const epee::span data_buff, std::vector> connections) { return false; } - virtual epee::net_utils::zone send_txs(std::vector txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs) + virtual epee::net_utils::zone send_txs(std::vector txs, const epee::net_utils::zone origin, const connection_id_t& source, const bool pad_txs) { return epee::net_utils::zone::invalid; } @@ -97,7 +95,7 @@ namespace nodetool { } - virtual bool for_connection(const boost::uuids::uuid&, std::function f) + virtual bool for_connection(const connection_id_t&, std::function f) { return false; } diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index a78dd3c38d5..3bfe1514529 100755 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -30,14 +30,12 @@ #pragma once -#include #include "epee/serialization/keyvalue_serialization.h" #include "epee/net/net_utils_base.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" namespace nodetool { - using boost::uuids::uuid; using peerid_type = uint64_t; #pragma pack (push, 1) @@ -90,7 +88,7 @@ namespace nodetool struct basic_node_data { - uuid network_id; + epee::connection_id_t network_id; uint32_t my_port; peerid_type peer_id; From e2cfe3b17ad10757d207f82609f9dc8f0df168e9 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 8 May 2025 16:06:03 +0530 Subject: [PATCH 133/182] parser: sort storage_server_ping inputs in ascending order --- src/rpc/common/param_parser.hpp | 2 -- src/rpc/core_rpc_server_command_parser.cpp | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/rpc/common/param_parser.hpp b/src/rpc/common/param_parser.hpp index 59f248bd4d2..da4d61dbe01 100644 --- a/src/rpc/common/param_parser.hpp +++ b/src/rpc/common/param_parser.hpp @@ -20,8 +20,6 @@ namespace cryptonote::rpc { // Checks that key names are given in ascending order template void check_ascending_names(std::string_view name1, std::string_view name2, const Ignore&...) { - std::cout << "name1" << name1 << std::endl; - std::cout << "name2" << name2 << std::endl; if (!(name2 > name1)) throw std::runtime_error{"Internal error: request values must be retrieved in ascending order"}; } diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index df061557ecd..c4b00770119 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -258,11 +258,11 @@ namespace cryptonote::rpc { void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in){ get_values(in, - "ed25519_pubkey", required{storage_server_ping.request.pubkey_ed25519}, - "error", storage_server_ping.request.error, - "https_port", required{storage_server_ping.request.https_port}, - "omq_port", required{storage_server_ping.request.omq_port}, - "version", required{storage_server_ping.request.version}); + "error", storage_server_ping.request.error, + "https_port", required{storage_server_ping.request.https_port}, + "omq_port", required{storage_server_ping.request.omq_port}, + "pubkey_ed25519", required{storage_server_ping.request.pubkey_ed25519}, + "version", required{storage_server_ping.request.version}); } void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in){ From 3090d1acf1fe3b45cac16cdcf30c9e951c69c160 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 9 May 2025 01:41:17 +0530 Subject: [PATCH 134/182] rpc request for names_to_owner fixed for new format --- src/cryptonote_core/master_node_list.cpp | 2 +- src/rpc/core_rpc_server.cpp | 1 + src/simplewallet/simplewallet.cpp | 196 ++++++------------- src/wallet/wallet2.cpp | 10 +- src/wallet/wallet_rpc_server.cpp | 132 +++++-------- src/wallet/wallet_rpc_server_commands_defs.h | 10 +- 6 files changed, 118 insertions(+), 233 deletions(-) diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index 856ae513e43..b6ee2b9ad4b 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -3742,7 +3742,7 @@ namespace master_nodes crypto::generate_signature(hash, keys.pub, keys.key, signature); std::stringstream stream; - if (make_friendly) + if (make_friendly) //TODO have to fix { stream << tr("Run this command in the operator wallet") << " (" << cryptonote::get_account_address_as_str(nettype, false, contributor_args.addresses[0]) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index ea0dc6114e3..152e7188edf 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -3245,6 +3245,7 @@ namespace cryptonote::rpc { for (auto const &record : records) { auto& elem = names_to_owners.response["result"].emplace_back(); + elem["entry_index"] = request_index; elem["name_hash"] = record.name_hash; elem["owner"] = record.owner.to_string(nettype()); if (record.backup_owner) elem["backup_owner"] = record.backup_owner.to_string(nettype()); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 99744c6458d..f60cbff048e 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -7221,18 +7221,14 @@ bool simple_wallet::bns_lookup(std::vector args) return true; } - nlohmann::json req_params{ - {"entries", {}} - }; + std::vector name_hash{}; for (auto& name : args) { name = tools::lowercase_ascii_string(std::move(name)); - req_params["entries"].emplace_back(nlohmann::json{ - {"name_hash", bns::name_to_base64_hash(name)} - }); + name_hash.push_back(bns::name_to_base64_hash(name)); } - auto [success, response] = m_wallet->bns_names_to_owners(req_params); + auto [success, response] = m_wallet->bns_names_to_owners({{"name_hash", name_hash}}); if (!success) { fail_msg_writer() << "Connection to daemon failed when requesting BNS owners"; @@ -7240,147 +7236,65 @@ bool simple_wallet::bns_lookup(std::vector args) } int last_index = -1; - for (auto const &mapping : response) - { - auto enc_bchat_hex = mapping["encrypted_bchat_value"].get(); - if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_bchat_hex) || enc_bchat_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) - { - fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; - return false; - } - - auto enc_wallet_hex = mapping["encrypted_wallet_value"].get(); - if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_wallet_hex) || enc_wallet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) - { - fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; - return false; - } - - auto enc_belnet_hex = mapping["encrypted_belnet_value"].get(); - if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_belnet_hex) || enc_belnet_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) - { - fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; - return false; - } - - auto enc_eth_hex = mapping["encrypted_eth_addr_value"].get(); - if (mapping["entry_index"].get() >= args.size() || !oxenc::is_hex(enc_eth_hex) || enc_eth_hex.size() > 2*bns::mapping_value::BUFFER_SIZE) - { - fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; - return false; - } - - // Print any skipped (i.e. not registered) results: - for (size_t i = last_index + 1; i < mapping["entry_index"]; i++) - fail_msg_writer() << args[i] << " not found\n"; - last_index = mapping["entry_index"]; - - const auto& name = args[mapping["entry_index"]]; - - //BCHAT - bns::mapping_value value_bchat{}; - { - if (!enc_bchat_hex.empty()) - { - value_bchat.len = enc_bchat_hex.size() / 2; - value_bchat.encrypted = true; - oxenc::from_hex(enc_bchat_hex.begin(), enc_bchat_hex.end(), value_bchat.buffer.begin()); - - if (!value_bchat.decrypt(name, bns::mapping_type::bchat)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_bchat_hex; - return false; - } - } - } - - //WALLET - bns::mapping_value value_wallet{}; - { - if (!enc_wallet_hex.empty()) - { - value_wallet.len = enc_wallet_hex.size() / 2; - value_wallet.encrypted = true; - oxenc::from_hex(enc_wallet_hex.begin(), enc_wallet_hex.end(), value_wallet.buffer.begin()); - - if (!value_wallet.decrypt(name, bns::mapping_type::wallet)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_wallet_hex; - return false; - } - } - } - - //BELNET - bns::mapping_value value_belnet{}; - { - if (!enc_belnet_hex.empty()) - { - value_belnet.len = enc_belnet_hex.size() / 2; - value_belnet.encrypted = true; - oxenc::from_hex(enc_belnet_hex.begin(), enc_belnet_hex.end(), value_belnet.buffer.begin()); - - if (!value_belnet.decrypt(name, bns::mapping_type::belnet)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_belnet_hex; + for (const auto& mapping : response["result"]) { + // Print any skipped (i.e. not registered) results: + for (size_t i = last_index + 1; i < mapping["entry_index"]; i++) + fail_msg_writer() << args[i] << " not found\n"; + last_index = mapping["entry_index"]; + + const std::string name = args[mapping["entry_index"]]; + std::string name_hash_b64 = mapping["name_hash"].get(); + std::string owner = mapping["owner"]; + std::string enc_bchat_hex = mapping["encrypted_bchat_value"]; + std::string enc_wallet_hex = mapping["encrypted_wallet_value"]; + std::string enc_belnet_hex = mapping["encrypted_belnet_value"]; + std::string enc_eth_hex = mapping["encrypted_eth_addr_value"]; + + auto validate = [&](const std::string& hex) { + return oxenc::is_hex(hex) && hex.size() <= 2 * bns::mapping_value::BUFFER_SIZE; + }; + + if ((!enc_bchat_hex.empty() && !validate(enc_bchat_hex)) || + (!enc_wallet_hex.empty() && !validate(enc_wallet_hex)) || + (!enc_belnet_hex.empty() && !validate(enc_belnet_hex)) || + (!enc_eth_hex.empty() && !validate(enc_eth_hex))) { + fail_msg_writer() << "Received invalid BNS mapping data from beldexd"; return false; - } } - } - - //ETH_ADDRESS - bns::mapping_value value_eth{}; - { - if (!enc_eth_hex.empty()) - { - value_eth.len = enc_eth_hex.size() / 2; - value_eth.encrypted = true; - oxenc::from_hex(enc_eth_hex.begin(), enc_eth_hex.end(), value_eth.buffer.begin()); - if (!value_eth.decrypt(name, bns::mapping_type::eth_addr)) - { - fail_msg_writer() << "Failed to decrypt the mapping eth_value=" << enc_eth_hex; + auto decrypt = [&](const std::string& hex, bns::mapping_type type, bns::mapping_value& out_val) -> bool { + if (hex.empty()) return true; + out_val.len = hex.size() / 2; + out_val.encrypted = true; + oxenc::from_hex(hex.begin(), hex.end(), out_val.buffer.begin()); + return out_val.decrypt(name, type); + }; + + bns::mapping_value val_bchat, val_wallet, val_belnet, val_eth; + if (!decrypt(enc_bchat_hex, bns::mapping_type::bchat, val_bchat) || + !decrypt(enc_wallet_hex, bns::mapping_type::wallet, val_wallet) || + !decrypt(enc_belnet_hex, bns::mapping_type::belnet, val_belnet) || + !decrypt(enc_eth_hex, bns::mapping_type::eth_addr, val_eth)) { + fail_msg_writer() << "Decryption failed for one of the BNS mapping values."; return false; - } } - } - auto writer = tools::msg_writer(); - writer - << fmt::format(fg(fmt::color::sky_blue), " Name : {}", name); - if(!enc_bchat_hex.empty()) writer - << "\n Value bchat : " << value_bchat.to_readable_value(m_wallet->nettype(), bns::mapping_type::bchat); - if(!enc_wallet_hex.empty()) writer - << "\n Value wallet : " << value_wallet.to_readable_value(m_wallet->nettype(), bns::mapping_type::wallet); - if(!enc_belnet_hex.empty()) writer - << "\n Value belnet : " << value_belnet.to_readable_value(m_wallet->nettype(), bns::mapping_type::belnet); - if(!enc_eth_hex.empty()) writer - << "\n Value ethAddress : " << value_eth.to_readable_value(m_wallet->nettype(), bns::mapping_type::eth_addr); - writer - << "\n Owner : " << mapping["owner"]; - if (mapping["backup_owner"]) writer - << "\n Backup owner : " << mapping["backup_owner"]; - writer - << "\n Last updated height : " << mapping["update_height"]; - if (mapping["expiration_height"]) writer - << "\n Expiration height : " << mapping["expiration_height"]; - writer - << "\n Encrypted bchat value : " << (enc_bchat_hex.empty() ? "(none)" :enc_bchat_hex); - writer - << "\n Encrypted wallet value : " << (enc_wallet_hex.empty() ? "(none)" :enc_wallet_hex); - writer - << "\n Encrypted belnet value : " << (enc_belnet_hex.empty() ? "(none)" :enc_belnet_hex); - writer - << "\n Encrypted Eth value : " << (enc_eth_hex.empty() ? "(none)" :enc_eth_hex); - writer - << "\n"; - - tools::wallet2::bns_detail detail = - { - name, - mapping["name_hash"]}; - m_wallet->set_bns_cache_record(detail); + auto writer = tools::msg_writer(); + writer << fmt::format(fg(fmt::color::sky_blue), " Name Hash : {}", name_hash_b64); + if (!enc_bchat_hex.empty()) writer << "\n Value bchat : " << val_bchat.to_readable_value(m_wallet->nettype(), bns::mapping_type::bchat); + if (!enc_wallet_hex.empty()) writer << "\n Value wallet : " << val_wallet.to_readable_value(m_wallet->nettype(), bns::mapping_type::wallet); + if (!enc_belnet_hex.empty()) writer << "\n Value belnet : " << val_belnet.to_readable_value(m_wallet->nettype(), bns::mapping_type::belnet); + if (!enc_eth_hex.empty()) writer << "\n Value ethAddress : " << val_eth.to_readable_value(m_wallet->nettype(), bns::mapping_type::eth_addr); + writer << "\n Owner : " << owner; + if (mapping.contains("backup_owner")) writer << "\n Backup owner : " << mapping["backup_owner"]; + writer << "\n Last updated height : " << mapping["update_height"]; + if (mapping.contains("expiration_height")) writer << "\n Expiration height : " << mapping["expiration_height"]; + writer << "\n"; + + tools::wallet2::bns_detail detail = {name, name_hash_b64}; + m_wallet->set_bns_cache_record(detail); } + for (size_t i = last_index + 1; i < args.size(); i++) fail_msg_writer() << args[i] << " not found\n"; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 323b3c9a8c9..a47bc17a436 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8813,13 +8813,9 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons return {}; { - nlohmann::json req_params{ - {"entries", { - {"name_hash", oxenc::to_base64(tools::view_guts(result.name_hash))}, - } - }, - }; - auto [success, response_] = wallet.bns_names_to_owners(req_params); + std::vector name_hash{}; + name_hash.push_back(oxenc::to_base64(tools::view_guts(result.name_hash))); + auto [success, response_] = wallet.bns_names_to_owners({{"name_hash", name_hash}}); if (!response) response = &response_; else diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 4136a4df8c0..bfe1098da83 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3316,106 +3316,80 @@ namespace { } auto nettype = m_wallet->nettype(); - nlohmann::json req_params{ - {"include_expired", req.include_expired }, - {"entries", {}} - }; + nlohmann::json req_params{{"include_expired", req.include_expired}}; + auto& name_hash = (req_params["name_hash"] = nlohmann::json::array()); + name_hash.get_ref().reserve( + std::min(rpc::BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES, res.known_names.size())); uint64_t curr_height = req.include_expired ? m_wallet->get_blockchain_current_height() : 0; // Query beldexd for the full record info for (auto it = res.known_names.begin(); it != res.known_names.end(); ) { - const size_t num_entries = std::distance(it, res.known_names.end()); - const auto end = num_entries < rpc::BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES + const size_t remaining = std::distance(it, res.known_names.end()); + const auto batch_end = remaining < rpc::BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES ? res.known_names.end() : it + rpc::BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES; - for (auto it2 = it; it2 != end; it2++) - { - auto& name_hash = req_params["entries"].emplace_back(nlohmann::json{ - {"name_hash", it2->hashed}, - }); - } + + // Build name_hash list for the batch + name_hash.clear(); + for (auto it2 = it; it2 != batch_end; ++it2) + name_hash.push_back(it2->hashed); if (auto [success, records] = m_wallet->bns_names_to_owners(req_params); success) { - size_t type_offset = std::distance(res.known_names.begin(), it); - for (auto& rec : records) - { - if (rec["entry_index"].get() >= num_entries) - { - MWARNING("Got back invalid entry_index " << rec["entry_index"] << " for a request for " << num_entries << " entries"); - continue; - } + size_t base_index = std::distance(res.known_names.begin(), it); - auto& res_e = *(it + rec["entry_index"]); - res_e.owner = std::move(rec["owner"]); - res_e.backup_owner = std::move(rec["backup_owner"]); - res_e.encrypted_bchat_value = std::move(rec["encrypted_bchat_value"]); - res_e.encrypted_wallet_value = std::move(rec["encrypted_wallet_value"]); - res_e.encrypted_belnet_value = std::move(rec["encrypted_belnet_value"]); - res_e.encrypted_eth_addr_value = std::move(rec["encrypted_eth_addr_value"]); - res_e.update_height = rec["update_height"]; - res_e.expiration_height = rec["expiration_height"]; - if (req.include_expired && res_e.expiration_height) - res_e.expired = *res_e.expiration_height < curr_height; - res_e.txid = std::move(rec["txid"]); + auto decrypt_and_store = [&](const std::optional& encrypted, bns::mapping_type type, std::optional& out_value, const std::string& name) + { + if (!req.decrypt || !encrypted || encrypted->empty() || !oxenc::is_hex(*encrypted)) + return; + + bns::mapping_value value; + std::string errmsg; + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(*encrypted), &value, &errmsg) && + value.decrypt(name, type)) + out_value = value.to_readable_value(nettype, type); + else + MWARNING("Failed to decrypt BNS value for " << name << (errmsg.empty() ? ""s : ": " + errmsg)); + }; - //BCHAT - if (req.decrypt && !res_e.encrypted_bchat_value.empty() && oxenc::is_hex(res_e.encrypted_bchat_value)) + for (const auto& rec : records["result"]) + { + size_t index = rec["entry_index"].get(); + if (index >= remaining) { - bns::mapping_value value; - const auto type = bns::mapping_type::bchat; - std::string errmsg; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(res_e.encrypted_bchat_value), &value, &errmsg) - && value.decrypt(res_e.name, type)) - res_e.value_bchat = value.to_readable_value(nettype, type); - else - MWARNING("Failed to decrypt BNS value for " << res_e.name << (errmsg.empty() ? ""s : ": " + errmsg)); + MWARNING("Invalid entry_index " << index << " for batch size " << remaining); + continue; } - //ETH_ADDR - if (req.decrypt && !res_e.encrypted_eth_addr_value.empty() && oxenc::is_hex(res_e.encrypted_eth_addr_value)) - { - bns::mapping_value value; - const auto type = bns::mapping_type::eth_addr; - std::string errmsg; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(res_e.encrypted_eth_addr_value), &value, &errmsg) - && value.decrypt(res_e.name, type)) - res_e.value_eth_addr = value.to_readable_value(nettype, type); - else - MWARNING("Failed to decrypt BNS value for " << res_e.name << (errmsg.empty() ? ""s : ": " + errmsg)); - } + auto& res_e = *(it + index); + res_e.owner = rec["owner"]; + if (rec.contains("backup_owner") && !rec["backup_owner"].is_null()) + res_e.backup_owner = rec["backup_owner"].get(); + if (rec.contains("encrypted_bchat_value") && !rec["encrypted_bchat_value"].empty()) + res_e.encrypted_bchat_value = rec["encrypted_bchat_value"]; + if (rec.contains("encrypted_wallet_value") && !rec["encrypted_wallet_value"].empty()) + res_e.encrypted_wallet_value = rec["encrypted_wallet_value"]; + if (rec.contains("encrypted_belnet_value") && !rec["encrypted_belnet_value"].empty()) + res_e.encrypted_belnet_value = rec["encrypted_belnet_value"]; + if (rec.contains("encrypted_eth_addr_value") && !rec["encrypted_eth_addr_value"].empty()) + res_e.encrypted_eth_addr_value = rec["encrypted_eth_addr_value"]; + res_e.update_height = rec["update_height"].get(); + res_e.expiration_height = rec["expiration_height"].get(); + res_e.txid = rec["txid"]; - //WALLET - if (req.decrypt && !res_e.encrypted_wallet_value.empty() && oxenc::is_hex(res_e.encrypted_wallet_value)) - { - bns::mapping_value value; - const auto type = bns::mapping_type::wallet; - std::string errmsg; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(res_e.encrypted_wallet_value), &value, &errmsg) - && value.decrypt(res_e.name, type)) - res_e.value_wallet = value.to_readable_value(nettype, type); - else - MWARNING("Failed to decrypt BNS value for " << res_e.name << (errmsg.empty() ? ""s : ": " + errmsg)); - } + if (req.include_expired && res_e.expiration_height) + res_e.expired = res_e.expiration_height < curr_height; - //BELNET - if (req.decrypt && !res_e.encrypted_belnet_value.empty() && oxenc::is_hex(res_e.encrypted_belnet_value)) - { - bns::mapping_value value; - const auto type = bns::mapping_type::belnet; - std::string errmsg; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(res_e.encrypted_belnet_value), &value, &errmsg) - && value.decrypt(res_e.name, type)) - res_e.value_belnet = value.to_readable_value(nettype, type); - else - MWARNING("Failed to decrypt BNS value for " << res_e.name << (errmsg.empty() ? ""s : ": " + errmsg)); - } + decrypt_and_store(res_e.encrypted_bchat_value, bns::mapping_type::bchat, res_e.value_bchat, res_e.name); + decrypt_and_store(res_e.encrypted_eth_addr_value, bns::mapping_type::eth_addr, res_e.value_eth_addr, res_e.name); + decrypt_and_store(res_e.encrypted_wallet_value, bns::mapping_type::wallet, res_e.value_wallet, res_e.name); + decrypt_and_store(res_e.encrypted_belnet_value, bns::mapping_type::belnet, res_e.value_belnet, res_e.name); } } - it = end; + it = batch_end; } // Erase anything we didn't get a response for (it will have update_height of 0) diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index a3ab94244a0..d1ce2b4459e 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -2413,16 +2413,16 @@ This command is only required if the open wallet is one of the owners of a BNS r std::string name; // The plaintext name std::string owner; // The public key that purchased the Beldex Name Service entry. std::optional backup_owner; // The backup public key or wallet that the owner specified when purchasing the Beldex Name Service entry. Omitted if no backup owner. - std::string encrypted_bchat_value; // The encrypted value of bchat that the name maps to, in hex. - std::string encrypted_wallet_value; // The encrypted value of wallet that the name maps to, in hex. - std::string encrypted_belnet_value; // The encrypted value of belnet that the name maps to, in hex. - std::string encrypted_eth_addr_value; + std::optional encrypted_bchat_value; // The encrypted value of bchat that the name maps to, in hex. + std::optional encrypted_wallet_value; // The encrypted value of wallet that the name maps to, in hex. + std::optional encrypted_belnet_value; // The encrypted value of belnet that the name maps to, in hex. + std::optional encrypted_eth_addr_value; std::optional value_bchat; // Decrypted value that that name maps to. Only provided if `decrypt: true` was specified in the request. std::optional value_wallet; // Decrypted value that that name maps to. Only provided if `decrypt: true` was specified in the request. std::optional value_belnet; // Decrypted value that that name maps to. Only provided if `decrypt: true` was specified in the request. std::optional value_eth_addr; uint64_t update_height; // The last height that this Beldex Name Service entry was updated on the Blockchain. - std::optional expiration_height; // For records that expire, this will be set to the expiration block height. + uint64_t expiration_height; // For records that expire, this will be set to the expiration block height. std::optional expired; // Indicates whether the record has expired. Only included in the response if "include_expired" is specified in the request. std::string txid; // The txid of the mapping's most recent update or purchase. From 816f139509cfbf8a6705289ee0eb39dc1426aad5 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 9 May 2025 15:13:32 +0530 Subject: [PATCH 135/182] bns_names_to_owners fix for bns_lookup, bns_update, bns_renew --- src/simplewallet/simplewallet.cpp | 274 ++++++++++++------------------ src/wallet/node_rpc_proxy.cpp | 14 +- src/wallet/wallet2.cpp | 53 +++--- src/wallet/wallet_rpc_server.cpp | 2 +- 4 files changed, 149 insertions(+), 194 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index f60cbff048e..11196032638 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -6808,7 +6808,7 @@ bool simple_wallet::bns_renew_mapping(std::vector args) std::optional blocks = bns::expiry_blocks(m_wallet->nettype(), *mapping_years); fmt::print(fmt::format(tr("Renewal {} {} ({} blocks)\n"),(years > 1) ? "years :" : "year :", years, *blocks)); - fmt::print(fmt::format(tr("New expiry : Block {}\n"), (response[0]["expiration_height"].get() + *blocks))); + fmt::print(fmt::format(tr("New expiry : Block {}\n"), (response["expiration_height"].get() + *blocks))); std::cout << std::flush; if (!confirm_and_send_tx(dsts, ptx_vector, false /*flash*/)) @@ -6891,100 +6891,48 @@ bool simple_wallet::bns_update_mapping(std::vector args) return true; } - auto enc_bchat_hex = response[0]["encrypted_bchat_value"].get(); - if (!oxenc::is_hex(enc_bchat_hex) || enc_bchat_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) - { - LOG_ERROR("invalid BNS data returned from beldexd"); - fail_msg_writer() << tr("invalid BNS data returned from beldexd"); - return true; - } - - auto enc_wallet_hex = response[0]["encrypted_wallet_value"].get(); - if (!oxenc::is_hex(enc_wallet_hex) || enc_wallet_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) - { - LOG_ERROR("invalid BNS data returned from beldexd"); - fail_msg_writer() << tr("invalid BNS data returned from beldexd"); - return true; - } - - auto enc_belnet_hex = response[0]["encrypted_belnet_value"].get(); - if (!oxenc::is_hex(enc_belnet_hex) || enc_belnet_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) - { - LOG_ERROR("invalid BNS data returned from beldexd"); - fail_msg_writer() << tr("invalid BNS data returned from beldexd"); + auto validate_encrypted_hex = [&](const std::string& label, const std::string& hex) -> bool { + if (!oxenc::is_hex(hex) || hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) { + LOG_ERROR("Invalid BNS data for " << label << " returned from beldexd"); + fail_msg_writer() << tr("Invalid BNS data returned from beldexd"); + return false; + } return true; - } + }; - auto enc_eth_hex = response[0]["encrypted_eth_addr_value"].get(); - if (!oxenc::is_hex(enc_eth_hex) || enc_eth_hex.size() > 2 * bns::mapping_value::BUFFER_SIZE) - { - LOG_ERROR("invalid BNS data returned from beldexd"); - fail_msg_writer() << tr("invalid BNS data returned from beldexd"); + auto enc_bchat_hex = response["encrypted_bchat_value"].get(); + auto enc_wallet_hex = response["encrypted_wallet_value"].get(); + auto enc_belnet_hex = response["encrypted_belnet_value"].get(); + auto enc_eth_hex = response["encrypted_eth_addr_value"].get(); + + if (!validate_encrypted_hex("bchat", enc_bchat_hex) || + !validate_encrypted_hex("wallet", enc_wallet_hex) || + !validate_encrypted_hex("belnet", enc_belnet_hex) || + !validate_encrypted_hex("eth_addr", enc_eth_hex)) { return true; } - //BCHAT - bns::mapping_value bchat{}; - { - if (!enc_bchat_hex.empty()) - { - bchat.len = enc_bchat_hex.size() / 2; - bchat.encrypted = true; - oxenc::from_hex(enc_bchat_hex.begin(), enc_bchat_hex.end(), bchat.buffer.begin()); - if (!bchat.decrypt(tools::lowercase_ascii_string(name), bns::mapping_type::bchat)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_bchat_hex; - return false; - } - } - } - - //WALLET - bns::mapping_value wallet{}; - { - if (!enc_wallet_hex.empty()) - { - wallet.len = enc_wallet_hex.size() / 2; - wallet.encrypted = true; - oxenc::from_hex(enc_wallet_hex.begin(), enc_wallet_hex.end(), wallet.buffer.begin()); - if (!wallet.decrypt(tools::lowercase_ascii_string(name), bns::mapping_type::wallet)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_wallet_hex; - return false; - } - } - } - - //BELNET - bns::mapping_value belnet{}; - { - if (!enc_belnet_hex.empty()) - { - belnet.len = enc_belnet_hex.size() / 2; - belnet.encrypted = true; - oxenc::from_hex(enc_belnet_hex.begin(), enc_belnet_hex.end(), belnet.buffer.begin()); - if (!belnet.decrypt(tools::lowercase_ascii_string(name), bns::mapping_type::belnet)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_belnet_hex; - return false; - } - } - } - - //ETH_ADDRESS - bns::mapping_value eth_addr{}; - { - if (!enc_eth_hex.empty()) - { - eth_addr.len = enc_eth_hex.size() / 2; - eth_addr.encrypted = true; - oxenc::from_hex(enc_eth_hex.begin(), enc_eth_hex.end(), eth_addr.buffer.begin()); - if (!eth_addr.decrypt(tools::lowercase_ascii_string(name), bns::mapping_type::eth_addr)) - { - fail_msg_writer() << "Failed to decrypt the mapping value=" << enc_eth_hex; - return false; - } + auto decrypt_mapping = [&](const std::string& hex, bns::mapping_type type, bns::mapping_value& out) -> bool { + if (hex.empty()) return true; + + out.len = hex.size() / 2; + out.encrypted = true; + oxenc::from_hex(hex.begin(), hex.end(), out.buffer.begin()); + + if (!out.decrypt(tools::lowercase_ascii_string(name), type)) { + fail_msg_writer() << "Failed to decrypt the mapping value=" << hex; + return false; } + + return true; + }; + + bns::mapping_value bchat{}, wallet{}, belnet{}, eth_addr{}; + if (!decrypt_mapping(enc_bchat_hex, bns::mapping_type::bchat, bchat) || + !decrypt_mapping(enc_wallet_hex, bns::mapping_type::wallet, wallet) || + !decrypt_mapping(enc_belnet_hex, bns::mapping_type::belnet, belnet) || + !decrypt_mapping(enc_eth_hex, bns::mapping_type::eth_addr, eth_addr)) { + return false; } std::vector dsts; @@ -7041,22 +6989,22 @@ bool simple_wallet::bns_update_mapping(std::vector args) if (owner.size()) { - fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Owner : {}\n"), response[0]["owner"])); + fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Owner : {}\n"), response["owner"].get())); fmt::print(fmt::fg(fmt::color::light_green),fmt::format(tr("New Owner : {}\n"), owner)); } else { - fmt::print(fmt::format(tr("Owner : {} (unchanged)\n"), response[0]["owner"])); + fmt::print(fmt::format(tr("Owner : {} (unchanged)\n"), response["owner"].get())); } if (backup_owner.size()) { - fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Backup Owner : {}\n"), response[0].value("backup_owner", ""))); + fmt::print(fmt::fg(fmt::color::red),fmt::format(tr("Old Backup Owner : {}\n"), response.value("backup_owner", ""))); fmt::print(fmt::fg(fmt::color::light_green),fmt::format(tr("New Backup Owner : {}\n"), backup_owner)); } else { - fmt::print(fmt::format(tr("Backup Owner : {} (unchanged)\n"), response[0].value("backup_owner", ""))); + fmt::print(fmt::format(tr("Backup Owner : {} (unchanged)\n"), response.value("backup_owner", ""))); } if (value_bchat.size() && (value_bchat == bchat.to_readable_value(m_wallet->nettype(), bns::mapping_type::bchat))) @@ -7236,7 +7184,7 @@ bool simple_wallet::bns_lookup(std::vector args) } int last_index = -1; - for (const auto& mapping : response["result"]) { + for (const auto& mapping : response) { // Print any skipped (i.e. not registered) results: for (size_t i = last_index + 1; i < mapping["entry_index"]; i++) fail_msg_writer() << args[i] << " not found\n"; @@ -7348,80 +7296,70 @@ bool simple_wallet::bns_by_owner(const std::vector& args) } auto nettype = m_wallet->nettype(); - for (auto const &entry : result["entries"]) - { - std::string_view name; - std::string value_bchat, value_wallet, value_belnet, value_eth; - if (auto got = cache.find(entry["name_hash"]); got != cache.end()) - { - name = got->second.name; - //BCHAT - { - bns::mapping_value mv; - const auto type = bns::mapping_type::bchat; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_bchat_value"].get()), &mv) - && mv.decrypt(name, type)) - value_bchat = mv.to_readable_value(nettype, type); - } - //WALLET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::wallet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_wallet_value"].get()), &mv) - && mv.decrypt(name, type)) - value_wallet = mv.to_readable_value(nettype,type); - } - //BELNET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::belnet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_belnet_value"].get()), &mv) - && mv.decrypt(name, type)) - value_belnet = mv.to_readable_value(nettype, type); - } - - //ETH_ADDRESS - { - bns::mapping_value mv; - const auto type = bns::mapping_type::eth_addr; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_eth_addr_value"].get()), &mv) - && mv.decrypt(name, type)) - value_eth = mv.to_readable_value(nettype, type); - } - } - auto writer = tools::msg_writer(); - writer - << fmt::format(fg(fmt::color::sky_blue) , " Name (hashed) : {}", entry["name_hash"]); - if (!name.empty()) writer - << fmt::format(fg(fmt::color::sky_blue) , "\n Name : {}", name); - if (!value_bchat.empty()) writer - << "\n Value bchat : " << value_bchat; - if (!value_wallet.empty()) writer - << "\n Value wallet : " << value_wallet; - if (!value_belnet.empty()) writer - << "\n Value belnet : " << value_belnet; - if (!value_eth.empty()) writer - << "\n Value ethAddress : " << value_eth; - writer - << "\n Owner : " << entry["owner"]; - if (auto got = entry.find("backup_owner"); got != entry.end()) writer - << "\n Backup owner: " << entry["backup_owner"]; - writer - << "\n Last updated height : " << entry["update_height"]; - if (auto got = entry.find("expiration_height"); got != entry.end()) writer - << "\n Expiration height: " << entry["expiration_height"]; - writer - << "\n Encrypted bchat value : " << (entry["encrypted_bchat_value"].empty() ? "(none)" : entry["encrypted_bchat_value"]); - writer - << "\n Encrypted wallet value : " << (entry["encrypted_wallet_value"].empty() ? "(none)" : entry["encrypted_wallet_value"]); - writer - << "\n Encrypted belnet value : " << (entry["encrypted_belnet_value"].empty() ? "(none)" : entry["encrypted_belnet_value"]); - writer - << "\n Encrypted Eth value : " << (entry["encrypted_eth_addr_value"].empty() ? "(none)" : entry["encrypted_eth_addr_value"]); - writer - << "\n"; - } - return true; + for (const auto& entry : result["entries"]) + { + std::string_view name; + std::string value_bchat, value_wallet, value_belnet, value_eth; + + if (auto got = cache.find(entry["name_hash"]); got != cache.end()) + { + name = got->second.name; + + auto decrypt_value = [&](std::string_view key, bns::mapping_type type, std::string& out) { + auto it = entry.find(key); + if (it != entry.end() && !it->empty()) + { + bns::mapping_value mv; + const auto& hex_str = it->get_ref(); + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(hex_str), &mv) && + mv.decrypt(name, type)) + { + out = mv.to_readable_value(nettype, type); + } + } + }; + + decrypt_value("encrypted_bchat_value", bns::mapping_type::bchat, value_bchat); + decrypt_value("encrypted_wallet_value", bns::mapping_type::wallet, value_wallet); + decrypt_value("encrypted_belnet_value", bns::mapping_type::belnet, value_belnet); + decrypt_value("encrypted_eth_addr_value", bns::mapping_type::eth_addr, value_eth); + } + + auto writer = tools::msg_writer(); + writer << fmt::format(fg(fmt::color::sky_blue), " Name (hashed) : {}", entry["name_hash"]); + if (!name.empty()) writer << fmt::format(fg(fmt::color::sky_blue), "\n Name : {}", name); + + writer << "\n Owner : " << entry["owner"].get(); + + if (entry.contains("backup_owner") && !entry["backup_owner"].is_null()) + writer << "\n Backup owner : " << entry["backup_owner"].get(); + + if (!value_bchat.empty()) writer << "\n Value bchat : " << value_bchat; + if (!value_wallet.empty()) writer << "\n Value wallet : " << value_wallet; + if (!value_belnet.empty()) writer << "\n Value belnet : " << value_belnet; + if (!value_eth.empty()) writer << "\n Value ethAddress : " << value_eth; + + writer << "\n Last updated height : " << entry["update_height"]; + + if (auto it = entry.find("expiration_height"); it != entry.end()) + writer << "\n Expiration height : " << *it; + + // Print encrypted values only if they are present and non-empty + auto safe_print_enc = [&](std::string_view label, std::string_view key) { + auto it = entry.find(key); + if (it != entry.end() && !it->get_ref().empty()) + writer << "\n " << label << " : " << it->get(); + }; + + safe_print_enc("Encrypted bchat value", "encrypted_bchat_value"); + safe_print_enc("Encrypted wallet value", "encrypted_wallet_value"); + safe_print_enc("Encrypted belnet value", "encrypted_belnet_value"); + safe_print_enc("Encrypted Eth value", "encrypted_eth_addr_value"); + + writer << "\n"; + } + + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::coin_burn(std::vector args) diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index eedd4961790..786b8818458 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -401,10 +401,22 @@ std::pair NodeRPCProxy::bns_names_to_owners(nlohmann::json try { auto res = m_http_client.json_rpc("bns_names_to_owners", request); - resolved = res; + auto st_it = res.find("status"); + auto res_it = res.find("result"); + if (st_it == res.end() || res_it == res.end() || !st_it->is_string() || + !res_it->is_array()) { + // log::error(logcat, "Did not find expected result or status in:\n{}", res.dump()); + throw std::runtime_error{"Missing result or status"}; + } + if (auto status = st_it->get(); status != "OK") + throw std::runtime_error("Received error status"); + + resolved = res["result"]; } catch (...) { + // log::error(logcat, "Failed to get ONS info: {}", e.what()); return result; } + success = true; return result; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a47bc17a436..ba3e9610d94 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8744,7 +8744,7 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons bns::bns_tx_type txtype, uint32_t account_index, std::string *reason, - nlohmann::json *response) + nlohmann::json* record = nullptr) { bns_prepared_args result = {}; if (priority == tools::tx_priority_flash) @@ -8815,29 +8815,34 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons { std::vector name_hash{}; name_hash.push_back(oxenc::to_base64(tools::view_guts(result.name_hash))); - auto [success, response_] = wallet.bns_names_to_owners({{"name_hash", name_hash}}); - if (!response) - response = &response_; - else - *response = std::move(response_); - if (!success) - { - if (reason) *reason = "Failed to query previous owner for BNS entry: communication with daemon failed"; + auto [success, response] = wallet.bns_names_to_owners({{"name_hash", name_hash}}); + bool bad_resp = false; + nlohmann::json tmp; + if (!success || !response.is_array()) + bad_resp = true; + else + *record = std::move(response.front()); + + if (bad_resp) { + if (reason) + *reason = "Failed to query previous owner for BNS entry: communication with daemon failed"; return result; } - if ((*response)["entries"].size()) - { - if (!tools::hex_to_type((*response)["entries"][0]["txid"].get(), result.prev_txid)) - { - if (reason) *reason = "Failed to convert response txid=" + (*response)["entries"][0]["txid"].get() + " from the daemon into a 32 byte hash, it must be a 64 char hex string"; - return result; - } + const auto& rec = *record; + + if (!rec.is_null()) { + auto txid = rec["txid"].get(); + if (!tools::hex_to_type(txid, result.prev_txid)) { + if (reason) + *reason ="Failed to convert response txid=" + (std::string)txid + " from the daemon into a 32 byte hash, it must be a 64 char hex string"; + return result; + } } if ((txtype == bns::bns_tx_type::update && make_signature) || (txtype == bns::bns_tx_type::renew)) { - if (response->empty()) + if (rec.is_null()) { if (reason) *reason = "Signature requested when preparing BNS TX but record to update/renew does not exist"; return result; @@ -8845,18 +8850,18 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons cryptonote::address_parse_info curr_owner_parsed = {}; cryptonote::address_parse_info curr_backup_owner_parsed = {}; - auto& rowner = (*response)["entries"].front()["owner"]; - std::string* rbackup_owner = (*response)["entries"].front().value("backup_owner", nullptr); - bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner.get()); - bool curr_backup_owner = rbackup_owner && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), *rbackup_owner); + auto rowner = rec["owner"].get(); + std::string rbackup_owner = rec.value("backup_owner", ""); + bool curr_owner = cryptonote::get_account_address_from_str(curr_owner_parsed, wallet.nettype(), rowner); + bool curr_backup_owner = rbackup_owner.size() && cryptonote::get_account_address_from_str(curr_backup_owner_parsed, wallet.nettype(), rbackup_owner); if (!try_generate_bns_signature(wallet, rowner, owner, backup_owner, result)) { - if (!rbackup_owner || !try_generate_bns_signature(wallet, *rbackup_owner, owner, backup_owner, result)) + if (rbackup_owner.empty() || !try_generate_bns_signature(wallet, rbackup_owner, owner, backup_owner, result)) { if (reason) { - *reason = "Signature requested when preparing ONS TX, but this wallet is not the owner of the record owner=" + rowner.get(); - if (rbackup_owner) *reason += ", backup_owner=" + *rbackup_owner; + *reason = "Signature requested when preparing ONS TX, but this wallet is not the owner of the record owner=" + rowner; + if (rbackup_owner.empty()) *reason += ", backup_owner=" + rbackup_owner; } return result; } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index bfe1098da83..c23fd8374c0 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3354,7 +3354,7 @@ namespace { MWARNING("Failed to decrypt BNS value for " << name << (errmsg.empty() ? ""s : ": " + errmsg)); }; - for (const auto& rec : records["result"]) + for (const auto& rec : records) { size_t index = rec["entry_index"].get(); if (index >= remaining) From 9bb85da3dbd02060d1ccf8595d1f9a535798138b Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Sun, 11 May 2025 15:47:27 +0530 Subject: [PATCH 136/182] wallet-rpc bns_update, bns_renew and bns_buy fix in cli --- src/wallet/node_rpc_proxy.cpp | 6 ++---- src/wallet/wallet2.cpp | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 786b8818458..7b7cbf0eb13 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -402,11 +402,9 @@ std::pair NodeRPCProxy::bns_names_to_owners(nlohmann::json try { auto res = m_http_client.json_rpc("bns_names_to_owners", request); auto st_it = res.find("status"); - auto res_it = res.find("result"); - if (st_it == res.end() || res_it == res.end() || !st_it->is_string() || - !res_it->is_array()) { + if (st_it == res.end() || !st_it->is_string()) { // log::error(logcat, "Did not find expected result or status in:\n{}", res.dump()); - throw std::runtime_error{"Missing result or status"}; + throw std::runtime_error{"Missing status"}; } if (auto status = st_it->get(); status != "OK") throw std::runtime_error("Received error status"); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ba3e9610d94..3217f9251dd 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -8818,17 +8818,28 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons auto [success, response] = wallet.bns_names_to_owners({{"name_hash", name_hash}}); bool bad_resp = false; nlohmann::json tmp; - if (!success || !response.is_array()) + if (!success) bad_resp = true; - else - *record = std::move(response.front()); + else if(response.is_null()) { + if (record) + *record = nullptr; + else + record = &tmp; + } + else { + if (record) + *record = std::move(response.front()); + else + record = &response.front(); + } + if (bad_resp) { if (reason) *reason = "Failed to query previous owner for BNS entry: communication with daemon failed"; return result; } - + const auto& rec = *record; if (!rec.is_null()) { From b996d966b6a2cd48ad857038dc0b83e932e18ab6 Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Tue, 13 May 2025 15:01:49 +0530 Subject: [PATCH 137/182] refactor: optimize Boost functionality --- cmake/StaticBuild.cmake | 4 +- .../include/epee/net/abstract_tcp_server2.h | 10 ++-- .../include/epee/net/abstract_tcp_server2.inl | 59 ++++++++----------- .../include/epee/net/connection_basic.hpp | 5 ++ .../epee/net/levin_protocol_handler_async.h | 10 ++-- contrib/epee/include/epee/net/local_ip.h | 9 ++- src/cryptonote_core/blockchain.cpp | 5 +- src/cryptonote_core/blockchain.h | 10 +++- src/cryptonote_protocol/levin_notify.cpp | 21 ++++--- src/cryptonote_protocol/levin_notify.h | 6 +- src/net/socks.cpp | 23 ++++---- src/net/socks.h | 7 ++- src/p2p/net_node.h | 6 +- src/p2p/net_node.inl | 8 +-- 14 files changed, 101 insertions(+), 82 deletions(-) diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 46a231fdffc..7bc1d3d9d94 100755 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -5,12 +5,12 @@ set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") -set(BOOST_VERSION 1.83.0 CACHE STRING "boost version") +set(BOOST_VERSION 1.87.0 CACHE STRING "boost version") set(BOOST_MIRROR ${LOCAL_MIRROR} https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION}/source CACHE STRING "boost download mirror(s)") string(REPLACE "." "_" BOOST_VERSION_ ${BOOST_VERSION}) set(BOOST_SOURCE boost_${BOOST_VERSION_}.tar.bz2) -set(BOOST_HASH SHA256=6478edfe2f3305127cffe8caf73ea0176c53769f4bf1585be237eb30798c3b8e +set(BOOST_HASH SHA256=af57be25cb4c4f4b413ed692fe378affb4352ea50fbe294a11ef548f4d527d89 CACHE STRING "boost source hash") set(NCURSES_VERSION 6.2 CACHE STRING "ncurses version") diff --git a/contrib/epee/include/epee/net/abstract_tcp_server2.h b/contrib/epee/include/epee/net/abstract_tcp_server2.h index 9d6a66b8b78..44b7a74e393 100755 --- a/contrib/epee/include/epee/net/abstract_tcp_server2.h +++ b/contrib/epee/include/epee/net/abstract_tcp_server2.h @@ -302,7 +302,7 @@ namespace net_utils { auto ptr = std::make_shared>(io_service_, std::move(callback), timeout); //needed call handler here ?... - ptr->m_timer.expires_from_now(ptr->m_period); + ptr->m_timer.expires_after(ptr->m_period); ptr->m_timer.async_wait([this, ptr] (const boost::system::error_code&) { global_timer_handler(ptr); }); return true; } @@ -313,14 +313,14 @@ namespace net_utils //if handler return false - he don't want to be called anymore if(!ptr->call_handler()) return; - ptr->m_timer.expires_from_now(ptr->m_period); + ptr->m_timer.expires_after(ptr->m_period); ptr->m_timer.async_wait([this, ptr] (const boost::system::error_code&) { global_timer_handler(ptr); }); } template bool async_call(t_handler t_callback) { - io_service_.post(std::move(t_callback)); + boost::asio::post(io_service_, std::move(t_callback)); return true; } @@ -340,11 +340,11 @@ namespace net_utils struct worker { worker() - : io_service(), work(io_service) + : io_service(), work(io_service.get_executor()) {} boost::asio::io_service io_service; - boost::asio::io_service::work work; + boost::asio::executor_work_guard work; }; std::unique_ptr m_io_service_local_instance; boost::asio::io_service& io_service_; diff --git a/contrib/epee/include/epee/net/abstract_tcp_server2.inl b/contrib/epee/include/epee/net/abstract_tcp_server2.inl index 233ee0169b1..b4b23b0427e 100755 --- a/contrib/epee/include/epee/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/epee/net/abstract_tcp_server2.inl @@ -145,7 +145,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) if (remote_ep.address().is_v4()) { - const unsigned long ip_ = boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong()); + const unsigned long ip_ = boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_uint()); return start(is_income, is_multithreaded, ipv4_network_address{uint32_t(ip_), remote_ep.port()}); } else @@ -229,7 +229,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) if(!self) return false; - strand_.post(boost::bind(&connection::call_back_starter, self)); + strand_.post(boost::bind(&connection::call_back_starter, self), std::allocator{}); CATCH_ENTRY_L0("connection::request_callback()", false); return true; } @@ -607,11 +607,11 @@ PRAGMA_WARNING_DISABLE_VS(4355) return std::chrono::milliseconds{DEFAULT_TIMEOUT_MS_REMOTE >> shift}; } //--------------------------------------------------------------------------------- - template + template std::chrono::milliseconds connection::get_timeout_from_bytes_read(size_t bytes) { std::chrono::milliseconds ms{(unsigned)(bytes * TIMEOUT_EXTRA_MS_PER_BYTE)}; - if (auto remaining = std::chrono::duration_cast(m_timer.expires_at() - std::chrono::steady_clock::now()); + if (auto remaining = std::chrono::duration_cast(m_timer.expiry() - std::chrono::steady_clock::now()); remaining > 0ms) ms += remaining; if (ms > get_default_timeout()) @@ -658,11 +658,11 @@ PRAGMA_WARNING_DISABLE_VS(4355) } if (add) { - if (const auto cur = std::chrono::duration_cast(m_timer.expires_at() - std::chrono::steady_clock::now()); + if (const auto cur = std::chrono::duration_cast(m_timer.expiry() - std::chrono::steady_clock::now()); cur > 0s) ms += cur; } - m_timer.expires_from_now(ms); + m_timer.expires_after(ms); m_timer.async_wait([=](const boost::system::error_code& ec) { if(ec == boost::asio::error::operation_aborted) @@ -880,8 +880,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) try { boost::asio::ip::tcp::resolver resolver(io_service_); - boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast(port), boost::asio::ip::tcp::resolver::query::canonical_name); - boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); + boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, boost::lexical_cast(port), boost::asio::ip::tcp::resolver::canonical_name).begin(); acceptor_.open(endpoint.protocol()); #if !defined(_WIN32) acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); @@ -916,8 +915,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) { if (port_ipv6 == 0) port_ipv6 = port; // default arg means bind to same port as ipv4 boost::asio::ip::tcp::resolver resolver(io_service_); - boost::asio::ip::tcp::resolver::query query(address_ipv6, boost::lexical_cast(port_ipv6), boost::asio::ip::tcp::resolver::query::canonical_name); - boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); + boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address_ipv6, boost::lexical_cast(port_ipv6), boost::asio::ip::tcp::resolver::canonical_name).begin(); acceptor_ipv6.open(endpoint.protocol()); #if !defined(_WIN32) acceptor_ipv6.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); @@ -1232,7 +1230,7 @@ POP_WARNINGS sock_.open(remote_endpoint.protocol()); if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" ) { - boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(bind_ip.c_str()), 0); + boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::make_address_v4(bind_ip.c_str()), 0); boost::system::error_code ec; sock_.bind(local_endpoint, ec); if (ec) @@ -1318,13 +1316,12 @@ POP_WARNINGS bool try_ipv6 = false; boost::asio::ip::tcp::resolver resolver(io_service_); - boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); boost::system::error_code resolve_error; - boost::asio::ip::tcp::resolver::iterator iterator; + boost::asio::ip::tcp::resolver::results_type results; try { //resolving ipv4 address as ipv6 throws, catch here and move on - iterator = resolver.resolve(query, resolve_error); + results = resolver.resolve(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::canonical_name, resolve_error); } catch (const boost::system::system_error& e) { @@ -1342,8 +1339,8 @@ POP_WARNINGS std::string bind_ip_to_use; - boost::asio::ip::tcp::resolver::iterator end; - if(iterator == end) + + if(!results.size()) { if (!m_use_ipv6) { @@ -1363,11 +1360,9 @@ POP_WARNINGS if (try_ipv6) { - boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); - - iterator = resolver.resolve(query6, resolve_error); + results = resolver.resolve(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::canonical_name, resolve_error); - if(iterator == end) + if(!results.size()) { MERROR("Failed to resolve " << adr); return false; @@ -1390,7 +1385,7 @@ POP_WARNINGS MTRACE("Trying to connect to " << adr << ":" << port << ", bind_ip = " << bind_ip_to_use); //boost::asio::ip::tcp::endpoint remote_endpoint(boost::asio::ip::address::from_string(addr.c_str()), port); - boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); + boost::asio::ip::tcp::endpoint remote_endpoint(*(results.begin())); auto try_connect_result = try_connect(new_connection_l, adr, port, sock_, remote_endpoint, bind_ip_to_use, conn_timeout); if (try_connect_result == CONNECT_FAILURE) @@ -1434,13 +1429,13 @@ POP_WARNINGS bool try_ipv6 = false; boost::asio::ip::tcp::resolver resolver(io_service_); - boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); + boost::system::error_code resolve_error; - boost::asio::ip::tcp::resolver::iterator iterator; + boost::asio::ip::tcp::resolver::results_type results; try { //resolving ipv4 address as ipv6 throws, catch here and move on - iterator = resolver.resolve(query, resolve_error); + results = resolver.resolve(boost::asio::ip::tcp::v4(), adr, port, boost::asio::ip::tcp::resolver::canonical_name, resolve_error); } catch (const boost::system::system_error& e) { @@ -1456,9 +1451,7 @@ POP_WARNINGS throw; } - boost::asio::ip::tcp::resolver::iterator end; - - if(iterator == end) + if(!results.size()) { if (!try_ipv6) { @@ -1473,11 +1466,9 @@ POP_WARNINGS if (try_ipv6) { - boost::asio::ip::tcp::resolver::query query6(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::query::canonical_name); - - iterator = resolver.resolve(query6, resolve_error); + results = resolver.resolve(boost::asio::ip::tcp::v6(), adr, port, boost::asio::ip::tcp::resolver::canonical_name, resolve_error); - if(iterator == end) + if(!results.size()) { MERROR("Failed to resolve " << adr); return false; @@ -1485,12 +1476,12 @@ POP_WARNINGS } - boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); + boost::asio::ip::tcp::endpoint remote_endpoint(*(results.begin())); sock_.open(remote_endpoint.protocol()); if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" ) { - boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(bind_ip.c_str()), 0); + boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::make_address(bind_ip.c_str()), 0); boost::system::error_code ec; sock_.bind(local_endpoint, ec); if (ec) @@ -1504,7 +1495,7 @@ POP_WARNINGS std::shared_ptr sh_deadline(new boost::asio::steady_timer(io_service_)); //start deadline - sh_deadline->expires_from_now(std::chrono::milliseconds(conn_timeout)); + sh_deadline->expires_after(std::chrono::milliseconds(conn_timeout)); sh_deadline->async_wait([=](const boost::system::error_code& error) { if(error != boost::asio::error::operation_aborted) diff --git a/contrib/epee/include/epee/net/connection_basic.hpp b/contrib/epee/include/epee/net/connection_basic.hpp index 14f91f124ca..a517afe1dc0 100755 --- a/contrib/epee/include/epee/net/connection_basic.hpp +++ b/contrib/epee/include/epee/net/connection_basic.hpp @@ -49,6 +49,11 @@ #include #include +#if BOOST_VERSION >= 108700 +namespace boost::asio { + using io_service = io_context; +} +#endif #include "../shared_sv.h" diff --git a/contrib/epee/include/epee/net/levin_protocol_handler_async.h b/contrib/epee/include/epee/net/levin_protocol_handler_async.h index 33885ab2e93..c69371927d9 100755 --- a/contrib/epee/include/epee/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/epee/net/levin_protocol_handler_async.h @@ -182,7 +182,7 @@ class async_protocol_handler { if(m_con.start_outer_call()) { - m_timer.expires_from_now(std::chrono::milliseconds(timeout)); + m_timer.expires_after(std::chrono::milliseconds(timeout)); m_timer.async_wait([&con, command, cb, timeout](const boost::system::error_code& ec) { if(ec == boost::asio::error::operation_aborted) @@ -231,20 +231,18 @@ class async_protocol_handler if(!m_cancel_timer_called) { m_cancel_timer_called = true; - boost::system::error_code ignored_ec; - m_timer_cancelled = 1 == m_timer.cancel(ignored_ec); + m_timer_cancelled = 1 == m_timer.cancel(); } return m_timer_cancelled; } virtual void reset_timer() { - boost::system::error_code ignored_ec; - if (!m_cancel_timer_called && m_timer.cancel(ignored_ec) > 0) + if (!m_cancel_timer_called && m_timer.cancel() > 0) { callback_t& cb = m_cb; async_protocol_handler& con = m_con; int command = m_command; - m_timer.expires_from_now(m_timeout); + m_timer.expires_after(m_timeout); m_timer.async_wait([&con, cb, command, timeout=m_timeout](const boost::system::error_code& ec) { if(ec == boost::asio::error::operation_aborted) diff --git a/contrib/epee/include/epee/net/local_ip.h b/contrib/epee/include/epee/net/local_ip.h index e85c075a9f9..48ac5915900 100755 --- a/contrib/epee/include/epee/net/local_ip.h +++ b/contrib/epee/include/epee/net/local_ip.h @@ -57,11 +57,14 @@ namespace epee return is_link_local || is_unique_local_unicast; } - inline - bool is_ipv6_loopback(const std::string& ip) + inline bool is_ipv6_loopback(const std::string &ip) { - // ipv6 loopback is ::1 +// ipv6 loopback is ::1 +#if BOOST_VERSION >= 106600 + return boost::asio::ip::make_address_v6(ip).is_loopback(); +#else return boost::asio::ip::address_v6::from_string(ip).is_loopback(); +#endif } inline diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index e0cec5ef068..6437beab056 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -505,7 +505,7 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *bns_db, const network_type nett // create general purpose async service queue - m_async_work_idle = std::unique_ptr < boost::asio::io_service::work > (new boost::asio::io_service::work(m_async_service)); + m_async_work_idle = std::make_unique(m_async_service.get_executor()); m_async_thread = std::thread{[this] { m_async_service.run(); }}; #if defined(PER_BLOCK_CHECKPOINT) @@ -4899,7 +4899,8 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync) { m_sync_counter = 0; m_bytes_to_sync = 0; - m_async_service.dispatch([this] { return store_blockchain(); }); + m_async_service.get_executor().dispatch( + [this] { return store_blockchain(); }, std::allocator{}); } else if(m_db_sync_mode == db_sync) { diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 59b16b3728c..3597991e9a6 100755 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -29,7 +29,8 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once -#include +#include +#include #include #include @@ -39,6 +40,10 @@ #include #endif +namespace boost::asio { +using io_service = io_context; +} + #include #include #include @@ -1119,7 +1124,8 @@ namespace cryptonote boost::asio::io_service m_async_service; std::thread m_async_thread; - std::unique_ptr m_async_work_idle; + using work_type = boost::asio::executor_work_guard; + std::unique_ptr m_async_work_idle; // some invalid blocks std::set m_invalid_blocks; diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index a70c0e764dc..0b813a8c12d 100755 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -339,7 +339,7 @@ namespace cryptonote::levin for (auto id = zone->map.begin(); id != zone->map.end(); ++id) { const std::size_t i = id - zone->map.begin(); - zone->channels[i].strand.post(update_channel{zone, i, *id}); + zone->channels[i].strand.post(update_channel{zone, i, *id}, std::allocator{}); } } @@ -443,7 +443,8 @@ namespace cryptonote::levin if (connections.empty()) MWARNING("Lost all outbound connections to anonymity network - currently unable to send transaction(s)"); - zone_->strand.post(update_channels{zone_, std::move(connections)}); + zone_->strand.post( + update_channels{zone_, std::move(connections)}, std::allocator{}); } } @@ -471,8 +472,11 @@ namespace cryptonote::levin const auto start = std::chrono::steady_clock::now(); zone_->strand.dispatch( - change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}} - ); + change_channels{ + zone_, + net::dandelionpp::connection_map{ + get_out_connections(*(zone_->p2p)), count_}}, + std::allocator{}); detail::zone& alias = *zone_; alias.next_epoch.expires_at(start + min_epoch_ + random_duration(epoch_range_)); @@ -513,8 +517,7 @@ namespace cryptonote::levin return; zone_->strand.dispatch( - update_channels{zone_, get_out_connections(*(zone_->p2p))} - ); + update_channels{zone_, get_out_connections(*(zone_->p2p))}, std::allocator{}); } void notify::run_epoch() @@ -567,8 +570,7 @@ namespace cryptonote::levin for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel) { zone_->channels[channel].strand.dispatch( - queue_covert_notify{zone_, message, channel} - ); + queue_covert_notify{zone_, message, channel}, std::allocator{}); } } else @@ -579,7 +581,8 @@ namespace cryptonote::levin epee::levin::make_notify(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan(payload))}; // traditional monero send technique - zone_->strand.dispatch(flood_notify{zone_, std::move(message), source}); + zone_->strand.dispatch( + flood_notify{zone_, std::move(message), source}, std::allocator{}); } return true; diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index 2d42bf1d73f..3fec6b72d96 100755 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -28,7 +28,7 @@ #pragma once -#include +#include #include #include @@ -38,6 +38,10 @@ #include "epee/span.h" #include "epee/net/net_utils_base.h" +namespace boost::asio { +using io_service = io_context; +} + namespace epee { namespace levin diff --git a/src/net/socks.cpp b/src/net/socks.cpp index 46179f503ac..72735b79915 100755 --- a/src/net/socks.cpp +++ b/src/net/socks.cpp @@ -177,8 +177,8 @@ namespace socks { std::shared_ptr self_; - static boost::asio::mutable_buffers_1 get_buffer(client& self) noexcept - { + static boost::asio::mutable_buffer get_buffer(client& self) noexcept { + static_assert(sizeof(v4_header) <= sizeof(self.buffer_), "buffer too small for v4 response"); return boost::asio::buffer(self.buffer_, sizeof(v4_header)); } @@ -202,8 +202,7 @@ namespace socks { std::shared_ptr self_; - static boost::asio::const_buffers_1 get_buffer(client const& self) noexcept - { + static boost::asio::const_buffer get_buffer(client const& self) noexcept { return boost::asio::buffer(self.buffer_, self.buffer_size_); } @@ -318,14 +317,14 @@ namespace socks if (self_ && error != boost::system::errc::operation_canceled) { const std::shared_ptr self = std::move(self_); - self->strand_.dispatch([self] () - { - if (self && self->proxy_.is_open()) - { - self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); - self->proxy_.close(); - } - }); + self->strand_.dispatch( + [self]() { + if (self && self->proxy_.is_open()) { + self->proxy_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); + self->proxy_.close(); + } + }, + std::allocator{}); } } } // socks diff --git a/src/net/socks.h b/src/net/socks.h index d9ffe26c088..dc590a82f44 100755 --- a/src/net/socks.h +++ b/src/net/socks.h @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include #include @@ -41,6 +41,11 @@ #include "net/fwd.h" #include "epee/span.h" +namespace boost::asio { +using io_service = io_context; +} + + namespace epee { namespace net_utils diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 7a2f88c896c..e4d704b71ff 100755 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -31,7 +31,7 @@ #pragma once #include #include -#include +#include #include #include #include @@ -60,6 +60,10 @@ PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) +namespace boost::asio { +using io_service = io_context; +} + namespace nodetool { struct proxy diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 96088ee4604..0a10fbeeb0d 100755 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -547,18 +547,18 @@ namespace nodetool io_service io_srv; ip::tcp::resolver resolver(io_srv); - ip::tcp::resolver::query query(host, port, boost::asio::ip::tcp::resolver::query::canonical_name); boost::system::error_code ec; - ip::tcp::resolver::iterator i = resolver.resolve(query, ec); + ip::tcp::resolver::results_type result = resolver.resolve(host, port, boost::asio::ip::tcp::resolver::canonical_name, ec); CHECK_AND_ASSERT_MES(!ec, false, "Failed to resolve host name '" << host << "': " << ec.message() << ':' << ec.value()); - ip::tcp::resolver::iterator iend; + auto i = result.begin(); + auto iend = result.end(); for (; i != iend; ++i) { ip::tcp::endpoint endpoint = *i; if (endpoint.address().is_v4()) { - epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port()}}; + epee::net_utils::network_address na{epee::net_utils::ipv4_network_address{boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_uint()), endpoint.port()}}; seed_nodes.push_back(na); MINFO("Added node: " << na.str()); } From 29ac40a38c54e8963a871a78511d04d6d917b1d9 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Thu, 15 May 2025 17:36:29 +0530 Subject: [PATCH 138/182] RPC: GET_OUTPUT_DISTRIBUTION updated --- src/rpc/core_rpc_server.cpp | 49 +++-- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_binary_commands.cpp | 75 +++++++ src/rpc/core_rpc_server_binary_commands.h | 32 ++- src/rpc/core_rpc_server_command_parser.cpp | 10 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.cpp | 219 ++++++++++++++------ src/rpc/core_rpc_server_commands_defs.h | 23 +- 8 files changed, 309 insertions(+), 102 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 152e7188edf..4af09f7d986 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2393,27 +2393,25 @@ namespace cryptonote::rpc { } //------------------------------------------------------------------------------------------------------------------------------ - GET_OUTPUT_DISTRIBUTION::response core_rpc_server::invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary) + void core_rpc_server::invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context context, bool binary) { - GET_OUTPUT_DISTRIBUTION::response res{}; - PERF_TIMER(on_get_output_distribution); // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - + std::vector response_distributions; try { // 0 is placeholder for the whole chain - const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1); - for (uint64_t amount: req.amounts) + const uint64_t req_to_height = get_output_distribution.request.to_height ? get_output_distribution.request.to_height : (m_core.get_current_blockchain_height() - 1); + for (uint64_t amount: get_output_distribution.request.amounts) { auto data = detail::get_output_distribution( [this](auto&&... args) { return m_core.get_output_distribution(std::forward(args)...); }, amount, - req.from_height, + get_output_distribution.request.from_height, req_to_height, [this](uint64_t height) { return m_core.get_blockchain_storage().get_db().get_block_hash_from_height(height); }, - req.cumulative, + get_output_distribution.request.cumulative, m_core.get_current_blockchain_height()); if (!data) throw rpc_error{ERROR_INTERNAL, "Failed to get output distribution"}; @@ -2421,16 +2419,16 @@ namespace cryptonote::rpc { // Force binary & compression off if this is a JSON request because trying to pass binary // data through JSON explodes it in terms of size (most values under 0x20 have to be encoded // using 6 chars such as "\u0002"). - res.distributions.push_back({std::move(*data), amount, "", binary && req.binary, binary && req.compress}); + response_distributions.push_back({std::move(*data), amount, "", binary && get_output_distribution.request.binary, binary && get_output_distribution.request.compress}); } } catch (const std::exception &e) { throw rpc_error{ERROR_INTERNAL, "Failed to get output distribution"}; } - - res.status = STATUS_OK; - return res; + get_output_distribution.response["distributions"] = response_distributions; + get_output_distribution.response["status"] = STATUS_OK; + return; } //------------------------------------------------------------------------------------------------------------------------------ GET_OUTPUT_DISTRIBUTION_BIN::response core_rpc_server::invoke(GET_OUTPUT_DISTRIBUTION_BIN::request&& req, rpc_context context) @@ -2444,11 +2442,34 @@ namespace cryptonote::rpc { res.status = "Binary only call"; return res; } + GET_OUTPUT_DISTRIBUTION req_param{}; + req_param.request.amounts = req.amounts; + req_param.request.from_height = req.from_height; + req_param.request.to_height = req.to_height; + req_param.request.compress = req.compress; + req_param.request.cumulative = req.cumulative; + invoke(req_param, context, true); + res.status = req_param.response["status"].get(); + // Manually convert the distributions + const auto source_distributions = req_param.response["distributions"].get>(); + + res.distributions.clear(); + res.distributions.reserve(source_distributions.size()); + for (const auto& dist : source_distributions) + { + GET_OUTPUT_DISTRIBUTION_BIN::distribution bin_dist; + bin_dist.data = dist.data; + bin_dist.amount = dist.amount; + bin_dist.compressed_data = dist.compressed_data; + bin_dist.binary = dist.binary; + bin_dist.compress = dist.compress; + + res.distributions.push_back(std::move(bin_dist)); + } // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - - return invoke(std::move(static_cast(req)), context, true); + return res; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 7844b09aaff..965db2afa3c 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -163,6 +163,7 @@ namespace cryptonote::rpc { void invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context); void invoke(OUT_PEERS& out_peers, rpc_context context); void invoke(IN_PEERS& in_peers, rpc_context context); + void invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context context, bool binary = false); void invoke(POP_BLOCKS& pop_blocks, rpc_context context); void invoke(BELNET_PING& lokinet_ping, rpc_context context); void invoke(STORAGE_SERVER_PING& storage_server_ping, rpc_context context); @@ -208,7 +209,6 @@ namespace cryptonote::rpc { GET_OUTPUTS_BIN::response invoke(GET_OUTPUTS_BIN::request&& req, rpc_context context); GET_TRANSACTION_POOL_HASHES_BIN::response invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context); GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); - GET_OUTPUT_DISTRIBUTION::response invoke(GET_OUTPUT_DISTRIBUTION::request&& req, rpc_context context, bool binary = false); // FIXME: unconverted JSON RPC endpoints: // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); diff --git a/src/rpc/core_rpc_server_binary_commands.cpp b/src/rpc/core_rpc_server_binary_commands.cpp index 3ddc42c85d3..3d3bd6b92a1 100644 --- a/src/rpc/core_rpc_server_binary_commands.cpp +++ b/src/rpc/core_rpc_server_binary_commands.cpp @@ -136,4 +136,79 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_BLACKLIST_BIN::response) KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION_BIN::request) + KV_SERIALIZE(amounts) + KV_SERIALIZE_OPT(from_height, (uint64_t)0) + KV_SERIALIZE_OPT(to_height, (uint64_t)0) + KV_SERIALIZE_OPT(cumulative, false) + KV_SERIALIZE_OPT(binary, true) + KV_SERIALIZE_OPT(compress, false) +KV_SERIALIZE_MAP_CODE_END() + + +namespace +{ + template + std::string compress_integer_array(const std::vector &v) + { + std::string s; + s.reserve(tools::VARINT_MAX_LENGTH); + auto ins = std::back_inserter(s); + for (const T &t: v) + tools::write_varint(ins, t); + return s; + } + + template + std::vector decompress_integer_array(const std::string &s) + { + std::vector v; + for (auto it = s.begin(); it < s.end(); ) + { + int read = tools::read_varint(it, s.end(), v.emplace_back()); + CHECK_AND_ASSERT_THROW_MES(read > 0, "Error decompressing data"); + } + return v; + } +} + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION_BIN::distribution) + KV_SERIALIZE(amount) + KV_SERIALIZE_N(data.start_height, "start_height") + KV_SERIALIZE(binary) + KV_SERIALIZE(compress) + if (binary) + { + if (is_store) + { + if (compress) + { + const_cast(compressed_data) = compress_integer_array(data.distribution); + KV_SERIALIZE(compressed_data) + } + else + KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(data.distribution, "distribution") + } + else + { + if (compress) + { + KV_SERIALIZE(compressed_data) + const_cast&>(data.distribution) = decompress_integer_array(compressed_data); + } + else + KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(data.distribution, "distribution") + } + } + else + KV_SERIALIZE_N(data.distribution, "distribution") + KV_SERIALIZE_N(data.base, "base") +KV_SERIALIZE_MAP_CODE_END() + + +KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION_BIN::response) + KV_SERIALIZE(status) + KV_SERIALIZE(distributions) + // KV_SERIALIZE(untrusted) +KV_SERIALIZE_MAP_CODE_END() } diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index eb117f49de7..53e8537df8f 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -261,9 +261,37 @@ namespace cryptonote::rpc { struct GET_OUTPUT_DISTRIBUTION_BIN : PUBLIC, BINARY { static constexpr auto names() { return NAMES("get_output_distribution.bin"); } + struct request + { + std::vector amounts; // Amounts to look for in atomic units. + uint64_t from_height; // (optional, default is 0) starting height to check from. + uint64_t to_height; // (optional, default is 0) ending height to check up to. + bool cumulative; // (optional, default is false) States if the result should be cumulative (true) or not (false). + bool binary; + bool compress; + + KV_MAP_SERIALIZABLE + }; + + struct distribution + { + rpc::output_distribution_data data; + uint64_t amount; + std::string compressed_data; + bool binary; + bool compress; - struct request : GET_OUTPUT_DISTRIBUTION::request {}; - using response = GET_OUTPUT_DISTRIBUTION::response; + KV_MAP_SERIALIZABLE + }; + + struct response + { + std::string status; // General RPC error code. "OK" means everything looks good. + std::vector distributions; // + // bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + + KV_MAP_SERIALIZABLE + }; }; BELDEX_RPC_DOC_INTROSPECT diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index c4b00770119..f7a348d0d21 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -244,6 +244,16 @@ namespace cryptonote::rpc { "set", in_peers.request.set); } + void parse_request(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_input in) { + get_values(in, + "amounts", get_output_distribution.request.amounts, + "binary", get_output_distribution.request.binary, + "compress", get_output_distribution.request.compress, + "cumulative", get_output_distribution.request.cumulative, + "from_height", get_output_distribution.request.from_height, + "to_height", get_output_distribution.request.to_height); + } + void parse_request(POP_BLOCKS& pop_blocks, rpc_input in){ get_values(in, "nblocks", required{pop_blocks.request.nblocks}); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 98f213be5e7..e91ff4dc248 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -43,6 +43,7 @@ namespace cryptonote::rpc { void parse_request(BNS_LOOKUP& lookup, rpc_input in); void parse_request(BNS_VALUE_DECRYPT& value_decrypt, rpc_input in); void parse_request(OUT_PEERS& out_peers, rpc_input in); + void parse_request(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_input in); void parse_request(POP_BLOCKS& pop_blocks, rpc_input in); void parse_request(PRUNE_BLOCKCHAIN& prune_blockchain, rpc_input in); void parse_request(REPORT_PEER_STATUS& report_peer_status, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index daf6b8ffa77..b2294ac0c41 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -167,6 +167,89 @@ void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& e) j.at("recent_instances").get_to(e.recent_instances); }; +namespace +{ + template + std::string compress_integer_array(const std::vector &v) + { + std::string s; + s.reserve(tools::VARINT_MAX_LENGTH); + auto ins = std::back_inserter(s); + for (const T &t: v) + tools::write_varint(ins, t); + return s; + } + + template + std::vector decompress_integer_array(const std::string &s) + { + std::vector v; + for (auto it = s.begin(); it < s.end(); ) + { + int read = tools::read_varint(it, s.end(), v.emplace_back()); + CHECK_AND_ASSERT_THROW_MES(read > 0, "Error decompressing data"); + } + return v; + } +} + +void to_json(nlohmann::json& j, const GET_OUTPUT_DISTRIBUTION::distribution& y) +{ + j["amount"] = y.amount; + j["binary"] = y.binary; + j["compress"] = y.compress; + j["data"] = { + {"start_height", y.data.start_height}, + {"base", y.data.base} + }; + + if (y.binary) + { + if (y.compress) + { + j["compressed_data"] = y.compressed_data; + } + else + { + j["data"]["distribution"] = y.data.distribution; // Uncompressed, stored as array + } + } + else + { + j["data"]["distribution"] = y.data.distribution; + } +} + +void from_json(const nlohmann::json& j, GET_OUTPUT_DISTRIBUTION::distribution& y) +{ + j.at("amount").get_to(y.amount); + j.at("binary").get_to(y.binary); + j.at("compress").get_to(y.compress); + + const auto& data_obj = j.at("data"); + data_obj.at("start_height").get_to(y.data.start_height); + data_obj.at("base").get_to(y.data.base); + + if (y.binary) + { + if (y.compress) + { + j.at("compressed_data").get_to(y.compressed_data); + + // Decompress here + y.data.distribution = decompress_integer_array(y.compressed_data); + } + else + { + data_obj.at("distribution").get_to(y.data.distribution); + } + } + else + { + data_obj.at("distribution").get_to(y.data.distribution); + } +} + void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) { j = nlohmann::json{ @@ -894,81 +977,81 @@ void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) // KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::request) - KV_SERIALIZE(amounts) - KV_SERIALIZE_OPT(from_height, (uint64_t)0) - KV_SERIALIZE_OPT(to_height, (uint64_t)0) - KV_SERIALIZE_OPT(cumulative, false) - KV_SERIALIZE_OPT(binary, true) - KV_SERIALIZE_OPT(compress, false) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::request) +// KV_SERIALIZE(amounts) +// KV_SERIALIZE_OPT(from_height, (uint64_t)0) +// KV_SERIALIZE_OPT(to_height, (uint64_t)0) +// KV_SERIALIZE_OPT(cumulative, false) +// KV_SERIALIZE_OPT(binary, true) +// KV_SERIALIZE_OPT(compress, false) +// KV_SERIALIZE_MAP_CODE_END() -namespace -{ - template - std::string compress_integer_array(const std::vector &v) - { - std::string s; - s.reserve(tools::VARINT_MAX_LENGTH); - auto ins = std::back_inserter(s); - for (const T &t: v) - tools::write_varint(ins, t); - return s; - } +// namespace +// { +// template +// std::string compress_integer_array(const std::vector &v) +// { +// std::string s; +// s.reserve(tools::VARINT_MAX_LENGTH); +// auto ins = std::back_inserter(s); +// for (const T &t: v) +// tools::write_varint(ins, t); +// return s; +// } - template - std::vector decompress_integer_array(const std::string &s) - { - std::vector v; - for (auto it = s.begin(); it < s.end(); ) - { - int read = tools::read_varint(it, s.end(), v.emplace_back()); - CHECK_AND_ASSERT_THROW_MES(read > 0, "Error decompressing data"); - } - return v; - } -} +// template +// std::vector decompress_integer_array(const std::string &s) +// { +// std::vector v; +// for (auto it = s.begin(); it < s.end(); ) +// { +// int read = tools::read_varint(it, s.end(), v.emplace_back()); +// CHECK_AND_ASSERT_THROW_MES(read > 0, "Error decompressing data"); +// } +// return v; +// } +// } -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::distribution) - KV_SERIALIZE(amount) - KV_SERIALIZE_N(data.start_height, "start_height") - KV_SERIALIZE(binary) - KV_SERIALIZE(compress) - if (binary) - { - if (is_store) - { - if (compress) - { - const_cast(compressed_data) = compress_integer_array(data.distribution); - KV_SERIALIZE(compressed_data) - } - else - KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(data.distribution, "distribution") - } - else - { - if (compress) - { - KV_SERIALIZE(compressed_data) - const_cast&>(data.distribution) = decompress_integer_array(compressed_data); - } - else - KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(data.distribution, "distribution") - } - } - else - KV_SERIALIZE_N(data.distribution, "distribution") - KV_SERIALIZE_N(data.base, "base") -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::distribution) +// KV_SERIALIZE(amount) +// KV_SERIALIZE_N(data.start_height, "start_height") +// KV_SERIALIZE(binary) +// KV_SERIALIZE(compress) +// if (binary) +// { +// if (is_store) +// { +// if (compress) +// { +// const_cast(compressed_data) = compress_integer_array(data.distribution); +// KV_SERIALIZE(compressed_data) +// } +// else +// KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(data.distribution, "distribution") +// } +// else +// { +// if (compress) +// { +// KV_SERIALIZE(compressed_data) +// const_cast&>(data.distribution) = decompress_integer_array(compressed_data); +// } +// else +// KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(data.distribution, "distribution") +// } +// } +// else +// KV_SERIALIZE_N(data.distribution, "distribution") +// KV_SERIALIZE_N(data.base, "base") +// KV_SERIALIZE_MAP_CODE_END() -KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::response) - KV_SERIALIZE(status) - KV_SERIALIZE(distributions) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION::response) +// KV_SERIALIZE(status) +// KV_SERIALIZE(distributions) // KV_SERIALIZE(untrusted) -KV_SERIALIZE_MAP_CODE_END() +// KV_SERIALIZE_MAP_CODE_END() // KV_SERIALIZE_MAP_CODE_BEGIN(POP_BLOCKS::request) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 157d06c1300..d08592b23f7 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1765,7 +1765,7 @@ namespace cryptonote::rpc { { static constexpr auto names() { return NAMES("get_output_distribution"); } - struct request + struct request_parameters { std::vector amounts; // Amounts to look for in atomic units. uint64_t from_height; // (optional, default is 0) starting height to check from. @@ -1773,9 +1773,7 @@ namespace cryptonote::rpc { bool cumulative; // (optional, default is false) States if the result should be cumulative (true) or not (false). bool binary; bool compress; - - KV_MAP_SERIALIZABLE - }; + }request; struct distribution { @@ -1784,19 +1782,10 @@ namespace cryptonote::rpc { std::string compressed_data; bool binary; bool compress; - - KV_MAP_SERIALIZABLE - }; - - struct response - { - std::string status; // General RPC error code. "OK" means everything looks good. - std::vector distributions; // - // bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). - - KV_MAP_SERIALIZABLE }; }; + void to_json(nlohmann::json& j, const GET_OUTPUT_DISTRIBUTION::distribution& y); + void from_json(const nlohmann::json& j, GET_OUTPUT_DISTRIBUTION::distribution& y); /// RPC: daemon/pop_blocks /// @@ -2735,6 +2724,7 @@ namespace cryptonote::rpc { BNS_LOOKUP, BNS_VALUE_DECRYPT, OUT_PEERS, + GET_OUTPUT_DISTRIBUTION, POP_BLOCKS, PRUNE_BLOCKCHAIN, REPORT_PEER_STATUS, @@ -2753,8 +2743,7 @@ namespace cryptonote::rpc { TEST_TRIGGER_UPTIME_PROOF >; using FIXME_old_rpc_types = tools::type_list< - RELAY_TX, - GET_OUTPUT_DISTRIBUTION + RELAY_TX >; } // namespace cryptonote::rpc From 0362c35e69ed9fdba951a212d74ef848bcfc47ea Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 16 May 2025 17:45:26 +0530 Subject: [PATCH 139/182] Refactor GET_OUTPUT_DISTRIBUTION_BIN - Minor cleanup of error handling and status assigning. --- src/rpc/core_rpc_server.cpp | 63 +++++++++--------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 2 - src/rpc/core_rpc_server_commands_defs.cpp | 75 +--------------------- src/rpc/core_rpc_server_commands_defs.h | 6 -- 5 files changed, 36 insertions(+), 112 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 4af09f7d986..50cb7ef3a33 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2393,12 +2393,12 @@ namespace cryptonote::rpc { } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context context, bool binary) + void core_rpc_server::invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context context) { PERF_TIMER(on_get_output_distribution); // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - std::vector response_distributions; + GET_OUTPUT_DISTRIBUTION::distribution distributions{}; try { // 0 is placeholder for the whole chain @@ -2419,14 +2419,14 @@ namespace cryptonote::rpc { // Force binary & compression off if this is a JSON request because trying to pass binary // data through JSON explodes it in terms of size (most values under 0x20 have to be encoded // using 6 chars such as "\u0002"). - response_distributions.push_back({std::move(*data), amount, "", binary && get_output_distribution.request.binary, binary && get_output_distribution.request.compress}); + distributions = {std::move(*data), amount}; + get_output_distribution.response["distribution"].push_back(distributions); } } catch (const std::exception &e) { throw rpc_error{ERROR_INTERNAL, "Failed to get output distribution"}; } - get_output_distribution.response["distributions"] = response_distributions; get_output_distribution.response["status"] = STATUS_OK; return; } @@ -2442,33 +2442,38 @@ namespace cryptonote::rpc { res.status = "Binary only call"; return res; } - GET_OUTPUT_DISTRIBUTION req_param{}; - req_param.request.amounts = req.amounts; - req_param.request.from_height = req.from_height; - req_param.request.to_height = req.to_height; - req_param.request.compress = req.compress; - req_param.request.cumulative = req.cumulative; - invoke(req_param, context, true); - res.status = req_param.response["status"].get(); - // Manually convert the distributions - const auto source_distributions = req_param.response["distributions"].get>(); - - res.distributions.clear(); - res.distributions.reserve(source_distributions.size()); - for (const auto& dist : source_distributions) - { - GET_OUTPUT_DISTRIBUTION_BIN::distribution bin_dist; - bin_dist.data = dist.data; - bin_dist.amount = dist.amount; - bin_dist.compressed_data = dist.compressed_data; - bin_dist.binary = dist.binary; - bin_dist.compress = dist.compress; - - res.distributions.push_back(std::move(bin_dist)); + // if (use_bootstrap_daemon_if_necessary(req, res)) + // return res; + + try + { + // 0 is placeholder for the whole chain + const uint64_t req_to_height = req.to_height ? req.to_height : (m_core.get_current_blockchain_height() - 1); + for (uint64_t amount: req.amounts) + { + auto data = detail::get_output_distribution( + [this](auto&&... args) { return m_core.get_output_distribution(std::forward(args)...); }, + amount, + req.from_height, + req_to_height, + [this](uint64_t height) { return m_core.get_blockchain_storage().get_db().get_block_hash_from_height(height); }, + req.cumulative, + m_core.get_current_blockchain_height()); + if (!data) + throw rpc_error{ERROR_INTERNAL, "Failed to get output distribution"}; + + // Force binary & compression off if this is a JSON request because trying to pass binary + // data through JSON explodes it in terms of size (most values under 0x20 have to be encoded + // using 6 chars such as "\u0002"). + res.distributions.push_back({std::move(*data), amount, "", req.binary, req.compress}); + } + } + catch (const std::exception &e) + { + throw rpc_error{ERROR_INTERNAL, "Failed to get output distribution"}; } - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + res.status = STATUS_OK; return res; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 965db2afa3c..d515972d59e 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -163,7 +163,7 @@ namespace cryptonote::rpc { void invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context); void invoke(OUT_PEERS& out_peers, rpc_context context); void invoke(IN_PEERS& in_peers, rpc_context context); - void invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context context, bool binary = false); + void invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context); void invoke(POP_BLOCKS& pop_blocks, rpc_context context); void invoke(BELNET_PING& lokinet_ping, rpc_context context); void invoke(STORAGE_SERVER_PING& storage_server_ping, rpc_context context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index f7a348d0d21..c2c248b1e54 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -247,8 +247,6 @@ namespace cryptonote::rpc { void parse_request(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_input in) { get_values(in, "amounts", get_output_distribution.request.amounts, - "binary", get_output_distribution.request.binary, - "compress", get_output_distribution.request.compress, "cumulative", get_output_distribution.request.cumulative, "from_height", get_output_distribution.request.from_height, "to_height", get_output_distribution.request.to_height); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index b2294ac0c41..6bb4feee3b6 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -167,87 +167,14 @@ void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& e) j.at("recent_instances").get_to(e.recent_instances); }; -namespace -{ - template - std::string compress_integer_array(const std::vector &v) - { - std::string s; - s.reserve(tools::VARINT_MAX_LENGTH); - auto ins = std::back_inserter(s); - for (const T &t: v) - tools::write_varint(ins, t); - return s; - } - - template - std::vector decompress_integer_array(const std::string &s) - { - std::vector v; - for (auto it = s.begin(); it < s.end(); ) - { - int read = tools::read_varint(it, s.end(), v.emplace_back()); - CHECK_AND_ASSERT_THROW_MES(read > 0, "Error decompressing data"); - } - return v; - } -} - void to_json(nlohmann::json& j, const GET_OUTPUT_DISTRIBUTION::distribution& y) { j["amount"] = y.amount; - j["binary"] = y.binary; - j["compress"] = y.compress; j["data"] = { {"start_height", y.data.start_height}, {"base", y.data.base} }; - - if (y.binary) - { - if (y.compress) - { - j["compressed_data"] = y.compressed_data; - } - else - { - j["data"]["distribution"] = y.data.distribution; // Uncompressed, stored as array - } - } - else - { - j["data"]["distribution"] = y.data.distribution; - } -} - -void from_json(const nlohmann::json& j, GET_OUTPUT_DISTRIBUTION::distribution& y) -{ - j.at("amount").get_to(y.amount); - j.at("binary").get_to(y.binary); - j.at("compress").get_to(y.compress); - - const auto& data_obj = j.at("data"); - data_obj.at("start_height").get_to(y.data.start_height); - data_obj.at("base").get_to(y.data.base); - - if (y.binary) - { - if (y.compress) - { - j.at("compressed_data").get_to(y.compressed_data); - - // Decompress here - y.data.distribution = decompress_integer_array(y.compressed_data); - } - else - { - data_obj.at("distribution").get_to(y.data.distribution); - } - } - else - { - data_obj.at("distribution").get_to(y.data.distribution); - } + j["data"]["distribution"] = y.data.distribution; } void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d08592b23f7..1a9b1cfd832 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1771,21 +1771,15 @@ namespace cryptonote::rpc { uint64_t from_height; // (optional, default is 0) starting height to check from. uint64_t to_height; // (optional, default is 0) ending height to check up to. bool cumulative; // (optional, default is false) States if the result should be cumulative (true) or not (false). - bool binary; - bool compress; }request; struct distribution { rpc::output_distribution_data data; uint64_t amount; - std::string compressed_data; - bool binary; - bool compress; }; }; void to_json(nlohmann::json& j, const GET_OUTPUT_DISTRIBUTION::distribution& y); - void from_json(const nlohmann::json& j, GET_OUTPUT_DISTRIBUTION::distribution& y); /// RPC: daemon/pop_blocks /// From 9ade6292efd12ad80f6d23e411d60badd7c14cfc Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Mon, 19 May 2025 18:00:59 +0530 Subject: [PATCH 140/182] refactor: update daemon CLI commands for new JSON-RPC structure --- src/daemon/command_parser_executor.cpp | 1 + src/daemon/rpc_command_executor.cpp | 190 +++++++++++++------------ src/rpc/core_rpc_server.cpp | 6 +- 3 files changed, 107 insertions(+), 90 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 032a37acd84..53b737bd364 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -111,6 +111,7 @@ bool command_parser_executor::print_mn_state_changes(const std::vector start_heigh if (!maybe_checkpoints) return false; - auto checkpoints = *maybe_checkpoints; + auto& checkpoints = maybe_checkpoints->at("checkpoints"); std::string entry; if (print_json) - entry.append(checkpoints.dump()); + entry = checkpoints.dump(); else { - for (size_t i = 0; i < checkpoints.size(); i++) - { - entry.append("["); - entry.append(std::to_string(i)); - entry.append("]"); - - entry.append(" Type: "); - entry.append(checkpoints[i]["type"]); - - entry.append(" Height: "); - entry.append(checkpoints[i]["height"]); - - entry.append(" Hash: "); - entry.append(checkpoints[i]["block_hash"]); - entry.append("\n"); - } + for (size_t i = 0; i < checkpoints.size(); i++) { + auto& cp = checkpoints[i]; + int type = cp["type"].get(); + fmt::format_to( + std::back_inserter(entry), + "[{}] Type: {}, Height: {}, Hash: {}\n", + i, + type == 0 ? "hard-coded" : "Master Node", + cp["height"].get(), + cp["block_hash"].get()); + } if (entry.empty()) entry.append("No Checkpoints"); } @@ -334,7 +329,7 @@ bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, std::op json params{{"start_height", start_height}}; if (end_height) params["end_height"] = *end_height; - return invoke(std::move(params)); }, "Failed to query service node state changes"); + return invoke(std::move(params)); }, "Failed to query master node state changes"); if (!maybe_mn_state) return false; @@ -342,12 +337,12 @@ bool rpc_command_executor::print_mn_state_changes(uint64_t start_height, std::op std::stringstream output; - output << "Master Node State Changes (blocks " << mn_state_changes["start_height"].get() << "-" << mn_state_changes["end_height"].get() << ")" << std::endl; - output << " Recommissions:\t\t" << mn_state_changes["total_recommission"].get() << std::endl; - output << " Unlocks:\t\t" << mn_state_changes["total_unlock"].get() << std::endl; - output << " Decommissions:\t\t" << mn_state_changes["total_decommission"].get() << std::endl; - output << " Deregistrations:\t" << mn_state_changes["total_deregister"].get() << std::endl; - output << " IP change penalties:\t" << mn_state_changes["total_ip_change_penalty"].get() << std::endl; + output << "Master Node State Changes (blocks " << mn_state_changes["start_height"].get() << "-" << mn_state_changes["end_height"].get() << ")" << std::endl; + output << " Recommissions:\t\t" << mn_state_changes["total_recommission"].get() << std::endl; + output << " Unlocks:\t\t" << mn_state_changes["total_unlock"].get() << std::endl; + output << " Decommissions:\t\t" << mn_state_changes["total_decommission"].get() << std::endl; + output << " Deregistrations:\t" << mn_state_changes["total_deregister"].get() << std::endl; + output << " IP change penalties:\t" << mn_state_changes["total_ip_change_penalty"].get() << std::endl; tools::success_msg_writer() << output.str(); return true; @@ -614,18 +609,17 @@ bool rpc_command_executor::mining_status() { bool mining_busy = false; auto& mres = *maybe_mining_info; - if (mres["status"] == STATUS_BUSY) + if (mres["status"].get() == STATUS_BUSY) mining_busy = true; - else if (mres["status"] != STATUS_OK) { + else if (mres["status"].get() != STATUS_OK) { tools::fail_msg_writer() << "Failed to retrieve mining info"; return false; } bool active = mres["active"].get(); - long speed = mres["speed"].get(); if (mining_busy || !active) tools::msg_writer() << "Not currently mining"; else { - tools::msg_writer() << "Mining at " << get_mining_speed(speed) << " with " << mres["threads_count"].get() << " threads"; + tools::msg_writer() << "Mining at " << get_mining_speed(mres["speed"].get()) << " with " << mres["threads_count"].get() << " threads"; tools::msg_writer() << "Mining address: " << mres["address"].get(); } tools::msg_writer() << "PoW algorithm: " << mres["pow_algorithm"].get(); @@ -768,35 +762,26 @@ bool rpc_command_executor::print_quorum_state(std::optional start_heig return false; auto& quorums = *maybe_quorums; - std::string output; - output.append("{\n\"quorums\": ["); - for (auto const& quorum : quorums["quorums"]) - { - output.append("\n"); - output.append(quorum); - output.append(",\n"); - } - output.append("]\n}"); - tools::success_msg_writer() << output; + tools::success_msg_writer() << "{{\n quorums : \n}}"<(json{{"level", level}})) + auto maybe_level = try_running([this, &level] { return invoke(json{{"level", level}}); }, "Failed to set log categories"); + if (!maybe_level) return false; + auto& level_response = *maybe_level; - tools::success_msg_writer() << "Log level is now " << std::to_string(level); - + tools::success_msg_writer() << "Log level is now " << level_response["level"].get(); return true; } bool rpc_command_executor::set_log_categories(std::string categories) { - // auto maybe_categories = try_running([this, &categories] { return invoke(json{{"categories", std::move(categories)}}); }, "Failed to set log categories"); - // if (!maybe_categories) - // return false; - // auto& categories_response = *maybe_categories; - auto categories_response = make_request(json{{"categories", std::move(categories)}}); + auto maybe_categories = try_running([this, &categories] { return invoke(json{{"categories", std::move(categories)}}); }, "Failed to set log categories"); + if (!maybe_categories) + return false; + auto& categories_response = *maybe_categories; tools::success_msg_writer() << "Log categories are now " << categories_response["categories"].get(); return true; @@ -873,7 +858,7 @@ bool rpc_command_executor::print_transaction(const crypto::hash& transaction_has auto prunable_hex = tx.value("prunable", ""sv); bool pruned = !prunable_hash.empty() && prunable_hex.empty(); - bool in_pool = tx["in_pool"].get(); + bool in_pool = tx.value("in_pool", false); if (in_pool) tools::success_msg_writer() << "Found in pool"; else @@ -1161,10 +1146,9 @@ bool rpc_command_executor::out_peers(bool set, uint32_t limit) if (!maybe_out_peers) return false; auto& out_peers = *maybe_out_peers; - - const std::string s = out_peers["out_peers"] == (uint32_t)-1 ? "unlimited" : out_peers["out_peers"].get(); - tools::msg_writer() << "Max number of out peers set to " << s << std::endl; - + auto peers = out_peers["out_peers"].get(); + tools::msg_writer() << "Max number of out peers set to " + << (peers == std::numeric_limits::max() ? "unlimited" : std::to_string(peers)); return true; } @@ -1174,31 +1158,33 @@ bool rpc_command_executor::in_peers(bool set, uint32_t limit) if (!maybe_in_peers) return false; auto& in_peers = *maybe_in_peers; - - const std::string s = in_peers["in_peers"] == (uint32_t)-1 ? "unlimited" : in_peers["in_peers"].get(); - tools::msg_writer() << "Max number of in peers set to " << s << std::endl; + auto peers = in_peers["in_peers"].get(); + tools::msg_writer() << "Max number of incoming peers set to " + << (peers == std::numeric_limits::max() ? "unlimited" : std::to_string(peers)); return true; } bool rpc_command_executor::print_bans() { - auto maybe_bans = try_running([this] { return invoke(); }, "Failed to retrieve ban list"); - if (!maybe_bans) - return false; - auto bans = *maybe_bans; - - if (!bans.empty()) - { - for (auto i = bans.begin(); i != bans.end(); ++i) - { - tools::msg_writer() << (*i)["host"] << " banned for " << (*i)["seconds"] << " seconds"; - } - } - else - tools::msg_writer() << "No IPs are banned"; + auto maybe_bans = + try_running([this] { return invoke(); }, "Failed to retrieve ban list"); + if (!maybe_bans) + return false; + auto bans = *maybe_bans; + auto ban_array = bans.at("bans"); + assert(ban_array.is_array() && "Internal error, RPC API has changed"); + + if (ban_array.empty()) { + tools::msg_writer()<<"No IPs are banned"; return true; + } + + for(const auto& ban : ban_array){ + tools::msg_writer() << ban["host"].get() << " banned for " << ban["seconds"].get() << " seconds"; + } + return true; } bool rpc_command_executor::ban(const std::string &address, time_t seconds, bool clear_ban) @@ -1231,7 +1217,7 @@ bool rpc_command_executor::banned(const std::string &address) auto& banned_response = *maybe_banned; if (banned_response["banned"].get()) - tools::msg_writer() << address << " is banned for " << banned_response["seconds"].get() << " seconds"; + tools::msg_writer() << address << " is banned for " << banned_response["seconds"].get() << " seconds"; else tools::msg_writer() << address << " is not banned"; @@ -1244,9 +1230,11 @@ bool rpc_command_executor::flush_txpool(std::string txid) if (!txid.empty()) txids.push_back(std::move(txid)); - if (!invoke(json{{txids, std::move(txids)}})) { - tools::fail_msg_writer() << "Failed to flush tx pool"; - return false; + try { + invoke(json{{"txids", std::move(txids)}}); + } catch (const std::exception& e) { + tools::fail_msg_writer() << "Failed to flush tx pool:"<< e.what(); + return false; } tools::success_msg_writer() << "Pool successfully flushed"; @@ -1397,10 +1385,21 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) return false; auto& feres = *maybe_fees; - auto height = info["height"].get(); - tools::msg_writer() << "Height: " << height << ", diff " << info["difficulty"].get() << ", cum. diff " << info["cumulative_difficulty"].get() - << ", target " << info["target"].get() << " sec" << ", dyn fee " << cryptonote::print_money(feres["fee_per_byte"]) << "/" << (hfinfo["enabled"].get() ? "byte" : "kB") - << " + " << cryptonote::print_money(feres["fee_per_output"]) << "/out"; + // Safe access with fallback + auto safe_uint64 = [](const json& j, const std::string& key) { + return j.contains(key) && !j[key].is_null() ? j[key].get() : 0; + }; + auto safe_int = [](const json& j, const std::string& key) { + return j.contains(key) && !j[key].is_null() ? j[key].get() : 0; + }; + + uint64_t height = safe_uint64(info, "height"); + uint64_t difficulty = safe_uint64(info, "difficulty"); + uint64_t cumulative_difficulty = safe_uint64(info, "cumulative_difficulty"); + int target = safe_int(info, "target"); + tools::msg_writer() << "Height: " << height << ", diff " << difficulty << ", cum. diff " << cumulative_difficulty + << ", target " << target << " sec" << ", dyn fee " << cryptonote::print_money(safe_uint64(feres, "fee_per_byte")) << "/" << (hfinfo["enabled"].get() ? "byte" : "kB") + << " + " << cryptonote::print_money(safe_uint64(feres, "fee_per_byte")) << "/out"; if (nblocks > 0) { @@ -1421,14 +1420,24 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) std::map> versions; // version -> {majorcount, minorcount} for (const auto &bhr: block_headers["headers"]) { - avgdiff += bhr["difficulty"].get(); - avgnumtxes += bhr["num_txes"].get(); - avgreward += bhr["reward"].get(); - weights.push_back(bhr["block_weight"].get()); - versions[bhr["major_version"]].first++; - versions[bhr["minor_version"]].second++; - earliest = std::min(earliest, bhr["timestamp"].get()); - latest = std::max(latest, bhr["timestamp"].get()); + auto get_or = [](const json& j, const std::string& key, uint64_t def) { + return j.contains(key) && !j[key].is_null() ? j[key].get() : def; + }; + auto get_or_unsigned = [](const json& j, const std::string& key) { + return j.contains(key) && !j[key].is_null() ? j[key].get() : 0; + }; + + avgdiff += get_or(bhr, "difficulty", 0); + avgnumtxes += get_or(bhr, "num_txes", 0); + avgreward += get_or(bhr, "reward", 0); + weights.push_back(get_or(bhr, "block_weight", 0)); + + versions[get_or_unsigned(bhr, "major_version")].first++; + versions[get_or_unsigned(bhr, "minor_version")].second++; + + uint64_t timestamp = get_or(bhr, "timestamp", 0); + earliest = std::min(earliest, timestamp); + latest = std::max(latest, timestamp); } avgdiff /= nblocks; avgnumtxes /= nblocks; @@ -1931,8 +1940,13 @@ bool rpc_command_executor::print_mn(const std::vector &args, bool s bool rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) { - if (!invoke(json{{"bad_txs", bad_txs}, {"bad_blocks", bad_blocks}}, "Failed to flush TX cache")) - return false; + try { + invoke(json{{"bad_txs", bad_txs}, {"bad_blocks", bad_blocks}}); + } catch (const std::exception& e) { + tools::fail_msg_writer()<<"Failed to flush cache: "<< e.what(); + return false; + } + tools::success_msg_writer() << "Cache flushed successfully"; return true; } @@ -2548,7 +2562,7 @@ bool rpc_command_executor::check_blockchain_pruning() return false; auto& pruning = *maybe_pruning; - tools::success_msg_writer() << "Blockchain is" << (pruning["pruning_seed"] ? "" : " not") << " pruned"; + tools::success_msg_writer() << "Blockchain " << (pruning["pruning_seed"].get() > 0 ? "is" : "is not") << " pruned"; return true; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 50cb7ef3a33..f51fdf01e21 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1883,6 +1883,7 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_BANS& get_bans, rpc_context context) { PERF_TIMER(on_get_bans); + get_bans.response["bans"] = nlohmann::json::array(); auto now = time(nullptr); std::map blocked_hosts = m_p2p.get_blocked_hosts(); @@ -2188,6 +2189,7 @@ namespace cryptonote::rpc { PERF_TIMER(on_out_peers); if (out_peers.request.set) m_p2p.change_max_out_public_peers(out_peers.request.out_peers); + out_peers.response["out_peers"] = m_p2p.get_max_out_public_peers(); out_peers.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2196,6 +2198,7 @@ namespace cryptonote::rpc { PERF_TIMER(on_in_peers); if (in_peers.request.set) m_p2p.change_max_in_public_peers(in_peers.request.in_peers); + in_peers.response["in_peers"] = m_p2p.get_max_in_public_peers(); in_peers.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2494,8 +2497,7 @@ namespace cryptonote::rpc { prune_blockchain.response["status"] = STATUS_OK; } - - + //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_QUORUM_STATE& get_quorum_state, rpc_context context) { PERF_TIMER(on_get_quorum_state); From 21689bba2c3e61b09779f3123608000172361498 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 21 May 2025 14:40:09 +0530 Subject: [PATCH 141/182] small fixes in rpc-endpoints --- src/rpc/core_rpc_server.cpp | 5 ++--- src/rpc/core_rpc_server_command_parser.cpp | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f51fdf01e21..adb2fbf6e48 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2401,7 +2401,6 @@ namespace cryptonote::rpc { PERF_TIMER(on_get_output_distribution); // if (use_bootstrap_daemon_if_necessary(req, res)) // return res; - GET_OUTPUT_DISTRIBUTION::distribution distributions{}; try { // 0 is placeholder for the whole chain @@ -2422,8 +2421,8 @@ namespace cryptonote::rpc { // Force binary & compression off if this is a JSON request because trying to pass binary // data through JSON explodes it in terms of size (most values under 0x20 have to be encoded // using 6 chars such as "\u0002"). - distributions = {std::move(*data), amount}; - get_output_distribution.response["distribution"].push_back(distributions); + GET_OUTPUT_DISTRIBUTION::distribution distributions = {std::move(*data), amount}; + get_output_distribution.response["distributions"].push_back(distributions); } } catch (const std::exception &e) diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index c2c248b1e54..368e29c676a 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -74,7 +74,7 @@ namespace cryptonote::rpc { } } if (!legacy_outputs) - get_values(in, "outputs", get_outputs.request.output_indices); + get_values(in, "output_indices", get_outputs.request.output_indices); } void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in) { diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 6bb4feee3b6..7266aae756c 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -169,12 +169,12 @@ void from_json(const nlohmann::json& j, GET_OUTPUT_HISTOGRAM::entry& e) void to_json(nlohmann::json& j, const GET_OUTPUT_DISTRIBUTION::distribution& y) { - j["amount"] = y.amount; - j["data"] = { + j = nlohmann::json{ + {"amount", y.amount}, + {"distribution", y.data.distribution}, {"start_height", y.data.start_height}, {"base", y.data.base} }; - j["data"]["distribution"] = y.data.distribution; } void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) From a1af4759ca1288db440fa8a95b105225e87db02c Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Wed, 9 Jul 2025 16:25:36 +0530 Subject: [PATCH 142/182] add bulletproofplus core functions --- .gitignore | 3 + src/blockchain_db/blockchain_db.cpp | 10 +- .../cryptonote_boost_serialization.h | 32 +- .../cryptonote_format_utils.cpp | 47 +- src/cryptonote_config.h | 3 + src/ringct/CMakeLists.txt | 3 +- src/ringct/bulletproofs_plus.cc | 1121 +++++++++++++++++ src/ringct/bulletproofs_plus.h | 49 + src/ringct/rctTypes.h | 38 +- 9 files changed, 1289 insertions(+), 17 deletions(-) create mode 100644 src/ringct/bulletproofs_plus.cc create mode 100644 src/ringct/bulletproofs_plus.h diff --git a/.gitignore b/.gitignore index c3b4edd6d58..a3bc8e32248 100755 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,6 @@ __pycache__/ # brew build lock file contrib/brew/Brewfile.lock.json + +*.pyc +*.log diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 0f712e48f37..c252142f882 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -161,8 +161,16 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { + rct::key commitment; + if (tx.version > 1) + { + commitment = tx.rct_signatures.outPk[i].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + commitment = rct::scalarmult8(commitment); + } amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time, - tx.version >= cryptonote::txversion::v2_ringct ? &tx.rct_signatures.outPk[i].mask : NULL); + tx.version >= cryptonote::txversion::v2_ringct ? &commitment : NULL); + //to check with sarav } } diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h index 9523ef4b3ea..97b75e6b412 100755 --- a/src/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/cryptonote_basic/cryptonote_boost_serialization.h @@ -254,6 +254,20 @@ namespace boost a & x.t; } + template + inline void serialize(Archive &a, rct::BulletproofPlus &x, const boost::serialization::version_type ver) + { + a & x.V; + a & x.A; + a & x.A1; + a & x.B; + a & x.r1; + a & x.s1; + a & x.d1; + a & x.L; + a & x.R; + } + template inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) { @@ -332,7 +346,7 @@ namespace boost a & x.type; if (x.type == rct::RCTType::Null) return; - if (!tools::equals_any(x.type, rct::RCTType::Full, rct::RCTType::Simple, rct::RCTType::Bulletproof, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG)) + if (!tools::equals_any(x.type, rct::RCTType::Full, rct::RCTType::Simple, rct::RCTType::Bulletproof, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -348,7 +362,11 @@ namespace boost { a & x.rangeSigs; if (x.rangeSigs.empty()) + { a & x.bulletproofs; + if (ver >= 2u) + a & x.bulletproofs_plus; + } a & x.MGs; if (ver >= 1u) a & x.CLSAGs; @@ -362,7 +380,7 @@ namespace boost a & x.type; if (x.type == rct::RCTType::Null) return; - if (!tools::equals_any(x.type, rct::RCTType::Full, rct::RCTType::Simple, rct::RCTType::Bulletproof, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG)) + if (!tools::equals_any(x.type, rct::RCTType::Full, rct::RCTType::Simple, rct::RCTType::Bulletproof, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -374,11 +392,15 @@ namespace boost //-------------- a & x.p.rangeSigs; if (x.p.rangeSigs.empty()) + { a & x.p.bulletproofs; + if (ver >= 2u) + a & x.p.bulletproofs_plus; + } a & x.p.MGs; if (ver >= 1u) a & x.p.CLSAGs; - if (rct::is_rct_bulletproof(x.type)) + if (x.type == rct::RCTType::Bulletproof || x.type == rct::RCTType::Bulletproof2 || x.type == rct::RCTType::CLSAG || x.type == rct::RCTType::BulletproofPlus) a & x.p.pseudoOuts; } @@ -391,6 +413,6 @@ namespace boost } } -BOOST_CLASS_VERSION(rct::rctSigPrunable, 1) -BOOST_CLASS_VERSION(rct::rctSig, 1) +BOOST_CLASS_VERSION(rct::rctSigPrunable, 2) +BOOST_CLASS_VERSION(rct::rctSig, 2) BOOST_CLASS_VERSION(rct::multisig_out, 1) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 0c0f5c2b399..e2b7b6985e4 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -82,7 +82,8 @@ namespace cryptonote uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { const rct::rctSig &rv = tx.rct_signatures; - const uint64_t bp_base = 368; + const bool plus = rv.type == rct::RCTType::BulletproofPlus; + const uint64_t bp_base = (32 * ((plus ? 6 : 9) + 7 * 2)) / 2; // notional size of a 2 output proof, normalized to 1 proof (ie, divided by 2) const size_t n_outputs = tx.vout.size(); if (n_padded_outputs <= 2) return 0; @@ -90,7 +91,7 @@ namespace cryptonote while ((1u << nlr) < n_padded_outputs) ++nlr; nlr += 6; - const size_t bp_size = 32 * (9 + 2 * nlr); + const size_t bp_size = 32 * ((plus ? 6 : 9) + 2 * nlr); CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= TX_BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(TX_BULLETPROOF_MAX_OUTPUTS) + " per transaction"); CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs " + std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size)); @@ -149,7 +150,33 @@ namespace cryptonote if (!base_only) { const bool bulletproof = rct::is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + + if (bulletproof_plus) + { + if (rv.p.bulletproofs_plus.size() != 1) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus size in tx " << get_transaction_hash(tx)); + return false; + } + if (rv.p.bulletproofs_plus[0].L.size() < 6) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus L size in tx " << get_transaction_hash(tx)); + return false; + } + const size_t max_outputs = rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus[0]); + if (max_outputs < tx.vout.size()) + { + LOG_PRINT_L1("Failed to parse transaction from blob, bad bulletproofs_plus max outputs in tx " << get_transaction_hash(tx)); + return false; + } + const size_t n_amounts = tx.vout.size(); + CHECK_AND_ASSERT_MES(n_amounts == rv.outPk.size(), false, "Internal error filling out V"); + rv.p.bulletproofs_plus[0].V.resize(n_amounts); + for (size_t i = 0; i < n_amounts; ++i) + rv.p.bulletproofs_plus[0].V[i] = rct::scalarmultKey(rv.outPk[i].mask, rct::INV_EIGHT); + } + else if (bulletproof) { if (rv.p.bulletproofs.size() != 1) { @@ -436,9 +463,11 @@ namespace cryptonote if (tx.version < txversion::v2_ringct) return blob_size; const rct::rctSig &rv = tx.rct_signatures; - if (!rct::is_rct_bulletproof(rv.type)) + const bool bulletproof = rct::is_rct_bulletproof(rv.type); + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(rv.type); + if (!bulletproof && !bulletproof_plus) return blob_size; - const size_t n_padded_outputs = rct::n_bulletproof_max_amounts(rv.p.bulletproofs); + const size_t n_padded_outputs = bulletproof_plus ? rct::n_bulletproof_plus_max_amounts(rv.p.bulletproofs_plus) : rct::n_bulletproof_max_amounts(rv.p.bulletproofs); uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs); CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits::max() - blob_size, "Weight overflow"); return blob_size + bp_clawback; @@ -448,8 +477,8 @@ namespace cryptonote { CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits::max(), "get_pruned_transaction_weight does not support non pruned txes"); CHECK_AND_ASSERT_MES(tx.version >= txversion::v2_ringct, std::numeric_limits::max(), "get_pruned_transaction_weight does not support v1 txes"); - CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTType::Bulletproof2, - std::numeric_limits::max(), "get_pruned_transaction_weight does not support older range proof types"); + CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTType::Bulletproof2 || tx.rct_signatures.type == rct::RCTType::CLSAG || tx.rct_signatures.type == rct::RCTType::BulletproofPlus, + std::numeric_limits::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits::max(), "empty vin"); CHECK_AND_ASSERT_MES(std::holds_alternative(tx.vin[0]), std::numeric_limits::max(), "empty vin"); @@ -464,12 +493,12 @@ namespace cryptonote while ((n_padded_outputs = (1u << nrl)) < tx.vout.size()) ++nrl; nrl += 6; - uint64_t extra = 32 * (9 + 2 * nrl) + 2; + extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2; weight += extra; // calculate deterministic CLSAG/MLSAG data size const size_t ring_size = var::get(tx.vin[0]).key_offsets.size(); - if (tx.rct_signatures.type == rct::RCTType::CLSAG) + if (rct::is_rct_clsag(tx.rct_signatures.type)) extra = tx.vin.size() * (ring_size + 2) * 32; else extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 21c041c9bb6..6fe98687641 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -55,6 +55,7 @@ inline constexpr uint64_t MINED_MONEY_UNLOCK_WINDOW = 60; inline constexpr uint64_t DEFAULT_TX_SPENDABLE_AGE_V17 = 2; inline constexpr uint64_t TX_OUTPUT_DECOYS = 9; inline constexpr size_t TX_BULLETPROOF_MAX_OUTPUTS = 16; +inline constexpr size_t TX_BULLETPROOF_PLUS_MAX_OUTPUTS = 16; inline constexpr uint64_t PUBLIC_ADDRESS_TEXTBLOB_VER = 0; inline constexpr uint64_t FINAL_SUBSIDY_PER_MINUTE = 500000000; // 3 * pow(10, 7) @@ -103,6 +104,8 @@ inline constexpr size_t HASH_OF_HASHES_STEP = 256; // Hash domain separators namespace hashkey { inline constexpr std::string_view BULLETPROOF_EXPONENT = "bulletproof"sv; + inline constexpr std::string_view BULLETPROOF_PLUS_EXPONENT = "bulletproof_plus"sv; + inline constexpr std::string_view BULLETPROOF_PLUS_TRANSCRIPT = "bulletproof_plus_transcript"sv; inline constexpr std::string_view RINGDB = "ringdsb\0"sv; inline constexpr std::string_view SUBADDRESS = "SubAddr\0"sv; inline constexpr unsigned char ENCRYPTED_PAYMENT_ID = 0x8d; diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt index d27425e0c10..9aebfcd4f39 100755 --- a/src/ringct/CMakeLists.txt +++ b/src/ringct/CMakeLists.txt @@ -32,7 +32,8 @@ add_library(ringct_basic rctTypes.cpp rctCryptoOps.c multiexp.cc - bulletproofs.cc) + bulletproofs.cc + bulletproofs_plus.cc) target_link_libraries(ringct_basic PUBLIC diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc new file mode 100644 index 00000000000..10bf6e7a376 --- /dev/null +++ b/src/ringct/bulletproofs_plus.cc @@ -0,0 +1,1121 @@ +// Copyright (c) 2017-2020, 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. + +// Implements the Bulletproofs+ prover and verifier algorithms +// +// Preprint: https://eprint.iacr.org/2020/735, version 17 Jun 2020 +// +// NOTE ON NOTATION: +// In the signature constructions used in Monero, commitments to zero are treated as +// public keys against the curve group generator `G`. This means that amount +// commitments must use another generator `H` for values in order to show balance. +// The result is that the roles of `g` and `h` in the preprint are effectively swapped +// in this code, taking on the roles of `H` and `G`, respectively. Read carefully! + +#include +#include +#include +#include "misc_log_ex.h" +#include "span.h" +#include "cryptonote_config.h" +extern "C" +{ +#include "crypto/crypto-ops.h" +} +#include "rctOps.h" +#include "multiexp.h" +#include "bulletproofs_plus.h" + +#undef BELDEX_DEFAULT_LOG_CATEGORY +#define BELDEX_DEFAULT_LOG_CATEGORY "bulletproof_plus" + +#define STRAUS_SIZE_LIMIT 232 +#define PIPPENGER_SIZE_LIMIT 0 + +namespace rct +{ + // Vector functions + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b); + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n); + + // Proof bounds + static constexpr size_t maxN = 64; // maximum number of bits in range + static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof + + // Cached public generators + static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; + static std::shared_ptr straus_HiGi_cache; + static std::shared_ptr pippenger_HiGi_cache; + + // Useful scalar constants + static const constexpr rct::key ZERO = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 0 + static const constexpr rct::key ONE = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 1 + static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; // 2 + static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; // -1 + static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; // -(8**(-1)) + static rct::key TWO_SIXTY_FOUR_MINUS_ONE; // 2**64 - 1 + + // Initial transcript hash + static rct::key initial_transcript; + + static boost::mutex init_mutex; + + // Use the generator caches to compute a multiscalar multiplication + static inline rct::key multiexp(const std::vector &data, size_t HiGi_size) + { + if (HiGi_size > 0) + { + static_assert(232 <= STRAUS_SIZE_LIMIT, "Straus in precalc mode can only be calculated till STRAUS_SIZE_LIMIT"); + return HiGi_size <= 232 && data.size() == HiGi_size ? straus(data, straus_HiGi_cache, 0) : pippenger(data, pippenger_HiGi_cache, HiGi_size, get_pippenger_c(data.size())); + } + else + { + return data.size() <= 95 ? straus(data, NULL, 0) : pippenger(data, NULL, 0, get_pippenger_c(data.size())); + } + } + + // Confirm that a scalar is properly reduced + static inline bool is_reduced(const rct::key &scalar) + { + return sc_check(scalar.bytes) == 0; + } + + // Use hashed values to produce indexed public generators + static ge_p3 get_exponent(const rct::key &base, size_t idx) + { + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); + rct::key generator; + ge_p3 generator_p3; + rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); + ge_p3_tobytes(generator.bytes, &generator_p3); + CHECK_AND_ASSERT_THROW_MES(!(generator == rct::identity()), "Exponent is point at infinity"); + return generator_p3; + } + + // Construct public generators + static void init_exponents() + { + boost::lock_guard lock(init_mutex); + + // Only needs to be done once + static bool init_done = false; + if (init_done) + return; + + std::vector data; + data.reserve(maxN*maxM*2); + for (size_t i = 0; i < maxN*maxM; ++i) + { + Hi_p3[i] = get_exponent(rct::H, i * 2); + Gi_p3[i] = get_exponent(rct::H, i * 2 + 1); + + data.push_back({rct::zero(), Gi_p3[i]}); + data.push_back({rct::zero(), Hi_p3[i]}); + } + + straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); + pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); + + // Compute 2**64 - 1 for later use in simplifying verification + TWO_SIXTY_FOUR_MINUS_ONE = TWO; + for (size_t i = 0; i < 6; i++) + { + sc_mul(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes); + } + sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); + + // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs + const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); + ge_p3 initial_transcript_p3; + rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size()))); + ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3); + + init_done = true; + } + + // Given two scalar arrays, construct a vector pre-commitment: + // + // a = (a_0, ..., a_{n-1}) + // b = (b_0, ..., b_{n-1}) + // + // Outputs a_0*Gi_0 + ... + a_{n-1}*Gi_{n-1} + + // b_0*Hi_0 + ... + b_{n-1}*Hi_{n-1} + static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN"); + + std::vector multiexp_data; + multiexp_data.reserve(a.size()*2); + for (size_t i = 0; i < a.size(); ++i) + { + multiexp_data.emplace_back(a[i], Gi_p3[i]); + multiexp_data.emplace_back(b[i], Hi_p3[i]); + } + return multiexp(multiexp_data, 2 * a.size()); + } + + // Helper function used to compute the L and R terms used in the inner-product round function + static rct::key compute_LR(size_t size, const rct::key &y, const std::vector &G, size_t G0, const std::vector &H, size_t H0, const rct::keyV &a, size_t a0, const rct::keyV &b, size_t b0, const rct::key &c, const rct::key &d) + { + CHECK_AND_ASSERT_THROW_MES(size + G0 <= G.size(), "Incompatible size for G"); + CHECK_AND_ASSERT_THROW_MES(size + H0 <= H.size(), "Incompatible size for H"); + CHECK_AND_ASSERT_THROW_MES(size + a0 <= a.size(), "Incompatible size for a"); + CHECK_AND_ASSERT_THROW_MES(size + b0 <= b.size(), "Incompatible size for b"); + CHECK_AND_ASSERT_THROW_MES(size <= maxN*maxM, "size is too large"); + + std::vector multiexp_data; + multiexp_data.resize(size*2 + 2); + rct::key temp; + for (size_t i = 0; i < size; ++i) + { + sc_mul(temp.bytes, a[a0+i].bytes, y.bytes); + sc_mul(multiexp_data[i*2].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + multiexp_data[i*2].point = G[G0+i]; + + sc_mul(multiexp_data[i*2+1].scalar.bytes, b[b0+i].bytes, INV_EIGHT.bytes); + multiexp_data[i*2+1].point = H[H0+i]; + } + + sc_mul(multiexp_data[2*size].scalar.bytes, c.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + multiexp_data[2*size].point = H_p3; + + sc_mul(multiexp_data[2*size+1].scalar.bytes, d.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + multiexp_data[2*size+1].point = G_p3; + + return multiexp(multiexp_data, 0); + } + + // Given a scalar, construct a vector of its powers: + // + // Output (1,x,x**2,...,x**{n-1}) + static rct::keyV vector_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::keyV res(n); + res[0] = rct::identity(); + if (n == 1) + return res; + res[1] = x; + for (size_t i = 2; i < n; ++i) + { + sc_mul(res[i].bytes, res[i-1].bytes, x.bytes); + } + return res; + } + + // Given a scalar, construct the sum of its powers from 2 to n (where n is a power of 2): + // + // Output x**2 + x**4 + x**6 + ... + x**n + static rct::key sum_of_even_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES((n & (n - 1)) == 0, "Need n to be a power of 2"); + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key x1 = copy(x); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + + rct::key res = copy(x1); + while (n > 2) + { + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + sc_mul(x1.bytes, x1.bytes, x1.bytes); + n /= 2; + } + + return res; + } + + // Given a scalar, return the sum of its powers from 1 to n + // + // Output x**1 + x**2 + x**3 + ... + x**n + static rct::key sum_of_scalar_powers(const rct::key &x, size_t n) + { + CHECK_AND_ASSERT_THROW_MES(n != 0, "Need n > 0"); + + rct::key res = ONE; + if (n == 1) + return x; + + n += 1; + rct::key x1 = copy(x); + + const bool is_power_of_2 = (n & (n - 1)) == 0; + if (is_power_of_2) + { + sc_add(res.bytes, res.bytes, x1.bytes); + while (n > 2) + { + sc_mul(x1.bytes, x1.bytes, x1.bytes); + sc_muladd(res.bytes, x1.bytes, res.bytes, res.bytes); + n /= 2; + } + } + else + { + rct::key prev = x1; + for (size_t i = 1; i < n; ++i) + { + if (i > 1) + sc_mul(prev.bytes, prev.bytes, x1.bytes); + sc_add(res.bytes, res.bytes, prev.bytes); + } + } + sc_sub(res.bytes, res.bytes, ONE.bytes); + + return res; + } + + // Given two scalar arrays, construct the weighted inner product against another scalar + // + // Output a_0*b_0*y**1 + a_1*b_1*y**2 + ... + a_{n-1}*b_{n-1}*y**n + static rct::key weighted_inner_product(const epee::span &a, const epee::span &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::key res = rct::zero(); + rct::key y_power = ONE; + rct::key temp; + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(temp.bytes, a[i].bytes, b[i].bytes); + sc_mul(y_power.bytes, y_power.bytes, y.bytes); + sc_muladd(res.bytes, temp.bytes, y_power.bytes, res.bytes); + } + return res; + } + + static rct::key weighted_inner_product(const rct::keyV &a, const epee::span &b, const rct::key &y) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + return weighted_inner_product(epee::to_span(a), b, y); + } + + // Fold inner-product point vectors + static void hadamard_fold(std::vector &v, const rct::key &a, const rct::key &b) + { + CHECK_AND_ASSERT_THROW_MES((v.size() & 1) == 0, "Vector size should be even"); + const size_t sz = v.size() / 2; + for (size_t n = 0; n < sz; ++n) + { + ge_dsmp c[2]; + ge_dsm_precomp(c[0], &v[n]); + ge_dsm_precomp(c[1], &v[sz + n]); + ge_double_scalarmult_precomp_vartime2_p3(&v[n], a.bytes, c[0], b.bytes, c[1]); + } + v.resize(sz); + } + + // Add vectors componentwise + static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b) + { + CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b"); + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b[i].bytes); + } + return res; + } + + // Add a scalar to all elements of a vector + static rct::keyV vector_add(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_add(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Subtract a scalar from all elements of a vector + static rct::keyV vector_subtract(const rct::keyV &a, const rct::key &b) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_sub(res[i].bytes, a[i].bytes, b.bytes); + } + return res; + } + + // Multiply a scalar by all elements of a vector + static rct::keyV vector_scalar(const epee::span &a, const rct::key &x) + { + rct::keyV res(a.size()); + for (size_t i = 0; i < a.size(); ++i) + { + sc_mul(res[i].bytes, a[i].bytes, x.bytes); + } + return res; + } + + // Inversion helper function + static rct::key sm(rct::key y, int n, const rct::key &x) + { + while (n--) + sc_mul(y.bytes, y.bytes, y.bytes); + sc_mul(y.bytes, y.bytes, x.bytes); + return y; + } + + // Compute the inverse of a nonzero + static rct::key invert(const rct::key &x) + { + CHECK_AND_ASSERT_THROW_MES(!(x == ZERO), "Cannot invert zero!"); + rct::key _1, _10, _100, _11, _101, _111, _1001, _1011, _1111; + + _1 = x; + sc_mul(_10.bytes, _1.bytes, _1.bytes); + sc_mul(_100.bytes, _10.bytes, _10.bytes); + sc_mul(_11.bytes, _10.bytes, _1.bytes); + sc_mul(_101.bytes, _10.bytes, _11.bytes); + sc_mul(_111.bytes, _10.bytes, _101.bytes); + sc_mul(_1001.bytes, _10.bytes, _111.bytes); + sc_mul(_1011.bytes, _10.bytes, _1001.bytes); + sc_mul(_1111.bytes, _100.bytes, _1011.bytes); + + rct::key inv; + sc_mul(inv.bytes, _1111.bytes, _1.bytes); + + inv = sm(inv, 123 + 3, _101); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 4, _1001); + inv = sm(inv, 2, _11); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 1 + 3, _101); + inv = sm(inv, 3 + 3, _101); + inv = sm(inv, 3, _111); + inv = sm(inv, 1 + 4, _1111); + inv = sm(inv, 2 + 3, _111); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 2 + 4, _1011); + inv = sm(inv, 6 + 4, _1001); + inv = sm(inv, 2 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 3 + 2, _11); + inv = sm(inv, 1 + 4, _1001); + inv = sm(inv, 1 + 3, _111); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 1 + 4, _1011); + inv = sm(inv, 3, _101); + inv = sm(inv, 2 + 4, _1111); + inv = sm(inv, 3, _101); + inv = sm(inv, 1 + 2, _11); + + return inv; + } + + // Invert a batch of scalars, all of which _must_ be nonzero + static rct::keyV invert(rct::keyV x) + { + rct::keyV scratch; + scratch.reserve(x.size()); + + rct::key acc = rct::identity(); + for (size_t n = 0; n < x.size(); ++n) + { + CHECK_AND_ASSERT_THROW_MES(!(x[n] == ZERO), "Cannot invert zero!"); + scratch.push_back(acc); + if (n == 0) + acc = x[0]; + else + sc_mul(acc.bytes, acc.bytes, x[n].bytes); + } + + acc = invert(acc); + + rct::key tmp; + for (int i = x.size(); i-- > 0; ) + { + sc_mul(tmp.bytes, acc.bytes, x[i].bytes); + sc_mul(x[i].bytes, acc.bytes, scratch[i].bytes); + acc = tmp; + } + + return x; + } + + // Compute the slice of a vector + static epee::span slice(const rct::keyV &a, size_t start, size_t stop) + { + CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index"); + CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index"); + CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices"); + return epee::span(&a[start], stop - start); + } + + // Update the transcript + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0) + { + rct::key data[2]; + data[0] = transcript; + data[1] = update_0; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + static rct::key transcript_update(rct::key &transcript, const rct::key &update_0, const rct::key &update_1) + { + rct::key data[3]; + data[0] = transcript; + data[1] = update_0; + data[2] = update_1; + rct::hash_to_scalar(transcript, data, sizeof(data)); + return transcript; + } + + // Given a value v [0..2**N) and a mask gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::key &sv, const rct::key &gamma) + { + return bulletproof_plus_PROVE(rct::keyV(1, sv), rct::keyV(1, gamma)); + } + + BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma) + { + return bulletproof_plus_PROVE(std::vector(1, v), rct::keyV(1, gamma)); + } + + // Given a set of values v [0..2**N) and masks gamma, construct a range proof + BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &sv, const rct::keyV &gamma) + { + // Sanity check on inputs + CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma"); + CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty"); + for (const rct::key &sve: sv) + CHECK_AND_ASSERT_THROW_MES(is_reduced(sve), "Invalid sv input"); + for (const rct::key &g: gamma) + CHECK_AND_ASSERT_THROW_MES(is_reduced(g), "Invalid gamma input"); + + init_exponents(); + + // Useful proof bounds + // + // N: number of bits in each range (here, 64) + // logN: base-2 logarithm + // M: first power of 2 greater than or equal to the number of range proofs to aggregate + // logM: base-2 logarithm + constexpr size_t logN = 6; // log2(64) + constexpr size_t N = 1< 0; ) + { + if (j < sv.size() && (sv[j][i/8] & (((uint64_t)1)<<(i%8)))) + { + aL[j*N+i] = rct::identity(); + aL8[j*N+i] = INV_EIGHT; + aR[j*N+i] = aR8[j*N+i] = rct::zero(); + } + else + { + aL[j*N+i] = aL8[j*N+i] = rct::zero(); + aR[j*N+i] = MINUS_ONE; + aR8[j*N+i] = MINUS_INV_EIGHT; + } + } + } + +try_again: + // This is a Fiat-Shamir transcript + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(V)); + + // A + rct::key alpha = rct::skGen(); + rct::key pre_A = vector_exponent(aL8, aR8); + rct::key A; + sc_mul(temp.bytes, alpha.bytes, INV_EIGHT.bytes); + rct::addKeys(A, pre_A, rct::scalarmultBase(temp)); + + // Challenges + rct::key y = transcript_update(transcript, A); + if (y == rct::zero()) + { + MINFO("y is 0, trying again"); + goto try_again; + } + rct::key z = transcript = rct::hash_to_scalar(y); + if (z == rct::zero()) + { + MINFO("z is 0, trying again"); + goto try_again; + } + rct::key z_squared; + sc_mul(z_squared.bytes, z.bytes, z.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + // + // We compute this iteratively in order to reduce scalar operations. + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_mul(d[i].bytes, d[i-1].bytes, TWO.bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + rct::keyV y_powers = vector_of_scalar_powers(y, MN+2); + + // Prepare inner product terms + rct::keyV aL1 = vector_subtract(aL, z); + + rct::keyV aR1 = vector_add(aR, z); + rct::keyV d_y(MN); + for (size_t i = 0; i < MN; i++) + { + sc_mul(d_y[i].bytes, d[i].bytes, y_powers[MN-i].bytes); + } + aR1 = vector_add(aR1, d_y); + + rct::key alpha1 = alpha; + temp = ONE; + for (size_t j = 0; j < sv.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + sc_mul(temp2.bytes, y_powers[MN+1].bytes, temp.bytes); + sc_mul(temp2.bytes, temp2.bytes, gamma[j].bytes); + sc_add(alpha1.bytes, alpha1.bytes, temp2.bytes); + } + + // These are used in the inner product rounds + size_t nprime = MN; + std::vector Gprime(MN); + std::vector Hprime(MN); + rct::keyV aprime(MN); + rct::keyV bprime(MN); + + const rct::key yinv = invert(y); + rct::keyV yinvpow(MN); + yinvpow[0] = ONE; + for (size_t i = 0; i < MN; ++i) + { + Gprime[i] = Gi_p3[i]; + Hprime[i] = Hi_p3[i]; + if (i > 0) + { + sc_mul(yinvpow[i].bytes, yinvpow[i-1].bytes, yinv.bytes); + } + aprime[i] = aL1[i]; + bprime[i] = aR1[i]; + } + rct::keyV L(logMN); + rct::keyV R(logMN); + int round = 0; + + // Inner-product rounds + while (nprime > 1) + { + nprime /= 2; + + rct::key cL = weighted_inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()), y); + rct::key cR = weighted_inner_product(vector_scalar(slice(aprime, nprime, aprime.size()), y_powers[nprime]), slice(bprime, 0, nprime), y); + + rct::key dL = rct::skGen(); + rct::key dR = rct::skGen(); + + L[round] = compute_LR(nprime, yinvpow[nprime], Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, cL, dL); + R[round] = compute_LR(nprime, y_powers[nprime], Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, cR, dR); + + const rct::key challenge = transcript_update(transcript, L[round], R[round]); + if (challenge == rct::zero()) + { + MINFO("challenge is 0, trying again"); + goto try_again; + } + + const rct::key challenge_inv = invert(challenge); + + sc_mul(temp.bytes, yinvpow[nprime].bytes, challenge.bytes); + hadamard_fold(Gprime, challenge_inv, temp); + hadamard_fold(Hprime, challenge, challenge_inv); + + sc_mul(temp.bytes, challenge_inv.bytes, y_powers[nprime].bytes); + aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), challenge), vector_scalar(slice(aprime, nprime, aprime.size()), temp)); + bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), challenge_inv), vector_scalar(slice(bprime, nprime, bprime.size()), challenge)); + + rct::key challenge_squared; + sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes); + rct::key challenge_squared_inv = invert(challenge_squared); + sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes); + sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes); + + ++round; + } + + // Final round computations + rct::key r = rct::skGen(); + rct::key s = rct::skGen(); + rct::key d_ = rct::skGen(); + rct::key eta = rct::skGen(); + + std::vector A1_data; + A1_data.reserve(4); + A1_data.resize(4); + + sc_mul(A1_data[0].scalar.bytes, r.bytes, INV_EIGHT.bytes); + A1_data[0].point = Gprime[0]; + + sc_mul(A1_data[1].scalar.bytes, s.bytes, INV_EIGHT.bytes); + A1_data[1].point = Hprime[0]; + + sc_mul(A1_data[2].scalar.bytes, d_.bytes, INV_EIGHT.bytes); + ge_p3 G_p3; + ge_frombytes_vartime(&G_p3, rct::G.bytes); + A1_data[2].point = G_p3; + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, bprime[0].bytes); + sc_mul(temp2.bytes, s.bytes, y.bytes); + sc_mul(temp2.bytes, temp2.bytes, aprime[0].bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(A1_data[3].scalar.bytes, temp.bytes, INV_EIGHT.bytes); + ge_p3 H_p3; + ge_frombytes_vartime(&H_p3, rct::H.bytes); + A1_data[3].point = H_p3; + + rct::key A1 = multiexp(A1_data, 0); + + sc_mul(temp.bytes, r.bytes, y.bytes); + sc_mul(temp.bytes, temp.bytes, s.bytes); + sc_mul(temp.bytes, temp.bytes, INV_EIGHT.bytes); + sc_mul(temp2.bytes, eta.bytes, INV_EIGHT.bytes); + rct::key B; + rct::addKeys2(B, temp2, temp, rct::H); + + rct::key e = transcript_update(transcript, A1, B); + if (e == rct::zero()) + { + MINFO("e is 0, trying again"); + goto try_again; + } + rct::key e_squared; + sc_mul(e_squared.bytes, e.bytes, e.bytes); + + rct::key r1; + sc_muladd(r1.bytes, aprime[0].bytes, e.bytes, r.bytes); + + rct::key s1; + sc_muladd(s1.bytes, bprime[0].bytes, e.bytes, s.bytes); + + rct::key d1; + sc_muladd(d1.bytes, d_.bytes, e.bytes, eta.bytes); + sc_muladd(d1.bytes, alpha1.bytes, e_squared.bytes, d1.bytes); + + return BulletproofPlus(std::move(V), A, A1, B, r1, s1, d1, std::move(L), std::move(R)); + } + + BulletproofPlus bulletproof_plus_PROVE(const std::vector &v, const rct::keyV &gamma) + { + CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma"); + + // vG + gammaH + rct::keyV sv(v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + sv[i] = rct::d2h(v[i]); + } + return bulletproof_plus_PROVE(sv, gamma); + } + + struct bp_plus_proof_data_t + { + rct::key y, z, e; + std::vector challenges; + size_t logM, inv_offset; + }; + + // Given a batch of range proofs, determine if they are all valid + bool bulletproof_plus_VERIFY(const std::vector &proofs) + { + init_exponents(); + + const size_t logN = 6; + const size_t N = 1 << logN; + + // Set up + size_t max_length = 0; // size of each of the longest proof's inner-product vectors + size_t nV = 0; // number of output commitments across all proofs + size_t inv_offset = 0; + size_t max_logM = 0; + + std::vector proof_data; + proof_data.reserve(proofs.size()); + + // We'll perform only a single batch inversion across all proofs in the batch, + // since batch inversion requires only one scalar inversion operation. + std::vector to_invert; + to_invert.reserve(11 * proofs.size()); // maximal size, given the aggregation limit + + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + + // Sanity checks + CHECK_AND_ASSERT_MES(is_reduced(proof.r1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.s1), false, "Input scalar not in range"); + CHECK_AND_ASSERT_MES(is_reduced(proof.d1), false, "Input scalar not in range"); + + CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element"); + CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes"); + CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof"); + + max_length = std::max(max_length, proof.L.size()); + nV += proof.V.size(); + + proof_data.push_back({}); + bp_plus_proof_data_t &pd = proof_data.back(); + + // Reconstruct the challenges + rct::key transcript = copy(initial_transcript); + transcript = transcript_update(transcript, rct::hash_to_scalar(proof.V)); + pd.y = transcript_update(transcript, proof.A); + CHECK_AND_ASSERT_MES(!(pd.y == rct::zero()), false, "y == 0"); + pd.z = transcript = rct::hash_to_scalar(pd.y); + CHECK_AND_ASSERT_MES(!(pd.z == rct::zero()), false, "z == 0"); + + // Determine the number of inner-product rounds based on proof size + size_t M; + for (pd.logM = 0; (M = 1< 0, false, "Zero rounds"); + + // The inner-product challenges are computed per round + pd.challenges.resize(rounds); + for (size_t j = 0; j < rounds; ++j) + { + pd.challenges[j] = transcript_update(transcript, proof.L[j], proof.R[j]); + CHECK_AND_ASSERT_MES(!(pd.challenges[j] == rct::zero()), false, "challenges[j] == 0"); + } + + // Final challenge + pd.e = transcript_update(transcript,proof.A1,proof.B); + CHECK_AND_ASSERT_MES(!(pd.e == rct::zero()), false, "e == 0"); + + // Batch scalar inversions + pd.inv_offset = inv_offset; + for (size_t j = 0; j < rounds; ++j) + to_invert.push_back(pd.challenges[j]); + to_invert.push_back(pd.y); + inv_offset += rounds + 1; + } + CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large"); + size_t maxMN = 1u << max_length; + + rct::key temp; + rct::key temp2; + + // Final batch proof data + std::vector multiexp_data; + multiexp_data.reserve(nV + (2 * (max_logM + logN) + 3) * proofs.size() + 2 * maxMN); + multiexp_data.resize(2 * maxMN); + + const std::vector inverses = invert(std::move(to_invert)); + to_invert.clear(); + + // Weights and aggregates + // + // The idea is to take the single multiscalar multiplication used in the verification + // of each proof in the batch and weight it using a random weighting factor, resulting + // in just one multiscalar multiplication check to zero for the entire batch. + // We can further simplify the verifier complexity by including common group elements + // only once in this single multiscalar multiplication. + // Common group elements' weighted scalar sums are tracked across proofs for this reason. + // + // To build a multiscalar multiplication for each proof, we use the method described in + // Section 6.1 of the preprint. Note that the result given there does not account for + // the construction of the inner-product inputs that are produced in the range proof + // verifier algorithm; we have done so here. + rct::key G_scalar = rct::zero(); + rct::key H_scalar = rct::zero(); + rct::keyV Gi_scalars(maxMN, rct::zero()); + rct::keyV Hi_scalars(maxMN, rct::zero()); + + int proof_data_index = 0; + rct::keyV challenges_cache; + std::vector proof8_V, proof8_L, proof8_R; + + // Process each proof and add to the weighted batch + for (const BulletproofPlus *p: proofs) + { + const BulletproofPlus &proof = *p; + const bp_plus_proof_data_t &pd = proof_data[proof_data_index++]; + + CHECK_AND_ASSERT_MES(proof.L.size() == 6+pd.logM, false, "Proof is not the expected size"); + const size_t M = 1 << pd.logM; + const size_t MN = M*N; + + // Random weighting factor must be nonzero, which is exceptionally unlikely! + rct::key weight = ZERO; + while (weight == ZERO) + { + weight = rct::skGen(); + } + + // Rescale previously offset proof elements + // + // This ensures that all such group elements are in the prime-order subgroup. + proof8_V.resize(proof.V.size()); for (size_t i = 0; i < proof.V.size(); ++i) rct::scalarmult8(proof8_V[i], proof.V[i]); + proof8_L.resize(proof.L.size()); for (size_t i = 0; i < proof.L.size(); ++i) rct::scalarmult8(proof8_L[i], proof.L[i]); + proof8_R.resize(proof.R.size()); for (size_t i = 0; i < proof.R.size(); ++i) rct::scalarmult8(proof8_R[i], proof.R[i]); + ge_p3 proof8_A1; + ge_p3 proof8_B; + ge_p3 proof8_A; + rct::scalarmult8(proof8_A1, proof.A1); + rct::scalarmult8(proof8_B, proof.B); + rct::scalarmult8(proof8_A, proof.A); + + // Compute necessary powers of the y-challenge + rct::key y_MN = copy(pd.y); + rct::key y_MN_1; + size_t temp_MN = MN; + while (temp_MN > 1) + { + sc_mul(y_MN.bytes, y_MN.bytes, y_MN.bytes); + temp_MN /= 2; + } + sc_mul(y_MN_1.bytes, y_MN.bytes, pd.y.bytes); + + // V_j: -e**2 * z**(2*j+1) * y**(MN+1) * weight + rct::key e_squared; + sc_mul(e_squared.bytes, pd.e.bytes, pd.e.bytes); + + rct::key z_squared; + sc_mul(z_squared.bytes, pd.z.bytes, pd.z.bytes); + + sc_sub(temp.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(temp.bytes, temp.bytes, y_MN_1.bytes); + sc_mul(temp.bytes, temp.bytes, weight.bytes); + for (size_t j = 0; j < proof8_V.size(); j++) + { + sc_mul(temp.bytes, temp.bytes, z_squared.bytes); + multiexp_data.emplace_back(temp, proof8_V[j]); + } + + // B: -weight + sc_mul(temp.bytes, MINUS_ONE.bytes, weight.bytes); + multiexp_data.emplace_back(temp, proof8_B); + + // A1: -weight*e + sc_mul(temp.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(temp, proof8_A1); + + // A: -weight*e*e + rct::key minus_weight_e_squared; + sc_mul(minus_weight_e_squared.bytes, temp.bytes, pd.e.bytes); + multiexp_data.emplace_back(minus_weight_e_squared, proof8_A); + + // G: weight*d1 + sc_muladd(G_scalar.bytes, weight.bytes, proof.d1.bytes, G_scalar.bytes); + + // Windowed vector + // d[j*N+i] = z**(2*(j+1)) * 2**i + rct::keyV d(MN, rct::zero()); + d[0] = z_squared; + for (size_t i = 1; i < N; i++) + { + sc_add(d[i].bytes, d[i-1].bytes, d[i-1].bytes); + } + + for (size_t j = 1; j < M; j++) + { + for (size_t i = 0; i < N; i++) + { + sc_mul(d[j*N+i].bytes, d[(j-1)*N+i].bytes, z_squared.bytes); + } + } + + // More efficient computation of sum(d) + rct::key sum_d; + sc_mul(sum_d.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, sum_of_even_powers(pd.z, 2*M).bytes); + + // H: weight*( r1*y*s1 + e**2*( y**(MN+1)*z*sum(d) + (z**2-z)*sum(y) ) ) + rct::key sum_y = sum_of_scalar_powers(pd.y, MN); + sc_sub(temp.bytes, z_squared.bytes, pd.z.bytes); + sc_mul(temp.bytes, temp.bytes, sum_y.bytes); + + sc_mul(temp2.bytes, y_MN_1.bytes, pd.z.bytes); + sc_mul(temp2.bytes, temp2.bytes, sum_d.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_mul(temp.bytes, temp.bytes, e_squared.bytes); + sc_mul(temp2.bytes, proof.r1.bytes, pd.y.bytes); + sc_mul(temp2.bytes, temp2.bytes, proof.s1.bytes); + sc_add(temp.bytes, temp.bytes, temp2.bytes); + sc_muladd(H_scalar.bytes, temp.bytes, weight.bytes, H_scalar.bytes); + + // Compute the number of rounds for the inner-product argument + const size_t rounds = pd.logM+logN; + CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); + + const rct::key *challenges_inv = &inverses[pd.inv_offset]; + const rct::key yinv = inverses[pd.inv_offset + rounds]; + + // Compute challenge products + challenges_cache.resize(1< 0; --s) + { + sc_mul(challenges_cache[s].bytes, challenges_cache[s/2].bytes, pd.challenges[j].bytes); + sc_mul(challenges_cache[s-1].bytes, challenges_cache[s/2].bytes, challenges_inv[j].bytes); + } + } + + // Gi and Hi + rct::key e_r1_w_y; + sc_mul(e_r1_w_y.bytes, pd.e.bytes, proof.r1.bytes); + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, weight.bytes); + rct::key e_s1_w; + sc_mul(e_s1_w.bytes, pd.e.bytes, proof.s1.bytes); + sc_mul(e_s1_w.bytes, e_s1_w.bytes, weight.bytes); + rct::key e_squared_z_w; + sc_mul(e_squared_z_w.bytes, e_squared.bytes, pd.z.bytes); + sc_mul(e_squared_z_w.bytes, e_squared_z_w.bytes, weight.bytes); + rct::key minus_e_squared_z_w; + sc_sub(minus_e_squared_z_w.bytes, ZERO.bytes, e_squared_z_w.bytes); + rct::key minus_e_squared_w_y; + sc_sub(minus_e_squared_w_y.bytes, ZERO.bytes, e_squared.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, weight.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, y_MN.bytes); + for (size_t i = 0; i < MN; ++i) + { + rct::key g_scalar = copy(e_r1_w_y); + rct::key h_scalar; + + // Use the binary decomposition of the index + sc_muladd(g_scalar.bytes, g_scalar.bytes, challenges_cache[i].bytes, e_squared_z_w.bytes); + sc_muladd(h_scalar.bytes, e_s1_w.bytes, challenges_cache[(~i) & (MN-1)].bytes, minus_e_squared_z_w.bytes); + + // Complete the scalar derivation + sc_add(Gi_scalars[i].bytes, Gi_scalars[i].bytes, g_scalar.bytes); + sc_muladd(h_scalar.bytes, minus_e_squared_w_y.bytes, d[i].bytes, h_scalar.bytes); + sc_add(Hi_scalars[i].bytes, Hi_scalars[i].bytes, h_scalar.bytes); + + // Update iterated values + sc_mul(e_r1_w_y.bytes, e_r1_w_y.bytes, yinv.bytes); + sc_mul(minus_e_squared_w_y.bytes, minus_e_squared_w_y.bytes, yinv.bytes); + } + + // L_j: -weight*e*e*challenges[j]**2 + // R_j: -weight*e*e*challenges[j]**(-2) + for (size_t j = 0; j < rounds; ++j) + { + sc_mul(temp.bytes, pd.challenges[j].bytes, pd.challenges[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_L[j]); + + sc_mul(temp.bytes, challenges_inv[j].bytes, challenges_inv[j].bytes); + sc_mul(temp.bytes, temp.bytes, minus_weight_e_squared.bytes); + multiexp_data.emplace_back(temp, proof8_R[j]); + } + } + + // Verify all proofs in the weighted batch + multiexp_data.emplace_back(G_scalar, rct::G); + multiexp_data.emplace_back(H_scalar, rct::H); + for (size_t i = 0; i < maxMN; ++i) + { + multiexp_data[i * 2] = {Gi_scalars[i], Gi_p3[i]}; + multiexp_data[i * 2 + 1] = {Hi_scalars[i], Hi_p3[i]}; + } + if (!(multiexp(multiexp_data, 2 * maxMN) == rct::identity())) + { + MERROR("Verification failure"); + return false; + } + + return true; + } + + bool bulletproof_plus_VERIFY(const std::vector &proofs) + { + std::vector proof_pointers; + proof_pointers.reserve(proofs.size()); + for (const BulletproofPlus &proof: proofs) + proof_pointers.push_back(&proof); + return bulletproof_plus_VERIFY(proof_pointers); + } + + bool bulletproof_plus_VERIFY(const BulletproofPlus &proof) + { + std::vector proofs; + proofs.push_back(&proof); + return bulletproof_plus_VERIFY(proofs); + } +} \ No newline at end of file diff --git a/src/ringct/bulletproofs_plus.h b/src/ringct/bulletproofs_plus.h new file mode 100644 index 00000000000..93090670da1 --- /dev/null +++ b/src/ringct/bulletproofs_plus.h @@ -0,0 +1,49 @@ +// Copyright (c) 2017-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#ifndef BULLETPROOFS_PLUS_H +#define BULLETPROOFS_PLUS_H + +#include "rctTypes.h" + +namespace rct +{ + +BulletproofPlus bulletproof_plus_PROVE(const rct::key &v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(uint64_t v, const rct::key &gamma); +BulletproofPlus bulletproof_plus_PROVE(const rct::keyV &v, const rct::keyV &gamma); +BulletproofPlus bulletproof_plus_PROVE(const std::vector &v, const rct::keyV &gamma); +bool bulletproof_plus_VERIFY(const BulletproofPlus &proof); +bool bulletproof_plus_VERIFY(const std::vector &proofs); +bool bulletproof_plus_VERIFY(const std::vector &proofs); + +} + +#endif \ No newline at end of file diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 8dd42a6ab44..59e515b9748 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -235,6 +235,39 @@ namespace rct { END_SERIALIZE() }; + struct BulletproofPlus + { + rct::keyV V; + rct::key A, A1, B; + rct::key r1, s1, d1; + rct::keyV L, R; + + BulletproofPlus(): + A({}), A1({}), B({}), r1({}), s1({}), d1({}) {} + BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): + V(V), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} + + bool operator==(const BulletproofPlus &other) const { return V == other.V && A == other.A && A1 == other.A1 && B == other.B && r1 == other.r1 && s1 == other.s1 && d1 == other.d1 && L == other.L && R == other.R; } + + BEGIN_SERIALIZE_OBJECT() + // Commitments aren't saved, they're restored via outPk + // FIELD(V) + FIELD(A) + FIELD(A1) + FIELD(B) + FIELD(r1) + FIELD(s1) + FIELD(d1) + FIELD(L) + FIELD(R) + + if (L.empty() || L.size() != R.size()) + return false; + END_SERIALIZE() + }; + size_t n_bulletproof_amounts(const Bulletproof &proof); size_t n_bulletproof_max_amounts(const Bulletproof &proof); size_t n_bulletproof_amounts(const std::vector &proofs); @@ -264,11 +297,14 @@ namespace rct { Bulletproof = 3, Bulletproof2 = 4, CLSAG = 5, + BulletproofPlus = 6, }; - inline bool is_rct_simple(RCTType type) { return tools::equals_any(type, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG); } + inline bool is_rct_simple(RCTType type) { return tools::equals_any(type, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG, RCTType::BulletproofPlus); } inline bool is_rct_bulletproof(RCTType type) { return tools::equals_any(type, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG); } inline bool is_rct_borromean(RCTType type) { return tools::equals_any(type, RCTType::Simple, RCTType::Full); } + inline bool is_rct_bulletproof_plus(RCTType type) { return tools::equals_any(type, RCTType::BulletproofPlus); } + inline bool is_rct_clsag(RCTType type) { return tools::equals_any(type, RCTType::CLSAG, RCTType::BulletproofPlus); } enum class RangeProofType : uint8_t { Borromean = 0, Bulletproof = 1, MultiOutputBulletproof = 2, PaddedBulletproof = 3 }; struct RCTConfig { From 548eea5b95ac94a774a895995e8d8b0fba516a26 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Wed, 16 Jul 2025 15:00:26 +0530 Subject: [PATCH 143/182] fix : log for MN registration and status --- src/daemon/rpc_command_executor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 00a0af6ee96..485bdbaeec9 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -502,8 +502,8 @@ bool rpc_command_executor::show_status() { my_mn_active = state["active"].get(); my_decomm_remaining = state["earned_downtime_blocks"].get(); my_mn_last_uptime = state["last_uptime_proof"].get(); - my_reason_all = state["last_decommission_reason_consensus_all"].get(); - my_reason_any = state["last_decommission_reason_consensus_any"].get(); + my_reason_all = state.value("last_decommission_reason_consensus_all", 0); + my_reason_any = state.value("last_decommission_reason_consensus_any", 0); } } } @@ -2537,7 +2537,7 @@ bool rpc_command_executor::prepare_registration(bool force_registration) auto& registration = *maybe_registration; - tools::success_msg_writer() << registration["registration_cmd"]; + tools::success_msg_writer() << registration["registration_cmd"].get(); } return true; From dbf5cff3ccb89743475ea53640e33f9f8242a73d Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Fri, 18 Jul 2025 17:14:12 +0530 Subject: [PATCH 144/182] plug in Bulletproof+ --- src/cryptonote_basic/hardfork.cpp | 2 +- src/cryptonote_config.h | 3 +- src/cryptonote_core/blockchain.cpp | 43 +++- src/cryptonote_core/cryptonote_core.cpp | 23 +- src/ringct/bulletproofs.cc | 24 +-- src/ringct/bulletproofs_plus.cc | 4 +- src/ringct/rctSigs.cpp | 214 +++++++++++++++---- src/ringct/rctTypes.cpp | 64 ++++-- src/ringct/rctTypes.h | 50 ++++- tests/core_tests/beldex_tests.cpp | 4 +- tests/core_tests/bulletproofs.cpp | 2 +- tests/core_tests/chaingen.h | 2 +- tests/performance_tests/check_tx_signature.h | 4 +- tests/performance_tests/construct_tx.h | 2 +- tests/unit_tests/master_nodes.cpp | 2 +- 15 files changed, 347 insertions(+), 96 deletions(-) diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 97bfeea0faf..3cb4c6a5bac 100755 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -150,7 +150,7 @@ get_ideal_block_version(network_type nettype, uint64_t height) result.first = it->version; result.second = it->mnode_revision; } - if (result.first < hf::hf20_bulletproof_plusplus) + if (result.first < hf::hf20_bulletproof_plus) result.second = static_cast(it->version); } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 6fe98687641..aa1c5892fce 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -203,7 +203,7 @@ enum class hf : uint8_t hf17_POS, // Proof Of Stake, Batched Governance hf18_bns, hf19_enhance_bns, // provided EVM address in BNS - hf20_bulletproof_plusplus, + hf20_bulletproof_plus, _next, none = 0 @@ -234,6 +234,7 @@ namespace feature { constexpr auto POS = hf::hf17_POS; constexpr auto CLSAG = hf::hf15_flash; constexpr auto PROOF_BTENC = hf::hf18_bns; + constexpr auto BULLETPROOF_PLUS = hf::hf20_bulletproof_plus; } enum network_type : uint8_t diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 6437beab056..78b18e44e55 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1342,7 +1342,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl } } - if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version > hf::hf20_bulletproof_plusplus) + if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version > hf::hf20_bulletproof_plus) { if (version >= hf::hf17_POS && reward_parts.governance_paid == 0) { @@ -1372,7 +1372,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl // TODO(beldex): eliminate all floating point math in reward calculations. uint64_t max_base_reward = reward_parts.base_miner + reward_parts.governance_paid + reward_parts.master_node_total + 1; uint64_t max_money_in_use = max_base_reward + reward_parts.miner_fee; - if (money_in_use > max_money_in_use && version > hf::hf20_bulletproof_plusplus) + if (money_in_use > max_money_in_use && version > hf::hf20_bulletproof_plus) { MERROR_VER("coinbase transaction spends too much money (" << print_money(money_in_use) << "). Maximum block reward is " << print_money(max_money_in_use) << " (= " << print_money(max_base_reward) << " base + " << print_money(reward_parts.miner_fee) << " fees)"); @@ -3157,6 +3157,32 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context return false; } + // allow bulletproofs plus + if (hf_version < feature::BULLETPROOF_PLUS) { + if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { + const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); + if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty()) + { + MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(feature::BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + + // forbid bulletproofs + if (hf_version >= feature::BULLETPROOF_PLUS) { + if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { + const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); + if (bulletproof) + { + MERROR_VER("Bulletproof range proofs are not allowed after v" + std::to_string(feature::BULLETPROOF_PLUS)); + tvc.m_invalid_output = true; + return false; + } + } + } + return true; } //------------------------------------------------------------------ @@ -3197,7 +3223,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr } } } - else if (tools::equals_any(rv.type, rct::RCTType::Simple, rct::RCTType::Bulletproof, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG)) + else if (tools::equals_any(rv.type, rct::RCTType::Simple, rct::RCTType::Bulletproof, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)) { CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys"); rv.mixRing.resize(pubkeys.size()); @@ -3232,7 +3258,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr rv.p.MGs[n].II[0] = rct::ki2rct(var::get(tx.vin[n]).k_image); } } - else if (rv.type == rct::RCTType::CLSAG) + else if (rv.type == rct::RCTType::CLSAG || rv.type == rct::RCTType::BulletproofPlus) { if (!tx.pruned) { @@ -3414,6 +3440,7 @@ if (tx.version >= cryptonote::txversion::v2_ringct) case rct::RCTType::Bulletproof: case rct::RCTType::Bulletproof2: case rct::RCTType::CLSAG: + case rct::RCTType::BulletproofPlus: { // check all this, either reconstructed (so should really pass), or not { @@ -3449,7 +3476,7 @@ if (tx.version >= cryptonote::txversion::v2_ringct) } } - const size_t n_sigs = rv.type == rct::RCTType::CLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size(); + const size_t n_sigs = rct::is_rct_clsag(rv.type) ? rv.p.CLSAGs.size() : rv.p.MGs.size(); if (n_sigs != tx.vin.size()) { MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes"); @@ -3458,7 +3485,7 @@ if (tx.version >= cryptonote::txversion::v2_ringct) for (size_t n = 0; n < tx.vin.size(); ++n) { bool error; - if (rv.type == rct::RCTType::CLSAG) + if (rct::is_rct_clsag(rv.type)) error = memcmp(&var::get(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32); else error = rv.p.MGs[n].II.empty() || memcmp(&var::get(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32); @@ -4148,7 +4175,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block // this is a cheap test // HF20 TODO: remove the requirement that minor_version must be >= network version - if (auto v = get_network_version(blk_height); (v > hf::none) && (blk.major_version != v || (blk.major_version < hf::hf20_bulletproof_plusplus && blk.minor_version < static_cast(v)))) + if (auto v = get_network_version(blk_height); (v > hf::none) && (blk.major_version != v || (blk.major_version < hf::hf20_bulletproof_plus && blk.minor_version < static_cast(v)))) { LOG_PRINT_L1("Block with id: " << blk_hash << ", has invalid version " << static_cast(blk.major_version) << "." << +blk.minor_version << "; current: " << static_cast(v) << "." << static_cast(v) << " for height " << blk_height); @@ -4192,7 +4219,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block // HF20 TODO: remove the requirement that minor_version must be >= network version if ((required_major_version > hf::none) && (blk.major_version != required_major_version || - (blk.major_version < hf::hf20_bulletproof_plusplus && + (blk.major_version < hf::hf20_bulletproof_plus && blk.minor_version < static_cast(required_major_version)))) { MGINFO_RED("Block with id: " << blk_hash << ", has invalid version " << static_cast(blk.major_version) << "." << +blk.minor_version << diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 55716b4f665..f528186732a 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1207,6 +1207,16 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + static bool is_canonical_bulletproof_plus_layout(const std::vector &proofs) + { + if (proofs.size() != 1) + return false; + const size_t sz = proofs[0].V.size(); + if (sz == 0 || sz > TX_BULLETPROOF_PLUS_MAX_OUTPUTS) + return false; + return true; + } + //----------------------------------------------------------------------------------------------- void core::parse_incoming_tx_accumulated_batch(std::vector &tx_info, bool kept_by_block) { if (kept_by_block && get_blockchain_storage().is_within_compiled_block_hash_area()) @@ -1273,6 +1283,17 @@ namespace cryptonote } rvv.push_back(&rv); // delayed batch verification break; + case rct::RCTTypeBulletproofPlus: + if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus)) + { + MERROR_VER("Bulletproof_plus does not have canonical form"); + set_semantics_failed(tx_info[n].tx_hash); + tx_info[n].tvc.m_verifivation_failed = true; + tx_info[n].result = false; + break; + } + rvv.push_back(&rv); // delayed batch verification + break; default: MERROR_VER("Unknown rct type: " << (int)rv.type); set_semantics_failed(tx_info[n].tx_hash); @@ -1289,7 +1310,7 @@ namespace cryptonote { if (!tx_info[n].result || tx_info[n].already_have) continue; - if (!rct::is_rct_bulletproof(tx_info[n].tx.rct_signatures.type)) + if (tx_info[n].tx->rct_signatures.type != rct::RCTType::Bulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTType::Bulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTType::CLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTType::BulletproofPlus) continue; if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx.rct_signatures)) { diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index e90955b2d29..3171deb3fd8 100755 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -69,13 +69,12 @@ static rct::key inner_product(const rct::keyV &a, const rct::keyV &b); static constexpr size_t maxN = 64; static constexpr size_t maxM = cryptonote::TX_BULLETPROOF_MAX_OUTPUTS;; -static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static std::shared_ptr straus_HiGi_cache; static std::shared_ptr pippenger_HiGi_cache; -static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; -static const rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; -static const rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; +static const constexpr rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; +static const constexpr rct::key MINUS_ONE = { { 0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } }; +static const constexpr rct::key MINUS_INV_EIGHT = { { 0x74, 0xa4, 0x19, 0x7a, 0xf0, 0x7d, 0x0b, 0xf7, 0x05, 0xc2, 0xda, 0x25, 0x2b, 0x5c, 0x0b, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a } }; static const rct::keyV oneN = vector_dup(rct::identity(), maxN); static const rct::keyV twoN = vector_powers(TWO, maxN); static const rct::key ip12 = inner_product(oneN, twoN); @@ -99,8 +98,7 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - static const std::string domain_separator(cryptonote::hashkey::BULLETPROOF_EXPONENT); - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::BULLETPROOF_EXPONENT + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -120,10 +118,10 @@ static void init_exponents() data.reserve(maxN*maxM*2); for (size_t i = 0; i < maxN*maxM; ++i) { - Hi[i] = get_exponent(rct::H, i * 2); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi[i].bytes) == 0, "ge_frombytes_vartime failed"); - Gi[i] = get_exponent(rct::H, i * 2 + 1); - CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Hi = get_exponent(rct::H, i * 2); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi.bytes) == 0, "ge_frombytes_vartime failed"); + const rct::key Gi = get_exponent(rct::H, i * 2 + 1); + CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi.bytes) == 0, "ge_frombytes_vartime failed"); data.push_back({rct::zero(), Gi_p3[i]}); data.push_back({rct::zero(), Hi_p3[i]}); @@ -132,11 +130,10 @@ static void init_exponents() straus_HiGi_cache = straus_init_cache(data, STRAUS_SIZE_LIMIT); pippenger_HiGi_cache = pippenger_init_cache(data, 0, PIPPENGER_SIZE_LIMIT); - MINFO("Hi/Gi cache size: " << (sizeof(Hi)+sizeof(Gi))/1024 << " kB"); MINFO("Hi_p3/Gi_p3 cache size: " << (sizeof(Hi_p3)+sizeof(Gi_p3))/1024 << " kB"); MINFO("Straus cache size: " << straus_get_cache_size(straus_HiGi_cache)/1024 << " kB"); + size_t cache_size = straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); MINFO("Pippenger cache size: " << pippenger_get_cache_size(pippenger_HiGi_cache)/1024 << " kB"); - size_t cache_size = (sizeof(Hi)+sizeof(Hi_p3))*2 + straus_get_cache_size(straus_HiGi_cache) + pippenger_get_cache_size(pippenger_HiGi_cache); MINFO("Total cache size: " << cache_size/1024 << "kB"); init_done = true; } @@ -894,7 +891,8 @@ bool bulletproof_VERIFY(const std::vector &proofs) multiexp_data.resize(2 * maxMN); PERF_TIMER_START_BP(VERIFY_line_24_25_invert); - const std::vector inverses = invert(to_invert); + const std::vector inverses = invert(std::move(to_invert)); + to_invert.clear(); PERF_TIMER_STOP_BP(VERIFY_line_24_25_invert); // setup weighted aggregates diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc index 10bf6e7a376..3f33c1c70c5 100644 --- a/src/ringct/bulletproofs_plus.cc +++ b/src/ringct/bulletproofs_plus.cc @@ -65,7 +65,7 @@ namespace rct // Proof bounds static constexpr size_t maxN = 64; // maximum number of bits in range - static constexpr size_t maxM = BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof + static constexpr size_t maxM = TX_BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof // Cached public generators static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; @@ -108,7 +108,7 @@ namespace rct // Use hashed values to produce indexed public generators static ge_p3 get_exponent(const rct::key &base, size_t idx) { - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::HASH_KEY_BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); rct::key generator; ge_p3 generator_p3; rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 5e177ae0a10..abb17cf2803 100755 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -36,6 +36,7 @@ #include "bulletproofs.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_config.h" +#include "bulletproofs_plus.h" #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "ringct" @@ -74,6 +75,37 @@ 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(); + const rct::key I = rct::identity(); + size_t nrl = 0; + while ((1u << nrl) < n_outs) + ++nrl; + nrl += 6; + + C.resize(n_outs); + masks.resize(n_outs); + for (size_t i = 0; i < n_outs; ++i) + { + masks[i] = I; + rct::key sv8, sv; + sv = rct::zero(); + sv.bytes[0] = outamounts[i] & 255; + sv.bytes[1] = (outamounts[i] >> 8) & 255; + sv.bytes[2] = (outamounts[i] >> 16) & 255; + sv.bytes[3] = (outamounts[i] >> 24) & 255; + sv.bytes[4] = (outamounts[i] >> 32) & 255; + sv.bytes[5] = (outamounts[i] >> 40) & 255; + sv.bytes[6] = (outamounts[i] >> 48) & 255; + sv.bytes[7] = (outamounts[i] >> 56) & 255; + sc_mul(sv8.bytes, sv.bytes, rct::INV_EIGHT.bytes); + rct::addKeys2(C[i], rct::INV_EIGHT, sv8, rct::H); + } + + return rct::BulletproofPlus{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I)}; + } } namespace rct { @@ -103,6 +135,32 @@ namespace rct { catch (...) { return false; } } + BulletproofPlus proveRangeBulletproofPlus(keyV &C, keyV &masks, const std::vector &amounts, epee::span sk, hw::device &hwdev) + { + CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes"); + masks.resize(amounts.size()); + for (size_t i = 0; i < masks.size(); ++i) + masks[i] = hwdev.genCommitmentMask(sk[i]); + BulletproofPlus proof = bulletproof_plus_PROVE(amounts, masks); + CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size"); + C = proof.V; + return proof; + } + + bool verBulletproofPlus(const BulletproofPlus &proof) + { + try { return bulletproof_plus_VERIFY(proof); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + + bool verBulletproofPlus(const std::vector &proofs) + { + try { return bulletproof_plus_VERIFY(proofs); } + // we can get deep throws from ge_frombytes_vartime if input isn't valid + catch (...) { return false; } + } + //Borromean (c.f. gmax/andytoshi's paper) boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices) { key64 L[2], alpha; @@ -607,10 +665,29 @@ namespace rct { kv.push_back(p.t); } } + else if (rv.type == RCTTypeBulletproofPlus) + { + kv.reserve((6 * 2 + 6) * rv.p.bulletproofs_plus.size()); + for (const auto &p : rv.p.bulletproofs_plus) + { + // V are not hashed as they're expanded from outPk.mask + // (and thus hashed as part of rctSigBase above) + kv.push_back(p.A); + kv.push_back(p.A1); + kv.push_back(p.B); + kv.push_back(p.r1); + kv.push_back(p.s1); + kv.push_back(p.d1); + for (size_t n = 0; n < p.L.size(); ++n) + kv.push_back(p.L[n]); + for (size_t n = 0; n < p.R.size(); ++n) + kv.push_back(p.R[n]); + } + } else { - kv.reserve((64*3+1) * rv.p.rangeSigs.size()); - for (const auto &r: rv.p.rangeSigs) + kv.reserve((64 * 3 + 1) * rv.p.rangeSigs.size()); + for (const auto &r : rv.p.rangeSigs) { for (size_t n = 0; n < 64; ++n) kv.push_back(r.asig.s0[n]); @@ -1092,7 +1169,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(amounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus); } //set txn fee @@ -1123,7 +1200,7 @@ namespace rct { //RCT simple //for post-rct only rctSig genRctSimple(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector &inamounts, const std::vector &outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector *kLRki, multisig_out *msout, const std::vector & index, ctkeyV &outSk, const RCTConfig &rct_config, hw::device &hwdev) { - const bool bulletproof = rct_config.range_proof_type != RangeProofType::Borromean; + const bool bulletproof_or_plus = rct_config.range_proof_type > RangeProofType::Borromean; CHECK_AND_ASSERT_THROW_MES(inamounts.size() > 0, "Empty inamounts"); CHECK_AND_ASSERT_THROW_MES(inamounts.size() == inSk.size(), "Different number of inamounts/inSk"); CHECK_AND_ASSERT_THROW_MES(outamounts.size() == destinations.size(), "Different number of amounts/destinations"); @@ -1139,11 +1216,14 @@ namespace rct { } rctSig rv; - if (bulletproof) + if (bulletproof_or_plus) { switch (rct_config.bp_version) { case 0: + case 4: + rv.type = RCTType::BulletproofPlus; + break; case 3: rv.type = RCTType::CLSAG; break; @@ -1162,7 +1242,7 @@ namespace rct { rv.message = message; rv.outPk.resize(destinations.size()); - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs.resize(destinations.size()); rv.ecdhInfo.resize(destinations.size()); @@ -1174,17 +1254,19 @@ namespace rct { //add destination to sig rv.outPk[i].dest = copy(destinations[i]); //compute range proof - if (!bulletproof) + if (!bulletproof_or_plus) rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); #ifdef DBG - if (!bulletproof) + if (!bulletproof_or_plus) CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif } rv.p.bulletproofs.clear(); - if (bulletproof) + rv.p.bulletproofs_plus.clear(); + if (bulletproof_or_plus) { + const bool plus = is_rct_bulletproof_plus(rv.type); size_t n_amounts = outamounts.size(); size_t amounts_proved = 0; if (rct_config.range_proof_type == RangeProofType::PaddedBulletproof) @@ -1193,19 +1275,32 @@ namespace rct { if (hwdev.get_mode() == hw::device::mode::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(outamounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts, C, masks)); } else { const epee::span keys{&amount_keys[0], amount_keys.size()}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, outamounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); #ifdef DBG CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < outamounts.size(); ++i) { - rv.outPk[i].mask = rct::scalarmult8(C[i]); + if (plus) + rv.outPk[i].mask = C[i]; + else + rv.outPk[i].mask = rct::scalarmult8(C[i]); outSk[i].mask = masks[i]; } } @@ -1213,7 +1308,7 @@ namespace rct { { size_t batch_size = 1; if (rct_config.range_proof_type == RangeProofType::MultiOutputBulletproof) - while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= cryptonote::TX_BULLETPROOF_MAX_OUTPUTS) + while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? cryptonote:TX_BULLETPROOF_PLUS_MAX_OUTPUTS : cryptonote::TX_BULLETPROOF_MAX_OUTPUTS)) batch_size *= 2; rct::keyV C, masks; std::vector batch_amounts(batch_size); @@ -1222,20 +1317,32 @@ namespace rct { if (hwdev.get_mode() == hw::device::mode::TRANSACTION_CREATE_FAKE) { // use a fake bulletproof for speed - rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); + if (plus) + rv.p.bulletproofs_plus.push_back(make_dummy_bulletproof_plus(batch_amounts, C, masks)); + else + rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts, C, masks)); } else { const epee::span keys{&amount_keys[amounts_proved], batch_size}; - rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); + if (plus) + rv.p.bulletproofs_plus.push_back(proveRangeBulletproofPlus(C, masks, batch_amounts, keys, hwdev)); + else + rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); + if (plus) + CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); + else + CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); #endif } for (i = 0; i < batch_size; ++i) { + if (plus) + rv.outPk[i + amounts_proved].mask = C[i]; + else rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); - outSk[i + amounts_proved].mask = masks[i]; + outSk[i + amounts_proved].mask = masks[i]; } amounts_proved += batch_size; } @@ -1249,7 +1356,7 @@ namespace rct { //mask amount and mask rv.ecdhInfo[i].mask = copy(outSk[i].mask); rv.ecdhInfo[i].amount = d2h(outamounts[i]); - hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG); + hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus); } //set txn fee @@ -1257,9 +1364,9 @@ namespace rct { // TODO: unused ?? // key txnFeeKey = scalarmultH(d2h(rv.txnFee)); rv.mixRing = mixRing; - keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + keyV &pseudoOuts = bulletproof_or_plus ? rv.p.pseudoOuts : rv.pseudoOuts; pseudoOuts.resize(inamounts.size()); - if (rv.type == RCTType::CLSAG) + if (is_rct_clsag(rv.type)) rv.p.CLSAGs.resize(inamounts.size()); else rv.p.MGs.resize(inamounts.size()); @@ -1278,11 +1385,11 @@ namespace rct { if (msout) { msout->c.resize(inamounts.size()); - msout->mu_p.resize(rv.type == RCTType::CLSAG ? inamounts.size() : 0); + msout->mu_p.resize(is_rct_clsag(rv.type) ? inamounts.size() : 0); } for (i = 0 ; i < inamounts.size(); i++) { - if (rv.type == RCTType::CLSAG) + 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); } @@ -1387,7 +1494,8 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; std::deque results; - std::vector proofs; + std::vector bp_proofs; + std::vector bpp_proofs; size_t max_non_bp_proofs = 0, offset = 0; for (const rctSig *rvp: rvv) @@ -1396,10 +1504,14 @@ namespace rct { const rctSig &rv = *rvp; CHECK_AND_ASSERT_MES(rct::is_rct_simple(rv.type), false, "verRctSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); - if (bulletproof) + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + if (bulletproof || bulletproof_plus) { - CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); - if (rv.type == RCTType::CLSAG) + if (bulletproof_plus) + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_plus_amounts(rv.p.bulletproofs_plus), false, "Mismatched sizes of outPk and bulletproofs_plus"); + else + CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs"); + if (is_rct_clsag(rv.type)) { CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG"); CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs"); @@ -1419,7 +1531,7 @@ namespace rct { } CHECK_AND_ASSERT_MES(rv.outPk.size() == rv.ecdhInfo.size(), false, "Mismatched sizes of outPk and rv.ecdhInfo"); - if (!bulletproof) + if (!bulletproof && !bulletproof_plus) max_non_bp_proofs += rv.p.rangeSigs.size(); } @@ -1429,11 +1541,15 @@ namespace rct { const rctSig &rv = *rvp; const bool bulletproof = is_rct_bulletproof(rv.type); - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; rct::keyV masks(rv.outPk.size()); for (size_t i = 0; i < rv.outPk.size(); i++) { - masks[i] = rv.outPk[i].mask; + if (bulletproof_plus) + masks[i] = rct::scalarmult8(rv.outPk[i].mask); + else + masks[i] = rv.outPk[i].mask; } key sumOutpks = addKeys(masks); DP(sumOutpks); @@ -1449,10 +1565,15 @@ namespace rct { return false; } - if (bulletproof) + if (bulletproof_plus) + { + for (size_t i = 0; i < rv.p.bulletproofs_plus.size(); i++) + bpp_proofs.push_back(&rv.p.bulletproofs_plus[i]); + } + else if (bulletproof) { for (size_t i = 0; i < rv.p.bulletproofs.size(); i++) - proofs.push_back(&rv.p.bulletproofs[i]); + bp_proofs.push_back(&rv.p.bulletproofs[i]); } else { @@ -1461,9 +1582,18 @@ namespace rct { offset += rv.p.rangeSigs.size(); } } - if (!proofs.empty() && !verBulletproof(proofs)) + if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs)) { LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; + return false; + } + if (!bp_proofs.empty() && !verBulletproof(bp_proofs)) + { + LOG_PRINT_L1("Aggregate range proof verified failed"); + if (!waiter.wait()) + return false; return false; } @@ -1504,8 +1634,9 @@ namespace rct { CHECK_AND_ASSERT_MES(rct::is_rct_simple(rv.type), false, "verRctNonSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); + const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); // semantics check is early, and mixRing/MGs aren't resolved yet - if (bulletproof) + if (bulletproof || bulletproof_plus) CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.p.pseudoOuts and mixRing"); else CHECK_AND_ASSERT_MES(rv.pseudoOuts.size() == rv.mixRing.size(), false, "Mismatched sizes of rv.pseudoOuts and mixRing"); @@ -1516,7 +1647,7 @@ namespace rct { tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; - const keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts; + const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; const key message = get_pre_clsag_hash(rv, hw::get_device("default")); @@ -1524,7 +1655,7 @@ namespace rct { results.resize(rv.mixRing.size()); for (size_t i = 0 ; i < rv.mixRing.size() ; i++) { tpool.submit(&waiter, [&, i] { - if (rv.type == RCTType::CLSAG) + if (is_rct_clsag(rv.type)) results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]); else results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]); @@ -1571,10 +1702,12 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; + if (is_rct_bulletproof_plus(rv.type)) + C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1601,10 +1734,12 @@ namespace rct { //mask amount and mask ecdhTuple ecdh_info = rv.ecdhInfo[i]; - hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG); + hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus); mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; + if (is_rct_bulletproof_plus(rv.type)) + C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1627,6 +1762,7 @@ namespace rct { bool signMultisigMLSAG(rctSig &rv, const std::vector &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { CHECK_AND_ASSERT_MES(tools::equals_any(rv.type, RCTType::Full, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2), false, "unsupported rct type"); + CHECK_AND_ASSERT_MES(!is_rct_clsag(rv.type), false, "CLSAG signature type in MLSAG signature function"); CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size"); CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); @@ -1651,7 +1787,7 @@ namespace rct { } bool signMultisigCLSAG(rctSig &rv, const std::vector &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - CHECK_AND_ASSERT_MES(rv.type == RCTType::CLSAG, false, "unsupported rct type"); + CHECK_AND_ASSERT_MES(is_rct_clsag(rv.type), false, "unsupported rct type"); CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes"); CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/CLSAGs size"); CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size"); @@ -1673,7 +1809,7 @@ namespace rct { } bool signMultisig(rctSig &rv, const std::vector &indices, const keyV &k, const multisig_out &msout, const key &secret_key) { - if (rv.type == RCTType::CLSAG) + if (is_rct_clsag(rv.type)) return signMultisigCLSAG(rv, indices, k, msout, secret_key); else return signMultisigMLSAG(rv, indices, k, msout, secret_key); diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index 71fa69f7313..b4a7a98efa6 100755 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -188,19 +188,22 @@ namespace rct { return vali; } - size_t n_bulletproof_amounts(const Bulletproof &proof) + static size_t n_bulletproof_amounts_base(const size_t L_size, const size_t R_size, const size_t V_size, const size_t max_outputs) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == cryptonote::TX_BULLETPROOF_MAX_OUTPUTS, "log2(TX_BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.V.size() <= (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() * 2 > (1u<<(proof.L.size()-6)), 0, "Invalid bulletproof V/L"); - CHECK_AND_ASSERT_MES(proof.V.size() > 0, 0, "Empty bulletproof"); - return proof.V.size(); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(V_size <= (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size * 2 > (1u<<(L_size-6)), 0, "Invalid bulletproof V/L"); + CHECK_AND_ASSERT_MES(V_size > 0, 0, "Empty bulletproof"); + return V_size; } + size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), TX_BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_amounts(const std::vector &proofs) { size_t n = 0; @@ -215,16 +218,33 @@ namespace rct { return n; } - size_t n_bulletproof_max_amounts(const Bulletproof &proof) + size_t n_bulletproof_plus_amounts(const std::vector &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + + static size_t n_bulletproof_max_amounts_base(size_t L_size, size_t R_size, size_t max_outputs) { - CHECK_AND_ASSERT_MES(proof.L.size() >= 6, 0, "Invalid bulletproof L size"); - CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), 0, "Mismatched bulletproof L/R size"); + CHECK_AND_ASSERT_MES(L_size >= 6, 0, "Invalid bulletproof L size"); + CHECK_AND_ASSERT_MES(L_size == R_size, 0, "Mismatched bulletproof L/R size"); static const size_t extra_bits = 4; - static_assert((1 << extra_bits) == cryptonote::TX_BULLETPROOF_MAX_OUTPUTS, "log2(TX_BULLETPROOF_MAX_OUTPUTS) is out of date"); - CHECK_AND_ASSERT_MES(proof.L.size() <= 6 + extra_bits, 0, "Invalid bulletproof L size"); - return 1 << (proof.L.size() - 6); + CHECK_AND_ASSERT_MES((1 << extra_bits) == max_outputs, 0, "log2(max_outputs) is out of date"); + CHECK_AND_ASSERT_MES(L_size <= 6 + extra_bits, 0, "Invalid bulletproof L size"); + return 1 << (L_size - 6); } + size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), TX_BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_max_amounts(const std::vector &proofs) { size_t n = 0; @@ -239,4 +259,18 @@ namespace rct { return n; } + size_t n_bulletproof_plus_max_amounts(const std::vector &proofs) + { + size_t n = 0; + for (const BulletproofPlus &proof: proofs) + { + size_t n2 = n_bulletproof_plus_max_amounts(proof); + CHECK_AND_ASSERT_MES(n2 < std::numeric_limits::max() - n, 0, "Invalid number of bulletproofs"); + if (n2 == 0) + return 0; + n += n2; + } + return n; + } + } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 59e515b9748..2d7d85e59ea 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -273,6 +273,11 @@ namespace rct { size_t n_bulletproof_amounts(const std::vector &proofs); size_t n_bulletproof_max_amounts(const std::vector &proofs); + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof); + size_t n_bulletproof_plus_amounts(const std::vector &proofs); + size_t n_bulletproof_plus_max_amounts(const std::vector &proofs); + template auto start_array(Archive& ar, std::string_view tag, std::vector& v, size_t size) { ar.tag(tag); @@ -327,7 +332,7 @@ namespace rct { field_varint(ar, "type", type); if (type == RCTType::Null) return; - if (!tools::equals_any(type, RCTType::Full, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG)) + if (!tools::equals_any(type, RCTType::Full, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG, RCTType::BulletproofPlus)) throw std::invalid_argument{"invalid ringct type"}; field_varint(ar, "txnFee", txnFee); @@ -344,7 +349,7 @@ namespace rct { { auto arr = start_array(ar, "ecdhInfo", ecdhInfo, outputs); - if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG)) + if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG, RCTType::BulletproofPlus)) { for (auto& e : ecdhInfo) { auto obj = ar.begin_object(); @@ -368,6 +373,7 @@ namespace rct { struct rctSigPrunable { std::vector rangeSigs; std::vector bulletproofs; + std::vector bulletproofs_plus; std::vector MGs; // simple rct has N, full has 1 std::vector CLSAGs; keyV pseudoOuts; //C - for simple rct @@ -378,9 +384,28 @@ namespace rct { { if (type == RCTType::Null) return; - if (!tools::equals_any(type, RCTType::Full, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG)) + if (!tools::equals_any(type, RCTType::Full, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG, RCTType::BulletproofPlus)) throw std::invalid_argument{"invalid ringct type"}; - if (rct::is_rct_bulletproof(type)) + if (type == RCTType::BulletproofPlus) + { + uint32_t nbp = bulletproofs_plus.size(); + VARINT_FIELD(nbp) + ar.tag("bpp"); + ar.begin_array(); + if (nbp > outputs) + return false; + PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus); + for (size_t i = 0; i < nbp; ++i) + { + FIELDS(bulletproofs_plus[i]) + if (nbp - i > 1) + ar.delimit_array(); + } + if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs) + return false; + ar.end_array(); + } + else if (type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG) { uint32_t nbp = bulletproofs.size(); if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG)) @@ -404,7 +429,7 @@ namespace rct { value(ar, s); } - if (type == RCTType::CLSAG) + if (type == RCTType::CLSAG || type == RCTType::BulletproofPlus) { auto arr = start_array(ar, "CLSAGs", CLSAGs, inputs); @@ -462,7 +487,7 @@ namespace rct { } } } - if (tools::equals_any(type, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG)) + if (tools::equals_any(type, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG, RCTType::BulletproofPlus)) { auto arr = start_array(ar, "pseudoOuts", pseudoOuts, inputs); for (auto& o : pseudoOuts) @@ -470,18 +495,26 @@ namespace rct { } } + BEGIN_SERIALIZE_OBJECT() + FIELD(rangeSigs) + FIELD(bulletproofs) + FIELD(bulletproofs_plus) + FIELD(MGs) + FIELD(CLSAGs) + FIELD(pseudoOuts) + END_SERIALIZE() }; struct rctSig: public rctSigBase { rctSigPrunable p; keyV& get_pseudo_outs() { - return rct::is_rct_bulletproof(type) ? p.pseudoOuts : pseudoOuts; + return type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG || type == RCTType::BulletproofPlus ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const { - return rct::is_rct_bulletproof(type) ? p.pseudoOuts : pseudoOuts; + return RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG || type == RCTType::BulletproofPlus ? p.pseudoOuts : pseudoOuts; } }; @@ -641,3 +674,4 @@ VARIANT_TAG(rct::Bulletproof, "rct_bulletproof", 0x9c); VARIANT_TAG(rct::multisig_kLRki, "rct_multisig_kLR", 0x9d); VARIANT_TAG(rct::multisig_out, "rct_multisig_out", 0x9e); VARIANT_TAG(rct::clsag, "rct_clsag", 0x9f); +VARIANT_TAG(rct::BulletproofPlus, "rct_bulletproof_plus", 0xa0); diff --git a/tests/core_tests/beldex_tests.cpp b/tests/core_tests/beldex_tests.cpp index 5e1f25ffcf9..a1bb4d9d159 100755 --- a/tests/core_tests/beldex_tests.cpp +++ b/tests/core_tests/beldex_tests.cpp @@ -477,7 +477,7 @@ bool beldex_core_block_reward_unpenalized_pre_POS::generate(std::vector& events) { - std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf20_bulletproof_plusplus, 150 /*Proof Of Stake Delay*/); + std::vector hard_forks = beldex_generate_hard_fork_table(cryptonote::hf::hf20_bulletproof_plus, 150 /*Proof Of Stake Delay*/); beldex_chain_generator gen(events, hard_forks); const cryptonote::hf newest_hf = hard_forks.back().version; @@ -2070,7 +2070,7 @@ bool beldex_name_system_update_mapping_after_expiry_fails::generate(std::vector< return true; } -cryptonote::hf beldex_name_system_update_mapping::hf() { return cryptonote::hf::hf20_bulletproof_plusplus; } +cryptonote::hf beldex_name_system_update_mapping::hf() { return cryptonote::hf::hf20_bulletproof_plus; } cryptonote::hf beldex_name_system_update_mapping_argon2::hf() { return cryptonote::hf::hf16; } bool beldex_name_system_update_mapping::generate(std::vector &events) { diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp index 90f3ea37393..abaee7c7842 100755 --- a/tests/core_tests/bulletproofs.cpp +++ b/tests/core_tests/bulletproofs.cpp @@ -53,7 +53,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector& eve MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); if (target_hf == cryptonote::hf::none) - target_hf = cryptonote::hf::hf20_bulletproof_plusplus; + target_hf = cryptonote::hf::hf20_bulletproof_plus; // NOTE: Monero tests use multiple null terminated entries in their arrays { int amounts_paid_len = 0; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 3c8c44c019b..9f7e7a2fae9 100755 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -1367,7 +1367,7 @@ class beldex_tx_builder { void fill_nonce_with_beldex_generator (struct beldex_chain_generator const *generator, cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); void beldex_register_callback (std::vector &events, std::string const &callback_name, beldex_callback callback); -std::vector beldex_generate_hard_fork_table(cryptonote::hf max_hf_version = cryptonote::hf::hf20_bulletproof_plusplus, uint64_t pos_delay = 60); +std::vector beldex_generate_hard_fork_table(cryptonote::hf max_hf_version = cryptonote::hf::hf20_bulletproof_plus, uint64_t pos_delay = 60); struct beldex_blockchain_entry { diff --git a/tests/performance_tests/check_tx_signature.h b/tests/performance_tests/check_tx_signature.h index b3a20f53b41..ca3991a624e 100755 --- a/tests/performance_tests/check_tx_signature.h +++ b/tests/performance_tests/check_tx_signature.h @@ -72,7 +72,7 @@ class test_check_tx_signature : private multi_tx_test_base std::unordered_map subaddresses; subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plusplus; + tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plus; rct::RCTConfig rct_config{range_proof_type, bp_version}; if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::tx_destination_entry{}, std::vector(), m_tx, 0, tx_key, additional_tx_keys, rct_config, nullptr, tx_params)) return false; @@ -136,7 +136,7 @@ class test_check_tx_signature_aggregated_bulletproofs : private multi_tx_test_ba subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plusplus; + tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plus; m_txes.resize(a_num_txes + (extra_outs > 0 ? 1 : 0)); for (size_t n = 0; n < a_num_txes; ++n) { diff --git a/tests/performance_tests/construct_tx.h b/tests/performance_tests/construct_tx.h index 6df8c4015a3..1b712c10a4d 100755 --- a/tests/performance_tests/construct_tx.h +++ b/tests/performance_tests/construct_tx.h @@ -75,7 +75,7 @@ class test_construct_tx : private multi_tx_test_base subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct::RCTConfig rct_config{range_proof_type, bp_version}; cryptonote::beldex_construct_tx_params tx_params; - tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plusplus; + tx_params.hf_version = cryptonote::hf::hf20_bulletproof_plus; return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::tx_destination_entry{}, std::vector(), m_tx, 0, tx_key, additional_tx_keys, rct_config, nullptr, tx_params); } diff --git a/tests/unit_tests/master_nodes.cpp b/tests/unit_tests/master_nodes.cpp index a256cb4c78a..5cac7129e41 100755 --- a/tests/unit_tests/master_nodes.cpp +++ b/tests/unit_tests/master_nodes.cpp @@ -122,7 +122,7 @@ static bool verify_vote(master_nodes::quorum_vote_t const &vote, master_nodes::quorum const &quorum) { bool result = master_nodes::verify_vote_age(vote, latest_height,vvc, cryptonote::hf::hf17_POS); - result &= master_nodes::verify_vote_signature(cryptonote::hf::hf20_bulletproof_plusplus, vote, vvc, quorum); + result &= master_nodes::verify_vote_signature(cryptonote::hf::hf20_bulletproof_plus, vote, vvc, quorum); return result; } From 8b9e53a1752fdff3e4780db08c2dd1dd3330fc0e Mon Sep 17 00:00:00 2001 From: abdevil Date: Mon, 21 Jul 2025 04:29:12 -0500 Subject: [PATCH 145/182] bulletproof+ integration for wallet --- .../cryptonote_format_utils.cpp | 2 +- src/cryptonote_config.h | 2 +- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/ringct/bulletproofs.cc | 2 +- src/ringct/bulletproofs_plus.cc | 11 +-- src/ringct/rctSigs.cpp | 2 +- src/ringct/rctTypes.cpp | 8 +-- src/ringct/rctTypes.h | 14 ++-- src/wallet.zip | Bin 0 -> 164 bytes src/wallet/wallet2.cpp | 66 ++++++++++-------- 10 files changed, 56 insertions(+), 53 deletions(-) create mode 100644 src/wallet.zip diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index e2b7b6985e4..fdfe432fc83 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -493,7 +493,7 @@ namespace cryptonote while ((n_padded_outputs = (1u << nrl)) < tx.vout.size()) ++nrl; nrl += 6; - extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2; + uint64_t extra = 32 * ((rct::is_rct_bulletproof_plus(tx.rct_signatures.type) ? 6 : 9) + 2 * nrl) + 2; weight += extra; // calculate deterministic CLSAG/MLSAG data size diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index aa1c5892fce..e6d06fa0a35 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -337,7 +337,7 @@ namespace config inline constexpr auto UPTIME_PROOF_FREQUENCY = 1h; // How often to send proofs out to the network since the last proof we successfully sent. (Approximately; this can be up to CHECK_INTERFACE/2 off in either direction). The minimum accepted time between proofs is half of this. inline constexpr auto UPTIME_PROOF_VALIDITY = 2h + 5min; // The maximum time that we consider an uptime proof to be valid (i.e. after this time since the last proof we consider the MN to be down) inline constexpr auto REACHABLE_MAX_FAILURE_VALIDITY = 5min; // If we don't hear any SS ping/belnet bchat test failures for more than this long then we start considering the MN as passing for the purpose of obligation testing until we get another test result. This should be somewhat larger than SS/belnet's max re-test backoff (2min). - + inline constexpr std::string_view HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT = "bulletproof_plus"sv; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 169960; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index f528186732a..6116af3ad1e 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1283,7 +1283,7 @@ namespace cryptonote } rvv.push_back(&rv); // delayed batch verification break; - case rct::RCTTypeBulletproofPlus: + case rct::RCTType::BulletproofPlus: if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus)) { MERROR_VER("Bulletproof_plus does not have canonical form"); diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index 3171deb3fd8..adac79e2be0 100755 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -98,7 +98,7 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::BULLETPROOF_EXPONENT + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + std::string(cryptonote::hashkey::BULLETPROOF_EXPONENT) + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc index 3f33c1c70c5..78a172d7f0c 100644 --- a/src/ringct/bulletproofs_plus.cc +++ b/src/ringct/bulletproofs_plus.cc @@ -40,8 +40,8 @@ #include #include #include -#include "misc_log_ex.h" -#include "span.h" +#include "epee/misc_log_ex.h" +#include "epee/span.h" #include "cryptonote_config.h" extern "C" { @@ -50,6 +50,7 @@ extern "C" #include "rctOps.h" #include "multiexp.h" #include "bulletproofs_plus.h" +#include "common/varint.h" #undef BELDEX_DEFAULT_LOG_CATEGORY #define BELDEX_DEFAULT_LOG_CATEGORY "bulletproof_plus" @@ -65,7 +66,7 @@ namespace rct // Proof bounds static constexpr size_t maxN = 64; // maximum number of bits in range - static constexpr size_t maxM = TX_BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof + static constexpr size_t maxM = cryptonote::TX_BULLETPROOF_PLUS_MAX_OUTPUTS; // maximum number of outputs to aggregate into a single proof // Cached public generators static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; @@ -108,7 +109,7 @@ namespace rct // Use hashed values to produce indexed public generators static ge_p3 get_exponent(const rct::key &base, size_t idx) { - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + config::BULLETPROOF_PLUS_EXPONENT + tools::get_varint_data(idx); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + std::string(cryptonote::hashkey::BULLETPROOF_PLUS_EXPONENT) + tools::get_varint_data(idx); rct::key generator; ge_p3 generator_p3; rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -150,7 +151,7 @@ namespace rct sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs - const std::string domain_separator(config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); + const std::string domain_separator(cryptonote::config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); ge_p3 initial_transcript_p3; rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size()))); ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index abb17cf2803..3c8e4724a07 100755 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -665,7 +665,7 @@ namespace rct { kv.push_back(p.t); } } - else if (rv.type == RCTTypeBulletproofPlus) + else if (rv.type == RCTType::BulletproofPlus) { kv.reserve((6 * 2 + 6) * rv.p.bulletproofs_plus.size()); for (const auto &p : rv.p.bulletproofs_plus) diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp index b4a7a98efa6..9b21f175d5f 100755 --- a/src/ringct/rctTypes.cpp +++ b/src/ringct/rctTypes.cpp @@ -201,8 +201,8 @@ namespace rct { return V_size; } - size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), BULLETPROOF_MAX_OUTPUTS); } - size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), TX_BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_amounts(const Bulletproof &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), cryptonote::TX_BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_amounts(const BulletproofPlus &proof) { return n_bulletproof_amounts_base(proof.L.size(), proof.R.size(), proof.V.size(), cryptonote::TX_BULLETPROOF_PLUS_MAX_OUTPUTS); } size_t n_bulletproof_amounts(const std::vector &proofs) { @@ -242,8 +242,8 @@ namespace rct { return 1 << (L_size - 6); } - size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), BULLETPROOF_MAX_OUTPUTS); } - size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), TX_BULLETPROOF_PLUS_MAX_OUTPUTS); } + size_t n_bulletproof_max_amounts(const Bulletproof &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), cryptonote::TX_BULLETPROOF_MAX_OUTPUTS); } + size_t n_bulletproof_plus_max_amounts(const BulletproofPlus &proof) { return n_bulletproof_max_amounts_base(proof.L.size(), proof.R.size(), cryptonote::TX_BULLETPROOF_PLUS_MAX_OUTPUTS); } size_t n_bulletproof_max_amounts(const std::vector &proofs) { diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 2d7d85e59ea..f89dbcc30b2 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -264,7 +264,7 @@ namespace rct { FIELD(R) if (L.empty() || L.size() != R.size()) - return false; + throw std::runtime_error("Bad bulletproofplus serialization"); END_SERIALIZE() }; @@ -390,19 +390,17 @@ namespace rct { { uint32_t nbp = bulletproofs_plus.size(); VARINT_FIELD(nbp) - ar.tag("bpp"); - ar.begin_array(); if (nbp > outputs) - return false; - PREPARE_CUSTOM_VECTOR_SERIALIZATION(nbp, bulletproofs_plus); + throw std::invalid_argument{"too many bulletproofs_plus"}; + auto arr = start_array(ar, "bpp", bulletproofs_plus, nbp); for (size_t i = 0; i < nbp; ++i) { FIELDS(bulletproofs_plus[i]) if (nbp - i > 1) ar.delimit_array(); } - if (n_bulletproof_plus_max_amounts(bulletproofs_plus) < outputs) - return false; + if (auto n_max = n_bulletproof_plus_max_amounts(bulletproofs_plus); n_max < outputs) + throw std::invalid_argument{"invalid bulletproofs_plus: n_max (" + std::to_string(n_max) + ") < outputs (" + std::to_string(outputs) + ")"}; ar.end_array(); } else if (type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG) @@ -514,7 +512,7 @@ namespace rct { keyV const& get_pseudo_outs() const { - return RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG || type == RCTType::BulletproofPlus ? p.pseudoOuts : pseudoOuts; + return (type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG || type == RCTType::BulletproofPlus) ? p.pseudoOuts : pseudoOuts; } }; diff --git a/src/wallet.zip b/src/wallet.zip new file mode 100644 index 0000000000000000000000000000000000000000..145a6ae3c17a4256af47646a96def2a11de6b7e7 GIT binary patch literal 164 zcmWIWW@h1H0D&03uTfwIlwfC&VJJ_`$w@8I4-MgDU=Da(n-K`ar4`%^j4U8UU?RYq nkx7mjmk| &short_chain_history, size_ } } -size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool clsag) +size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool clsag, bool bulletproof_plus) { size_t size = 0; @@ -782,7 +782,7 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra size_t log_padded_outputs = 0; while ((1< 2) + size_t size = estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, clsag, bulletproof_plus); + if (n_outputs > 2 && bulletproof_plus) //need to check this condition "check" later { - const uint64_t bp_base = 368; + 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) size_t log_padded_outputs = 2; while ((1< wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector 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.first * fee_percent / 100 * tx_weight_per_ring; @@ -11153,7 +11155,7 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector 0 && !dsts.empty() && estimate_tx_weight(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), clsag) < tx_weight_target(upper_transaction_weight_limit)) { + if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), clsag, bulletproof_plus) < 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)); @@ -11308,7 +11310,7 @@ std::vector wallet2::create_transactions_2(std::vector= 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); } @@ -11319,7 +11321,7 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_burn(const std::ve std::vector> outs; const bool clsag = use_fork_rules(feature::CLSAG, 0); - const rct::RCTConfig rct_config { rct::RangeProofType::PaddedBulletproof, clsag ? 3 : 2 }; + const bool bulletproof_plus = use_fork_rules(HF_VERSION_BULLETPROOF_PLUS(), 0); + const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); @@ -11709,7 +11712,7 @@ std::vector wallet2::create_transactions_burn(const std::ve // get a tx that can't pay for itself uint64_t fee_dust_threshold; { - const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra_base.size(), clsag); + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra_base.size(), clsag, bulletproof_plus); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, outputs, fee_percent, fixed_fee, fee_quantization_mask); } @@ -11736,7 +11739,7 @@ std::vector wallet2::create_transactions_burn(const std::ve // 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(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra_base.size(), clsag); + const size_t estimated_rct_tx_weight = estimate_tx_weight(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra_base.size(), clsag, bulletproof_plus); bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= tx_weight_target(upper_transaction_weight_limit)); LOG_PRINT_L2("Accumulated_outputs : " << accumulated_outputs); if (try_tx) { @@ -11857,7 +11860,8 @@ std::vector wallet2::create_transactions_from(const crypton std::vector> outs; const bool clsag = use_fork_rules(feature::CLSAG, 0); - const rct::RCTConfig rct_config { rct::RangeProofType::PaddedBulletproof, clsag ? 3 : 2 }; + const bool bulletproof_plus = use_fork_rules(HF_VERSION_BULLETPROOF_PLUS(), 0); + const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); @@ -11907,7 +11911,7 @@ std::vector wallet2::create_transactions_from(const crypton // get a tx that can't pay for itself uint64_t fee_dust_threshold; { - const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), clsag); + const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), clsag, bulletproof_plus); fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, outputs, fee_percent, fixed_fee, fee_quantization_mask); } @@ -11934,7 +11938,7 @@ std::vector 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(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), clsag); + const size_t estimated_rct_tx_weight = estimate_tx_weight(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), clsag, bulletproof_plus); 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) { @@ -11942,7 +11946,7 @@ std::vector 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, beldex_tx_params); - needed_fee = estimate_fee(tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), clsag, base_fee, fee_percent, fixed_fee, fee_quantization_mask); + needed_fee = estimate_fee(tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), clsag, bulletproof_plus, base_fee, fee_percent, fixed_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) @@ -12091,7 +12095,7 @@ void wallet2::cold_sign_tx(const std::vector& 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(feature::CLSAG, 0) ? 3 : 2; + aux_data.bp_version = use_fork_rules(HF_VERSION_BULLETPROOF_PLUS , 0) ? 4 : 3; auto hf_version = get_hard_fork_version(); CHECK_AND_ASSERT_THROW_MES(hf_version, "Failed to query hard fork"); aux_data.hard_fork = static_cast(*hf_version); @@ -12691,8 +12695,8 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt crypto::secret_key scalar1; crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tools::equals_any(tx.rct_signatures.type, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG)); - const rct::key C = tx.rct_signatures.outPk[n].mask; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tools::equals_any(tx.rct_signatures.type, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)); + rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.amount.bytes) != 0, error::wallet_internal_error, "Bad ECDH input amount"); @@ -13261,7 +13265,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr crypto::secret_key shared_secret; crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tools::equals_any(tx.rct_signatures.type, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG)); + rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tools::equals_any(tx.rct_signatures.type, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)); amount = rct::h2d(ecdh_info.amount); } total += amount; From 554f3b57bead0c535ac48b1c5da3ed060523aa0e Mon Sep 17 00:00:00 2001 From: abdevil Date: Mon, 21 Jul 2025 06:22:44 -0500 Subject: [PATCH 146/182] update: macros for Bulletproof+ --- src/blockchain_db/blockchain_db.cpp | 9 +-------- src/cryptonote_core/blockchain.cpp | 4 ++-- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/ringct/rctSigs.cpp | 6 +----- src/ringct/rctTypes.h | 4 +--- src/wallet.zip | Bin 164 -> 347513 bytes src/wallet/wallet2.cpp | 12 ++++++------ 7 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index c252142f882..314c8242ed8 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -161,15 +161,8 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { - rct::key commitment; - if (tx.version > 1) - { - commitment = tx.rct_signatures.outPk[i].mask; - if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) - commitment = rct::scalarmult8(commitment); - } amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time, - tx.version >= cryptonote::txversion::v2_ringct ? &commitment : NULL); + tx.version >= cryptonote::txversion::v2_ringct ? &tx.rct_signatures.outPk[i].mask : NULL); //to check with sarav } } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 78b18e44e55..1a847335dc4 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3163,7 +3163,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty()) { - MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(feature::BULLETPROOF_PLUS)); + MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(static_cast(feature::BULLETPROOF_PLUS))); tvc.m_invalid_output = true; return false; } @@ -3176,7 +3176,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); if (bulletproof) { - MERROR_VER("Bulletproof range proofs are not allowed after v" + std::to_string(feature::BULLETPROOF_PLUS)); + MERROR_VER("Bulletproof range proofs are not allowed after v" << std::to_string(static_cast(feature::BULLETPROOF_PLUS))); tvc.m_invalid_output = true; return false; } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 6116af3ad1e..b949af57d0d 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1310,7 +1310,7 @@ namespace cryptonote { if (!tx_info[n].result || tx_info[n].already_have) continue; - if (tx_info[n].tx->rct_signatures.type != rct::RCTType::Bulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTType::Bulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTType::CLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTType::BulletproofPlus) + if (tx_info[n].tx.rct_signatures.type != rct::RCTType::Bulletproof && tx_info[n].tx.rct_signatures.type != rct::RCTType::Bulletproof2 && tx_info[n].tx.rct_signatures.type != rct::RCTType::CLSAG && tx_info[n].tx.rct_signatures.type != rct::RCTType::BulletproofPlus) continue; if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx.rct_signatures)) { diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 3c8e4724a07..bec1e917183 100755 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1308,7 +1308,7 @@ namespace rct { { size_t batch_size = 1; if (rct_config.range_proof_type == RangeProofType::MultiOutputBulletproof) - while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? cryptonote:TX_BULLETPROOF_PLUS_MAX_OUTPUTS : cryptonote::TX_BULLETPROOF_MAX_OUTPUTS)) + while (batch_size * 2 + amounts_proved <= n_amounts && batch_size * 2 <= (plus ? cryptonote::TX_BULLETPROOF_PLUS_MAX_OUTPUTS : cryptonote::TX_BULLETPROOF_MAX_OUTPUTS)) batch_size *= 2; rct::keyV C, masks; std::vector batch_amounts(batch_size); @@ -1585,15 +1585,11 @@ namespace rct { if (!bpp_proofs.empty() && !verBulletproofPlus(bpp_proofs)) { LOG_PRINT_L1("Aggregate range proof verified failed"); - if (!waiter.wait()) - return false; return false; } if (!bp_proofs.empty() && !verBulletproof(bp_proofs)) { LOG_PRINT_L1("Aggregate range proof verified failed"); - if (!waiter.wait()) - return false; return false; } diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index f89dbcc30b2..94cdad8cf7e 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -396,12 +396,10 @@ namespace rct { for (size_t i = 0; i < nbp; ++i) { FIELDS(bulletproofs_plus[i]) - if (nbp - i > 1) - ar.delimit_array(); } if (auto n_max = n_bulletproof_plus_max_amounts(bulletproofs_plus); n_max < outputs) throw std::invalid_argument{"invalid bulletproofs_plus: n_max (" + std::to_string(n_max) + ") < outputs (" + std::to_string(outputs) + ")"}; - ar.end_array(); + } else if (type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG) { diff --git a/src/wallet.zip b/src/wallet.zip index 145a6ae3c17a4256af47646a96def2a11de6b7e7..2395d8ea57ba4195d548d16eac06165dc465c0a5 100644 GIT binary patch literal 347513 zcmagFV~j3L)UNroZQHhO+qR9}wr$(CZQHi?Zrkpj_k1(S$;l)$mDEaAQn|B!)t#!f zE=3s-P-wva8oKb$+W)ioe*y#m7vOGWYis7hpr#5108#wwYpL|#boGD%0D=CmL;n*L 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%0DVbKk`4@Z}58rRA# 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%0D)g(Yfp9hv-F~~YbJmLwHz&vJrSkl6I@7V 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�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 z z>+27Qqw7ikh9-@N=T?IeK``RUGcQ)+kwT4LcbRPi0nEQcPZ)j_PaArt zZySCDjO&Lm^Ul{J%#k}rh@1}&l=vOfvt`2C8b#2Cjg7f9^D}xal@WfdsYO<(TEj>@ zV2wSpLi(Nz4*Lh5YLdz*ℛ1aR>VvD3gugW01gv5+1sGHj1UUo*{8+v@c@J(Ra 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�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 z z>+27Qqw7ikh9-@N=T?IeK``RUGcQ)+kwT4LcbRPi0nEQcPZ)j_PaArt zZySCDjO&Lm^Ul{J%#k}rh@1}&l=vOfvt`2C8b#2Cjg7f9^D}xal@WfdsYO<(TEj>@ zV2wSpLi(Nz4*Lh5YLdz*ℛ1aR>VvD3gugW01gv5+1sGHj1UUo*{8+v@c@J(Ra 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�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ò|JNNeorg9#NOQ;I6bZbY32J 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 z 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 DMS;N- From df642cda46e498457572682c700ea5488334e193 Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Mon, 4 Aug 2025 19:42:35 +0530 Subject: [PATCH 148/182] fix: GET_TRANSACTION_POOL rpc --- src/rpc/core_rpc_server_command_parser.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index e91ff4dc248..8760caff505 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -32,6 +32,7 @@ namespace cryptonote::rpc { void parse_request(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_input in); void parse_request(GET_STAKING_REQUIREMENT& get_staking_requirement, rpc_input in); void parse_request(GET_TRANSACTIONS& get, rpc_input in); + void parse_request(GET_TRANSACTION_POOL& get, rpc_input in); void parse_request(GET_TRANSACTION_POOL_STATS& pstats, rpc_input in); void parse_request(HARD_FORK_INFO& hfinfo, rpc_input in); void parse_request(IN_PEERS& in_peers, rpc_input in); From b94ea8c91702ed7cf426baddefd82d9d9c41cecd Mon Sep 17 00:00:00 2001 From: peter-pratt Date: Thu, 7 Aug 2025 16:02:25 +0530 Subject: [PATCH 149/182] fix(set_log): validate log levels in category:level format --- src/daemon/command_parser_executor.cpp | 18 +++++++++++++++++- src/daemon/rpc_command_executor.cpp | 3 +-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 53b737bd364..debafb82595 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -326,7 +326,23 @@ bool command_parser_executor::set_log_level(const std::vector& args } else { - return m_executor.set_log_categories(args.front()); + static std::unordered_map level_map = { + {"WARNING", "*:WARNING"}, {"INFO", "*:INFO"}, {"DEBUG", "*:DEBUG"}, {"TRACE", "*:TRACE"}, + {"*WARNING", "*:WARNING"}, {"*INFO", "*:INFO"}, {"*DEBUG", "*:DEBUG"}, {"*TRACE", "*:TRACE"}, + {":WARNING", "*:WARNING"}, {":INFO", "*:INFO"}, {":DEBUG", "*:DEBUG"}, {":TRACE", "*:TRACE"} + }; + + std::string input = args[0]; + std::transform(input.begin(), input.end(), input.begin(), ::toupper); + + auto it = level_map.find(input); + if (it != level_map.end()) + return m_executor.set_log_categories(it->second); + + if (args[0].find(':') != std::string::npos) return m_executor.set_log_categories(args.front()); + + std::cout << "Invalid log level. Use: 0-4, WARNING/INFO/DEBUG/TRACE, or category:level format" << std::endl; + return true; } } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 485bdbaeec9..306207bec7d 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -771,9 +771,8 @@ bool rpc_command_executor::set_log_level(int8_t level) { auto maybe_level = try_running([this, &level] { return invoke(json{{"level", level}}); }, "Failed to set log categories"); if (!maybe_level) return false; - auto& level_response = *maybe_level; - tools::success_msg_writer() << "Log level is now " << level_response["level"].get(); + tools::success_msg_writer() << "Log level is now " << std::to_string(level); return true; } From 8e92794a5132272ddc4664684f575fdd10f2c040 Mon Sep 17 00:00:00 2001 From: abdevil Date: Mon, 18 Aug 2025 13:10:21 +0530 Subject: [PATCH 150/182] handle Bulletproof+ commitments with scalarmult8 in tx key check --- src/wallet/wallet2.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 331289be30a..94fab4922fd 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -12698,6 +12698,8 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tools::equals_any(tx.rct_signatures.type, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)); rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + C = rct::scalarmult8(C); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.amount.bytes) != 0, error::wallet_internal_error, "Bad ECDH input amount"); rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); From 97c092b63ba0b0279ec60c75987243ba7b0bb430 Mon Sep 17 00:00:00 2001 From: peter-pratt Date: Thu, 21 Aug 2025 13:41:07 +0530 Subject: [PATCH 151/182] fix(print_cn: print_cn issue fixed) --- src/daemon/rpc_command_executor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 306207bec7d..569314c2465 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -652,7 +652,7 @@ bool rpc_command_executor::print_connections() { "Remote Host", "Type", "Peer id", "Recv/Sent (inactive,sec)", "State", "Livetime(sec)", "Down (kB/sec)", "Down(now)", "Up (kB/s)", "Up(now)"); - for (auto& info : conns) + for (auto& info : conns["connections"]) { std::string address = info["incoming"].get() ? "INC " : "OUT "; address += info["ip"].get(); @@ -1398,7 +1398,7 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) int target = safe_int(info, "target"); tools::msg_writer() << "Height: " << height << ", diff " << difficulty << ", cum. diff " << cumulative_difficulty << ", target " << target << " sec" << ", dyn fee " << cryptonote::print_money(safe_uint64(feres, "fee_per_byte")) << "/" << (hfinfo["enabled"].get() ? "byte" : "kB") - << " + " << cryptonote::print_money(safe_uint64(feres, "fee_per_byte")) << "/out"; + << " + " << cryptonote::print_money(safe_uint64(feres, "fee_per_output")) << "/out"; if (nblocks > 0) { From d7817fb532fb8f22630921db4c6e8be9d7b8a4c5 Mon Sep 17 00:00:00 2001 From: peter-pratt Date: Fri, 22 Aug 2025 13:12:07 +0530 Subject: [PATCH 152/182] fix(test_trigger_uptime_proof): issue fix in test_trigger_uptime_proof --- src/daemon/rpc_command_executor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 569314c2465..d4553782e8c 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2598,7 +2598,8 @@ bool rpc_command_executor::version() bool rpc_command_executor::test_trigger_uptime_proof() { - return invoke(json{{}}, "Failed to trigger uptime proof"); + tools::success_msg_writer() << invoke(json{{}}, "Failed to trigger uptime proof"); + return true; } }// namespace daemonize From 837dc3a873ffa464ddbcc49d90b896b3763fd95c Mon Sep 17 00:00:00 2001 From: deen-kakarot Date: Tue, 26 Aug 2025 09:04:41 +0530 Subject: [PATCH 153/182] Fix broken RPC "get_fee_estimate" endpoint name The endpoint has always been named get_fee_estimate but for some reason the struct has always been named GET_BASE_FEE_ESTIMATE. In the RPC refactor, wallet2 started using "get_base_fee_estimate" erroneously, which doesn't work. Fix it and drop `_BASE` from the name everywhere. --- src/cryptonote_core/blockchain.cpp | 2 +- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/daemon/rpc_command_executor.cpp | 2 +- src/rpc/core_rpc_server.cpp | 24 +++++++++++----------- src/rpc/core_rpc_server.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 4 ++-- src/rpc/core_rpc_server_command_parser.h | 2 +- src/rpc/core_rpc_server_commands_defs.cpp | 4 ++-- src/rpc/core_rpc_server_commands_defs.h | 4 ++-- src/wallet/node_rpc_proxy.cpp | 2 +- src/wallet/wallet2.cpp | 5 ++--- 11 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 6437beab056..da93705a0d3 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -5062,7 +5062,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar if(height == 742425) { - reward = 850000000 * beldex::COIN; //mint 8.5 billion bdx governance in this block + reward = 8500000000 * beldex::COIN; //mint 8.5 billion bdx governance in this block return true; } // Ignore governance reward and payout instead the last diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 55716b4f665..2373891af45 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1318,7 +1318,7 @@ namespace cryptonote } catch (const std::exception &e) { - MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what()); + MERROR_VER("Exception in parse_incoming_tx_pre: " << e.what()); info.tvc.m_verifivation_failed = true; } }); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index d4553782e8c..bf431e7f8f3 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1379,7 +1379,7 @@ bool rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) return false; auto& hfinfo = *maybe_hf; - auto maybe_fees = try_running([this] { return invoke(json{}); }, "Failed to retrieve current fee info"); + auto maybe_fees = try_running([this] { return invoke(json{}); }, "Failed to retrieve current fee info"); if (!maybe_fees) return false; auto& feres = *maybe_fees; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index adb2fbf6e48..db2b251991d 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1202,7 +1202,7 @@ namespace cryptonote::rpc { { tx.response["status"] = STATUS_FAILED; auto reason = print_tx_verification_context(tvc); - LOG_PRINT_L0("[on_send_raw_tx]: " << (tvc.m_verifivation_failed ? "tx verification failed" : "Failed to process tx") << reason); + LOG_PRINT_L0("[on_submit_transaction]: " << (tvc.m_verifivation_failed ? "tx verification failed" : "Failed to process tx") << reason); tx.response["reason"] = std::move(reason); tx.response["reason_codes"] = tx_verification_failure_codes(tvc); return; @@ -2103,22 +2103,22 @@ namespace cryptonote::rpc { } } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context) + void core_rpc_server::invoke(GET_FEE_ESTIMATE& get_fee_estimate, rpc_context context) { - PERF_TIMER(on_get_base_fee_estimate); + PERF_TIMER(on_get_fee_estimate); //TODO handle bootstrap daemon in new RPC format - //if (use_bootstrap_daemon_if_necessary(req, res)) + //if (use_bootstrap_daemon_if_necessary(req, res)) //return res; - auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(get_base_fee_estimate.request.grace_blocks); - get_base_fee_estimate.response["fee_per_byte"] = fees.first; - get_base_fee_estimate.response["fee_per_output"] = fees.second; - get_base_fee_estimate.response["flash_fee_fixed"] = beldex::FLASH_BURN_FIXED; + auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(get_fee_estimate.request.grace_blocks); + get_fee_estimate.response["fee_per_byte"] = fees.first; + get_fee_estimate.response["fee_per_output"] = fees.second; + get_fee_estimate.response["flash_fee_fixed"] = beldex::FLASH_BURN_FIXED; constexpr auto flash_percent = beldex::FLASH_MINER_TX_FEE_PERCENT + beldex::FLASH_BURN_TX_FEE_PERCENT_OLD; - get_base_fee_estimate.response["flash_fee_per_byte"] = fees.first * flash_percent / 100; - get_base_fee_estimate.response["flash_fee_per_output"] = fees.second * flash_percent / 100; - get_base_fee_estimate.response["quantization_mask"] = Blockchain::get_fee_quantization_mask(); - get_base_fee_estimate.response["status"] = STATUS_OK; + get_fee_estimate.response["flash_fee_per_byte"] = fees.first * flash_percent / 100; + get_fee_estimate.response["flash_fee_per_output"] = fees.second * flash_percent / 100; + get_fee_estimate.response["quantization_mask"] = Blockchain::get_fee_quantization_mask(); + get_fee_estimate.response["status"] = STATUS_OK; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_ALTERNATE_CHAINS& get_alternate_chains, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index d515972d59e..70dcec0ea68 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -160,7 +160,7 @@ namespace cryptonote::rpc { void invoke(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_context context); void invoke(GET_VERSION& version, rpc_context context); void invoke(GET_COINBASE_TX_SUM& get_coinbase_tx_sum, rpc_context context); - void invoke(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_context context); + void invoke(GET_FEE_ESTIMATE& get_fee_estimate, rpc_context context); void invoke(OUT_PEERS& out_peers, rpc_context context); void invoke(IN_PEERS& in_peers, rpc_context context); void invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context); diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 368e29c676a..7f8dd707c89 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -227,9 +227,9 @@ namespace cryptonote::rpc { "height", get_coinbase_tx_sum.request.height); } - void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in) { + void parse_request(GET_FEE_ESTIMATE& get_fee_estimate, rpc_input in) { get_values(in, - "grace_blocks", get_base_fee_estimate.request.grace_blocks); + "grace_blocks", get_fee_estimate.request.grace_blocks); } void parse_request(OUT_PEERS& out_peers, rpc_input in){ diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 8760caff505..a808e8f2758 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -13,7 +13,7 @@ namespace cryptonote::rpc { void parse_request(BANNED& banned, rpc_input in); void parse_request(FLUSH_CACHE& flush_cache, rpc_input in); void parse_request(FLUSH_TRANSACTION_POOL& flush_transaction_pool, rpc_input in); - void parse_request(GET_BASE_FEE_ESTIMATE& get_base_fee_estimate, rpc_input in); + void parse_request(GET_FEE_ESTIMATE& get_fee_estimate, rpc_input in); void parse_request(GET_BLOCK& get_block, rpc_input in); void parse_request(GET_BLOCK_HASH& bh, rpc_input in); void parse_request(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.cpp b/src/rpc/core_rpc_server_commands_defs.cpp index 7266aae756c..bb11ddabe03 100755 --- a/src/rpc/core_rpc_server_commands_defs.cpp +++ b/src/rpc/core_rpc_server_commands_defs.cpp @@ -839,12 +839,12 @@ void to_json(nlohmann::json& j, const BNS_OWNERS_TO_NAMES::response_entry& r) // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::request) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_FEE_ESTIMATE::request) // KV_SERIALIZE(grace_blocks) // KV_SERIALIZE_MAP_CODE_END() -// KV_SERIALIZE_MAP_CODE_BEGIN(GET_BASE_FEE_ESTIMATE::response) +// KV_SERIALIZE_MAP_CODE_BEGIN(GET_FEE_ESTIMATE::response) // KV_SERIALIZE(status) // KV_SERIALIZE(fee_per_byte) // KV_SERIALIZE(fee_per_output) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1a9b1cfd832..a9225c18bf8 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1615,7 +1615,7 @@ namespace cryptonote::rpc { /// ``` /// /// Example-JSON-Fetch - struct GET_BASE_FEE_ESTIMATE : PUBLIC + struct GET_FEE_ESTIMATE : PUBLIC { static constexpr auto names() { return NAMES("get_fee_estimate"); } @@ -2674,7 +2674,7 @@ namespace cryptonote::rpc { FLUSH_TRANSACTION_POOL, GET_ALTERNATE_CHAINS, GET_BANS, - GET_BASE_FEE_ESTIMATE, + GET_FEE_ESTIMATE, GET_BLOCK, GET_BLOCK_COUNT, GET_BLOCK_HASH, diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7b7cbf0eb13..35d74f1d78f 100755 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -208,7 +208,7 @@ bool NodeRPCProxy::refresh_dynamic_base_fee_cache(uint64_t grace_blocks) const {"grace_blocks", grace_blocks} }; try { - auto res = m_http_client.json_rpc("get_base_fee_estimate", req_params); + auto res = m_http_client.json_rpc("get_fee_estimate", req_params); m_dynamic_base_fee_estimate = {res.at("fee_per_byte").get(), res.at("fee_per_output").get()}; m_dynamic_base_fee_estimate_cached_height = height; m_dynamic_base_fee_estimate_grace_blocks = grace_blocks; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3217f9251dd..b1648453dfa 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -80,9 +80,8 @@ #include "ringdb.h" #include "device/device_cold.hpp" #ifdef DEVICE_TREZOR_READY -# include "device_trezor/device_trezor.hpp" -#endif #include "device_trezor/device_trezor.hpp" +#endif #include "cryptonote_core/master_node_list.h" #include "cryptonote_core/master_node_rules.h" @@ -3268,7 +3267,7 @@ std::vector wallet2::get_pool_state(bool refreshed) nlohmann::json get_transactions_params{ {"tx_hashes", hex_hashes} }; - auto res = m_http_client.json_rpc("get_transactions", get_transactions_params); + res = m_http_client.json_rpc("get_transactions", get_transactions_params); } catch (const std::exception& e) { LOG_PRINT_L0("Failed to retrieve transactions: " << e.what()); return process_txs; From 88de283dbe3e52f40e2650422685f5054e1fb3f6 Mon Sep 17 00:00:00 2001 From: peter-pratt Date: Tue, 26 Aug 2025 16:30:23 +0530 Subject: [PATCH 154/182] fix(bc_dyn_stats): issue fix in bc_dyn_stats --- src/rpc/core_rpc_server.cpp | 9 +++++++++ src/rpc/core_rpc_server_commands_defs.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index db2b251991d..3386830ab7f 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1727,6 +1727,15 @@ namespace cryptonote::rpc { uint64_t end_height = get_block_headers_range.request.end_height; if (start_height >= bc_height || end_height >= bc_height || start_height > end_height) throw rpc_error{ERROR_TOO_BIG_HEIGHT, "Invalid start/end heights."}; + + if (end_height - start_height >= GET_BLOCK_HEADERS_RANGE::MAX_COUNT) + throw rpc_error{ + ERROR_TOO_BIG_HEIGHT, + "Invalid start/end heights: requested range of " + + std::to_string(end_height - start_height + 1) + + " blocks exceeds limit " + + std::to_string(GET_BLOCK_HEADERS_RANGE::MAX_COUNT)}; + std::vector headers; for (uint64_t h = start_height; h <= end_height; ++h) { diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index a9225c18bf8..3eacca4c161 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1167,6 +1167,9 @@ namespace cryptonote::rpc { { static constexpr auto names() { return NAMES("get_block_headers_range", "getblockheadersrange"); } + // Used for this endpoint as well as the by_hash/by_height versions. + static constexpr int64_t MAX_COUNT = 1000; + struct request_parameters { uint64_t start_height; From e02f3da7206686391f2529cdfc5a69e9d2eae152 Mon Sep 17 00:00:00 2001 From: techgoku Date: Fri, 29 Aug 2025 16:28:19 +0530 Subject: [PATCH 155/182] fix on bns and stake_amount in extra --- src/rpc/core_rpc_server.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 3386830ab7f..c61542e1852 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -787,19 +787,20 @@ namespace cryptonote::rpc { bns["update"] = true; else if (x.is_renewing()) bns["renew"] = true; - auto bns_bin = json_binary_proxy{bns, format}; - bns_bin["name_hash"] = x.name_hash; + // ✅ Always store name_hash as hex string (RPC-compatible) + bns["name_hash"] = oxenc::to_hex(std::string_view{x.name_hash.data, sizeof(x.name_hash.data)}); if (!x.encrypted_bchat_value.empty()) - bns_bin["value_bchat"] = x.encrypted_bchat_value; + bns["value_bchat"] = oxenc::to_hex(x.encrypted_bchat_value); if (!x.encrypted_wallet_value.empty()) - bns_bin["value_wallet"] = x.encrypted_wallet_value; + bns["value_wallet"] = oxenc::to_hex(x.encrypted_wallet_value); if (!x.encrypted_belnet_value.empty()) - bns_bin["value_belnet"] = x.encrypted_belnet_value; + bns["value_belnet"] = oxenc::to_hex(x.encrypted_belnet_value); if (!x.encrypted_eth_addr_value.empty()) - bns_bin["value_eth_addr"] = x.encrypted_eth_addr_value; + bns["value_eth_addr"] = oxenc::to_hex(x.encrypted_eth_addr_value); _load_owner(bns, "owner", x.owner); _load_owner(bns, "backup_owner", x.backup_owner); - } + set("bns", std::move(bns)); + } // Ignore these fields: void operator()(const tx_extra_padding&) {} @@ -1034,17 +1035,20 @@ namespace cryptonote::rpc { if (get.request.tx_extra_raw) e_bin["tx_extra_raw"] = std::string_view{reinterpret_cast(tx.extra.data()), tx.extra.size()}; - // Clear it because we don't want/care about it in the RPC output (we already got it more - // usefully from the above). - tx.extra.clear(); - { + // Serialize *without* extra because we don't want/care about it in the RPC output (we + // already have all the extra info in more useful form from the other bits of this + // code). + std::vector saved_extra; + std::swap(tx.extra, saved_extra); + serialization::json_archiver ja{ get.is_bt() ? json_binary_proxy::fmt::bt : json_binary_proxy::fmt::hex}; serialize(ja, tx); auto dumped = std::move(ja).json(); e.update(dumped); + std::swap(saved_extra, tx.extra); } if (extra) From e711552b4dc47c4342185ced29266b72a43a201a Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Fri, 29 Aug 2025 17:27:28 +0530 Subject: [PATCH 156/182] optimize Bulletproof+ workflow --- src/blockchain_db/blockchain_db.cpp | 10 +----- .../cryptonote_format_utils.cpp | 2 ++ src/cryptonote_config.h | 1 - src/cryptonote_core/blockchain.cpp | 8 ++--- src/cryptonote_core/cryptonote_core.cpp | 2 +- src/ringct/bulletproofs_plus.cc | 11 ++++--- src/ringct/rctSigs.cpp | 33 +++++++------------ src/ringct/rctTypes.h | 18 +++++----- src/wallet/wallet2.cpp | 16 ++++----- 9 files changed, 40 insertions(+), 61 deletions(-) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 518a9291dbf..0f712e48f37 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -161,16 +161,8 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { - - rct::key commitment; - if (tx.version > cryptonote::txversion::v2_ringct) - { - commitment = tx.rct_signatures.outPk[i].mask; - if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) - commitment = rct::scalarmult8(commitment); - } amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time, - tx.version >= cryptonote::txversion::v2_ringct ? &commitment : NULL); + tx.version >= cryptonote::txversion::v2_ringct ? &tx.rct_signatures.outPk[i].mask : NULL); } } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index fdfe432fc83..a35f2100d70 100755 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -479,6 +479,8 @@ namespace cryptonote CHECK_AND_ASSERT_MES(tx.version >= txversion::v2_ringct, std::numeric_limits::max(), "get_pruned_transaction_weight does not support v1 txes"); CHECK_AND_ASSERT_MES(tx.rct_signatures.type == rct::RCTType::Bulletproof2 || tx.rct_signatures.type == rct::RCTType::CLSAG || tx.rct_signatures.type == rct::RCTType::BulletproofPlus, std::numeric_limits::max(), "Unsupported rct_signatures type in get_pruned_transaction_weight"); + CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTType::Bulletproof2, + std::numeric_limits::max(), "get_pruned_transaction_weight does not support older range proof types"); CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits::max(), "empty vin"); CHECK_AND_ASSERT_MES(std::holds_alternative(tx.vin[0]), std::numeric_limits::max(), "empty vin"); diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index e6d06fa0a35..0fd15bfff30 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -337,7 +337,6 @@ namespace config inline constexpr auto UPTIME_PROOF_FREQUENCY = 1h; // How often to send proofs out to the network since the last proof we successfully sent. (Approximately; this can be up to CHECK_INTERFACE/2 off in either direction). The minimum accepted time between proofs is half of this. inline constexpr auto UPTIME_PROOF_VALIDITY = 2h + 5min; // The maximum time that we consider an uptime proof to be valid (i.e. after this time since the last proof we consider the MN to be down) inline constexpr auto REACHABLE_MAX_FAILURE_VALIDITY = 5min; // If we don't hear any SS ping/belnet bchat test failures for more than this long then we start considering the MN as passing for the purpose of obligation testing until we get another test result. This should be somewhat larger than SS/belnet's max re-test backoff (2min). - inline constexpr std::string_view HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT = "bulletproof_plus"sv; namespace testnet { inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 169960; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 1a847335dc4..27544d858a3 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3158,12 +3158,12 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } // allow bulletproofs plus - if (hf_version < feature::BULLETPROOF_PLUS) { + if (hf_version < cryptonote::feature::BULLETPROOF_PLUS) { if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); if (bulletproof_plus || !tx.rct_signatures.p.bulletproofs_plus.empty()) { - MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(static_cast(feature::BULLETPROOF_PLUS))); + MERROR_VER("Bulletproofs plus are not allowed before v" << std::to_string(static_cast(cryptonote::feature::BULLETPROOF_PLUS))); tvc.m_invalid_output = true; return false; } @@ -3171,12 +3171,12 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } // forbid bulletproofs - if (hf_version >= feature::BULLETPROOF_PLUS) { + if (hf_version >= cryptonote::feature::BULLETPROOF_PLUS) { if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); if (bulletproof) { - MERROR_VER("Bulletproof range proofs are not allowed after v" << std::to_string(static_cast(feature::BULLETPROOF_PLUS))); + MERROR_VER("Bulletproof range proofs are not allowed after v" << std::to_string(static_cast(cryptonote::feature::BULLETPROOF_PLUS))); tvc.m_invalid_output = true; return false; } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index b949af57d0d..2b417e88ae2 100755 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1212,7 +1212,7 @@ namespace cryptonote if (proofs.size() != 1) return false; const size_t sz = proofs[0].V.size(); - if (sz == 0 || sz > TX_BULLETPROOF_PLUS_MAX_OUTPUTS) + if (sz == 0 || sz > cryptonote::TX_BULLETPROOF_PLUS_MAX_OUTPUTS) return false; return true; } diff --git a/src/ringct/bulletproofs_plus.cc b/src/ringct/bulletproofs_plus.cc index 78a172d7f0c..86767cbee50 100644 --- a/src/ringct/bulletproofs_plus.cc +++ b/src/ringct/bulletproofs_plus.cc @@ -109,7 +109,8 @@ namespace rct // Use hashed values to produce indexed public generators static ge_p3 get_exponent(const rct::key &base, size_t idx) { - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + std::string(cryptonote::hashkey::BULLETPROOF_PLUS_EXPONENT) + tools::get_varint_data(idx); + static const std::string domain_separator(cryptonote::hashkey::BULLETPROOF_PLUS_EXPONENT); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); rct::key generator; ge_p3 generator_p3; rct::hash_to_p3(generator_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -151,7 +152,7 @@ namespace rct sc_sub(TWO_SIXTY_FOUR_MINUS_ONE.bytes, TWO_SIXTY_FOUR_MINUS_ONE.bytes, ONE.bytes); // Generate the initial Fiat-Shamir transcript hash, which is constant across all proofs - const std::string domain_separator(cryptonote::config::HASH_KEY_BULLETPROOF_PLUS_TRANSCRIPT); + const std::string domain_separator(cryptonote::hashkey::BULLETPROOF_PLUS_TRANSCRIPT); ge_p3 initial_transcript_p3; rct::hash_to_p3(initial_transcript_p3, rct::hash2rct(crypto::cn_fast_hash(domain_separator.data(), domain_separator.size()))); ge_p3_tobytes(initial_transcript.bytes, &initial_transcript_p3); @@ -645,8 +646,7 @@ namespace rct { sc_mul(temp.bytes, temp.bytes, z_squared.bytes); sc_mul(temp2.bytes, y_powers[MN+1].bytes, temp.bytes); - sc_mul(temp2.bytes, temp2.bytes, gamma[j].bytes); - sc_add(alpha1.bytes, alpha1.bytes, temp2.bytes); + sc_muladd(alpha1.bytes, temp2.bytes, gamma[j].bytes, alpha1.bytes); } // These are used in the inner product rounds @@ -707,7 +707,8 @@ namespace rct rct::key challenge_squared; sc_mul(challenge_squared.bytes, challenge.bytes, challenge.bytes); - rct::key challenge_squared_inv = invert(challenge_squared); + rct::key challenge_squared_inv; + sc_mul(challenge_squared_inv.bytes, challenge_inv.bytes, challenge_inv.bytes); sc_muladd(alpha1.bytes, dL.bytes, challenge_squared.bytes, alpha1.bytes); sc_muladd(alpha1.bytes, dR.bytes, challenge_squared_inv.bytes, alpha1.bytes); diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index bec1e917183..bfd39e6d2a2 100755 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1163,7 +1163,7 @@ namespace rct { //compute range proof rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, amounts[i]); #ifdef DBG - if (!bulletproof) + if (!bulletproof_or_plus) CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif //mask amount and mask @@ -1257,7 +1257,7 @@ namespace rct { if (!bulletproof_or_plus) rv.p.rangeSigs[i] = proveRange(rv.outPk[i].mask, outSk[i].mask, outamounts[i]); #ifdef DBG - if (!bulletproof_or_plus) + if (!bulletproof) CHECK_AND_ASSERT_THROW_MES(verRange(rv.outPk[i].mask, rv.p.rangeSigs[i]), "verRange failed on newly created proof"); #endif } @@ -1266,7 +1266,7 @@ namespace rct { rv.p.bulletproofs_plus.clear(); if (bulletproof_or_plus) { - const bool plus = is_rct_bulletproof_plus(rv.type); + const bool plus = rv.type == RCTType::BulletproofPlus; size_t n_amounts = outamounts.size(); size_t amounts_proved = 0; if (rct_config.range_proof_type == RangeProofType::PaddedBulletproof) @@ -1288,7 +1288,6 @@ namespace rct { else rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev)); #ifdef DBG - CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof"); if (plus) CHECK_AND_ASSERT_THROW_MES(verBulletproofPlus(rv.p.bulletproofs_plus.back()), "verBulletproofPlus failed on newly created proof"); else @@ -1297,10 +1296,7 @@ namespace rct { } for (i = 0; i < outamounts.size(); ++i) { - if (plus) - rv.outPk[i].mask = C[i]; - else - rv.outPk[i].mask = rct::scalarmult8(C[i]); + rv.outPk[i].mask = rct::scalarmult8(C[i]); outSk[i].mask = masks[i]; } } @@ -1338,10 +1334,7 @@ namespace rct { } for (i = 0; i < batch_size; ++i) { - if (plus) - rv.outPk[i + amounts_proved].mask = C[i]; - else - rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); + rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]); outSk[i + amounts_proved].mask = masks[i]; } amounts_proved += batch_size; @@ -1502,7 +1495,8 @@ namespace rct { { CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL"); const rctSig &rv = *rvp; - CHECK_AND_ASSERT_MES(rct::is_rct_simple(rv.type), false, "verRctSemanticsSimple called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTType::Simple || rv.type == RCTType::Bulletproof || rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus, + false, "verRctSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); if (bulletproof || bulletproof_plus) @@ -1546,9 +1540,6 @@ namespace rct { rct::keyV masks(rv.outPk.size()); for (size_t i = 0; i < rv.outPk.size(); i++) { - if (bulletproof_plus) - masks[i] = rct::scalarmult8(rv.outPk[i].mask); - else masks[i] = rv.outPk[i].mask; } key sumOutpks = addKeys(masks); @@ -1628,7 +1619,8 @@ namespace rct { { PERF_TIMER(verRctNonSemanticsSimple); - CHECK_AND_ASSERT_MES(rct::is_rct_simple(rv.type), false, "verRctNonSemanticsSimple called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTType::Simple || rv.type == RCTType::Bulletproof || rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus, + false, "verRctNonSemanticsSimple called on non simple rctSig"); const bool bulletproof = is_rct_bulletproof(rv.type); const bool bulletproof_plus = is_rct_bulletproof_plus(rv.type); // semantics check is early, and mixRing/MGs aren't resolved yet @@ -1702,8 +1694,6 @@ namespace rct { mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; - if (is_rct_bulletproof_plus(rv.type)) - C = scalarmult8(C); DP("C"); DP(C); key Ctmp; @@ -1724,7 +1714,8 @@ namespace rct { } xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) { - CHECK_AND_ASSERT_MES(rct::is_rct_simple(rv.type), false, "decodeRct called on non simple rctSig"); + CHECK_AND_ASSERT_MES(rv.type == RCTType::Simple || rv.type == RCTType::Bulletproof || rv.type == RCTType::Bulletproof2 || rv.type == RCTType::CLSAG || rv.type == RCTType::BulletproofPlus, + false, "decodeRct called on non simple rctSig"); CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index"); CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo"); @@ -1734,8 +1725,6 @@ namespace rct { mask = ecdh_info.mask; key amount = ecdh_info.amount; key C = rv.outPk[i].mask; - if (is_rct_bulletproof_plus(rv.type)) - C = scalarmult8(C); DP("C"); DP(C); key Ctmp; diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 94cdad8cf7e..1794efa815b 100755 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -46,6 +46,7 @@ extern "C" { #include "common/hex.h" #include "serialization/variant.h" #include "common/util.h" +#include //Define this flag when debugging to get additional info on the console @@ -242,8 +243,7 @@ namespace rct { rct::key r1, s1, d1; rct::keyV L, R; - BulletproofPlus(): - A({}), A1({}), B({}), r1({}), s1({}), d1({}) {} + BulletproofPlus() {} BulletproofPlus(const rct::key &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): V({V}), A(A), A1(A1), B(B), r1(r1), s1(s1), d1(d1), L(L), R(R) {} BulletproofPlus(const rct::keyV &V, const rct::key &A, const rct::key &A1, const rct::key &B, const rct::key &r1, const rct::key &s1, const rct::key &d1, const rct::keyV &L, const rct::keyV &R): @@ -386,22 +386,20 @@ namespace rct { return; if (!tools::equals_any(type, RCTType::Full, RCTType::Simple, RCTType::Bulletproof, RCTType::Bulletproof2, RCTType::CLSAG, RCTType::BulletproofPlus)) throw std::invalid_argument{"invalid ringct type"}; - if (type == RCTType::BulletproofPlus) + if (rct::is_rct_bulletproof_plus(type)) { uint32_t nbp = bulletproofs_plus.size(); - VARINT_FIELD(nbp) + field_varint(ar, "nbp", nbp); if (nbp > outputs) throw std::invalid_argument{"too many bulletproofs_plus"}; auto arr = start_array(ar, "bpp", bulletproofs_plus, nbp); - for (size_t i = 0; i < nbp; ++i) - { - FIELDS(bulletproofs_plus[i]) - } + for (auto& b : bulletproofs_plus) + value(ar, b); if (auto n_max = n_bulletproof_plus_max_amounts(bulletproofs_plus); n_max < outputs) throw std::invalid_argument{"invalid bulletproofs_plus: n_max (" + std::to_string(n_max) + ") < outputs (" + std::to_string(outputs) + ")"}; } - else if (type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG) + else if (rct::is_rct_bulletproof(type)) { uint32_t nbp = bulletproofs.size(); if (tools::equals_any(type, RCTType::Bulletproof2, RCTType::CLSAG)) @@ -505,7 +503,7 @@ namespace rct { keyV& get_pseudo_outs() { - return type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG || type == RCTType::BulletproofPlus ? p.pseudoOuts : pseudoOuts; + return (type == RCTType::Bulletproof || type == RCTType::Bulletproof2 || type == RCTType::CLSAG || type == RCTType::BulletproofPlus) ? p.pseudoOuts : pseudoOuts; } keyV const& get_pseudo_outs() const diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 94fab4922fd..21a67b37ce0 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -809,7 +809,7 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra uint64_t estimate_tx_weight(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool clsag, bool bulletproof_plus) { size_t size = estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, clsag, bulletproof_plus); - if (n_outputs > 2 && bulletproof_plus) //need to check this condition "check" later + if (n_outputs > 2) //need to check this condition "check" later { 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) size_t log_padded_outputs = 2; @@ -10283,7 +10283,7 @@ void wallet2::transfer_selected_rct(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_burn(const std::ve std::vector> outs; const bool clsag = use_fork_rules(feature::CLSAG, 0); - const bool bulletproof_plus = use_fork_rules(feature::BULLETPROOF_PLUS, 0); + const bool bulletproof_plus = use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0); const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); @@ -11860,7 +11860,7 @@ std::vector wallet2::create_transactions_from(const crypton std::vector> outs; const bool clsag = use_fork_rules(feature::CLSAG, 0); - const bool bulletproof_plus = use_fork_rules(feature::BULLETPROOF_PLUS, 0); + const bool bulletproof_plus = use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0); const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); @@ -12095,7 +12095,7 @@ void wallet2::cold_sign_tx(const std::vector& 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(feature::BULLETPROOF_PLUS , 0) ? 4 : 3; + aux_data.bp_version = use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS , 0) ? 4 : 3; auto hf_version = get_hard_fork_version(); CHECK_AND_ASSERT_THROW_MES(hf_version, "Failed to query hard fork"); aux_data.hard_fork = static_cast(*hf_version); @@ -12696,10 +12696,8 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt crypto::derivation_to_scalar(found_derivation, n, scalar1); rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tools::equals_any(tx.rct_signatures.type, rct::RCTType::Bulletproof2, rct::RCTType::CLSAG, rct::RCTType::BulletproofPlus)); - rct::key C = tx.rct_signatures.outPk[n].mask; + const rct::key C = tx.rct_signatures.outPk[n].mask; rct::key Ctmp; - if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) - C = rct::scalarmult8(C); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask"); THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.amount.bytes) != 0, error::wallet_internal_error, "Bad ECDH input amount"); rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); From 2275ab0c9991d75e5fc95792e02190dfbfdf6966 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 5 Sep 2025 12:28:31 +0530 Subject: [PATCH 157/182] wallet: feature: transfer amount with fee included --- src/rpc/core_rpc_server.cpp | 6 +- src/simplewallet/simplewallet.cpp | 81 ++++++++- src/wallet/wallet2.cpp | 169 +++++++++++++++--- src/wallet/wallet2.h | 5 +- src/wallet/wallet_errors.h | 18 ++ src/wallet/wallet_rpc_server.cpp | 70 +++++--- src/wallet/wallet_rpc_server.h | 6 +- .../wallet_rpc_server_commands_defs.cpp | 28 +++ src/wallet/wallet_rpc_server_commands_defs.h | 37 ++++ 9 files changed, 359 insertions(+), 61 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index c61542e1852..9fe9f3a9cdb 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1069,10 +1069,10 @@ namespace cryptonote::rpc { const auto& meta = ptx_it->second.meta; e["in_pool"] = true; e["weight"] = meta.weight; - e["relayed"] = (bool) ptx_it->second.meta.relayed; - e["received_timestamp"] = ptx_it->second.meta.receive_time; + e["relayed"] = (bool) meta.relayed; + e["received_timestamp"] = meta.receive_time; e["flash"] = ptx_it->second.flash; - if (meta.double_spend_seen) e["double_spend_seen"] = true; + e["double_spend_seen"] = meta.double_spend_seen; if (meta.do_not_relay) e["do_not_relay"] = true; if (meta.last_relayed_time) e["last_relayed_time"] = meta.last_relayed_time; if (meta.kept_by_block) e["kept_by_block"] = (bool) meta.kept_by_block; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 11196032638..28f8a2faae3 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -157,7 +157,7 @@ namespace const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [uses] [index=[,[,...]]]"); const char* USAGE_PAYMENTS("payments [ ... ]"); const char* USAGE_PAYMENT_ID("payment_id"); - const char* USAGE_TRANSFER("transfer [index=[,,...]] [flash|unimportant] ( |

) []"); + const char* USAGE_TRANSFER("transfer [index=[,,...]] [flash|unimportant] ( |
) [subtractfeefrom=[,,all,...]] []"); const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=[,,...]] [] ( | ) []"); const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=[,,...] | index=all] [] [
] []"); const char* USAGE_SWEEP_ALL("sweep_all [index=[,,...] | index=all] [flash|unimportant] [outputs=] [
[]]"); @@ -462,6 +462,52 @@ namespace return r; } + + static constexpr std::string_view SFFD_ARG_NAME{"subtractfeefrom="}; + + bool parse_subtract_fee_from_outputs + ( + const std::string& arg, + tools::wallet2::unique_index_container& subtract_fee_from_outputs, + bool& subtract_fee_from_all, + bool& matches + ) + { + matches = false; + if (std::string_view{arg}.rfind(SFFD_ARG_NAME, 0) != 0) // if arg doesn't match + return true; + matches = true; + + const char* arg_end = arg.c_str() + arg.size(); + for (const char* p = arg.c_str() + SFFD_ARG_NAME.size(); p < arg_end;) + { + const char* new_p = nullptr; + const unsigned long dest_index = strtoul(p, const_cast(&new_p), 10); + if (dest_index == 0 && new_p == p) // numerical conversion failed + { + if (0 != strncmp(p, "all", 3)) + { + fail_msg_writer() << tr("Failed to parse subtractfeefrom list"); + return false; + } + subtract_fee_from_all = true; + break; + } + else if (dest_index > std::numeric_limits::max()) + { + fail_msg_writer() << tr("Destination index is too large") << ": " << dest_index; + return false; + } + else + { + subtract_fee_from_outputs.insert(dest_index); + p = new_p + 1; // skip the comma + } + } + + return true; + } + void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) { bool warn_of_possible_attack = !trusted_daemon; @@ -2605,7 +2651,7 @@ simple_wallet::simple_wallet() tr("Show the blockchain height.")); m_cmd_binder.set_handler("transfer", [this](const auto& x) { return transfer(x); }, tr(USAGE_TRANSFER), - tr("Transfer to
. If the parameter \"index=[,,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. is the priority of the transaction, or \"flash\" for an instant transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. Multiple payments can be made at once by adding et cetera (before the payment ID, if it's included)")); + tr("Transfer to
. If the parameter \"index=[,,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. Multiple payments can be made at once by adding etcetera (before the payment ID, if it's included). The \"subtractfeefrom=\" list allows you to choose which destinations to fund the tx fee from instead of the change output. The fee will be split across the chosen destinations proportionally equally. For example, to make 3 transfers where the fee is taken from the first and third destinations, one could do: \"transfer 3 0.5 1 subtractfeefrom=0,2\". Let's say the tx fee is 0.1. The balance would drop by exactly 4.5 BDX including fees, and addr1 & addr3 would receive 2.925 & 0.975 BDX, respectively. Use \"subtractfeefrom=all\" to spread the fee across all destinations.")); m_cmd_binder.set_handler("locked_transfer", [this](const auto& x) { return locked_transfer(x); }, tr(USAGE_LOCKED_TRANSFER), @@ -5894,6 +5940,28 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vector dsts_info; std::vector dsts; @@ -5985,6 +6053,13 @@ bool simple_wallet::transfer_main(Transfer transfer_type, const std::vectorcreate_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params); + ptx_vector = m_wallet->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices, tx_params, subtract_fee_from_outputs); if (ptx_vector.empty()) { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b1648453dfa..46b398f8931 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -891,11 +891,11 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry cryptonote::blobdata bd; // easy case if we have the whole tx - if (entry["as_hex"] || (entry["prunable"] && entry["pruned"])) + if (auto hex_it = entry.find("as_hex"); hex_it != entry.end() || (entry.contains("prunable") && entry.contains("pruned"))) { std::string hex_blob; - if (entry["as_hex"]) - hex_blob = entry["as_hex"].get(); + if (hex_it != entry.end()) + hex_blob = hex_it->get(); else hex_blob = entry["pruned"].get() + entry["prunable"].get(); @@ -904,12 +904,12 @@ bool get_pruned_tx(const nlohmann::json& entry, cryptonote::transaction &tx, cry CHECK_AND_ASSERT_MES(cryptonote::parse_and_validate_tx_from_blob(bd, tx), false, "Invalid tx data"); tx_hash = cryptonote::get_transaction_hash(tx); // if the hash was given, check it matches - CHECK_AND_ASSERT_MES(entry["tx_hash"].empty() || tools::type_to_hex(tx_hash) == entry["tx_hash"], false, + CHECK_AND_ASSERT_MES(entry.value("tx_hash", ""sv).empty() || tools::type_to_hex(tx_hash) == entry["tx_hash"], false, "Response claims a different hash than the data yields"); return true; } // case of a pruned tx with its prunable data hash - if (entry["pruned_as_hex"] && entry["prunable_hash"]) + if (entry.contains("pruned") && entry.contains("prunable_hash")) { crypto::hash ph; CHECK_AND_ASSERT_MES(tools::hex_to_type(entry["prunable_hash"].get(), ph), false, "Failed to parse prunable hash"); @@ -3265,7 +3265,9 @@ std::vector wallet2::get_pool_state(bool refreshed) try { nlohmann::json get_transactions_params{ - {"tx_hashes", hex_hashes} + {"tx_hashes", hex_hashes}, + {"prune",true}, + {"split",true} }; res = m_http_client.json_rpc("get_transactions", get_transactions_params); } catch (const std::exception& e) { @@ -3286,7 +3288,7 @@ std::vector wallet2::get_pool_state(bool refreshed) [tx_hash](const std::pair &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_txs.push_back({std::move(tx), tx_hash, tx_entry["double_spend_seen"], tx_entry["flash"]}); + process_txs.push_back({std::move(tx), tx_hash, tx_entry["double_spend_seen"].get() != 0, tx_entry["flash"].get()}); } else { @@ -10900,10 +10902,10 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, // This system allows for sending (almost) the entire balance, since it does // not generate spurious change in all txes, thus decreasing the instantaneous // usable balance. -std::vector wallet2::create_transactions_2(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra_base, uint32_t subaddr_account, std::set subaddr_indices, beldex_construct_tx_params &tx_params) +std::vector wallet2::create_transactions_2(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra_base, uint32_t subaddr_account, std::set subaddr_indices, beldex_construct_tx_params &tx_params, const unique_index_container& subtract_fee_from_outputs) { //ensure device is let in NONE mode in any case - LOG_PRINT_L0("create_transactions_2 get_device prio:" << priority); + LOG_PRINT_L0("create_transactions_2 get_device prio:" << priority); hw::device &hwdev = m_account.get_device(); std::unique_lock hwdev_lock{hwdev}; hw::mode_resetter rst{hwdev}; @@ -10935,12 +10937,13 @@ std::vector wallet2::create_transactions_2(std::vector>> unused_transfers_indices_per_subaddr; std::vector>> unused_dust_indices_per_subaddr; - uint64_t needed_money; + uint64_t needed_money, total_needed_money; // 'needed_money' is the sum of the destination amounts, while 'total_needed_money' includes 'needed_money' plus the fee if not 'subtract_fee_from_outputs' uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { std::vector selected_transfers; std::vector dsts; + std::vector dsts_are_fee_subtractable; cryptonote::transaction tx; pending_tx ptx; size_t weight; @@ -10949,7 +10952,7 @@ std::vector wallet2::create_transactions_2(std::vector::iterator i; @@ -10957,6 +10960,7 @@ std::vector wallet2::create_transactions_2(std::vectoramount = 0; } @@ -10970,11 +10974,64 @@ std::vector wallet2::create_transactions_2(std::vector get_adjusted_dsts(uint64_t needed_fee) const + { + uint64_t dest_total = 0; + uint64_t subtractable_dest_total = 0; + std::vector subtractable_indices; + subtractable_indices.reserve(dsts.size()); + for (size_t i = 0; i < dsts.size(); ++i) + { + dest_total += dsts[i].amount; + if (dsts_are_fee_subtractable[i]) + { + subtractable_dest_total += dsts[i].amount; + subtractable_indices.push_back(i); + } + } + + if (subtractable_indices.empty()) // if subtract_fee_from_outputs is not enabled for this tx + return dsts; + + THROW_WALLET_EXCEPTION_IF(subtractable_dest_total < needed_fee, error::tx_not_possible, + subtractable_dest_total, dest_total, needed_fee); + + std::vector res = dsts; + + // subtract fees from destinations equally, rounded down, until dust is left where we subtract 1 + uint64_t subtractable_remaining = needed_fee; + auto si_it = subtractable_indices.cbegin(); + uint64_t amount_to_subtract = 0; + while (subtractable_remaining) + { + // Set the amount to subtract iterating at the beginning of the list so equal amounts are + // subtracted throughout the list of destinations. We use max(x, 1) so that we we still step + // forwards even when the amount remaining is less than the number of subtractable indices + if (si_it == subtractable_indices.cbegin()) + amount_to_subtract = std::max(subtractable_remaining / subtractable_indices.size(), 1); + + cryptonote::tx_destination_entry& d = res[*si_it]; + THROW_WALLET_EXCEPTION_IF(d.amount <= amount_to_subtract, error::zero_amount); + + subtractable_remaining -= amount_to_subtract; + d.amount -= amount_to_subtract; + ++si_it; + + // Wrap around to first subtractable index once we hit the end of the list + if (si_it == subtractable_indices.cend()) + si_it = subtractable_indices.cbegin(); + } + + return res; + } }; std::vector txes; @@ -11009,6 +11066,14 @@ std::vector wallet2::create_transactions_2(std::vector= dsts.size(), + error::subtract_fee_from_bad_index, *subtract_fee_from_outputs.crbegin()); + + // throw if subtract_fee_from_outputs is enabled and we have too many outputs to fit into one tx + THROW_WALLET_EXCEPTION_IF(subtract_fee_from_outputs.size() && dsts.size() > cryptonote::TX_BULLETPROOF_MAX_OUTPUTS - 1, + error::wallet_internal_error, "subtractfeefrom transfers cannot be split over multiple transactions yet"); + // calculate total amount being sent to all destinations // throw if total amount overflows uint64_t needed_money = 0; @@ -11043,6 +11108,7 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector balance_subtotal, error::not_enough_money, + THROW_WALLET_EXCEPTION_IF(total_needed_money > balance_subtotal || min_fee + fixed_fee > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); // first check overall balance is enough, then unlocked one, so we throw distinct exceptions - THROW_WALLET_EXCEPTION_IF(needed_money + min_fee + fixed_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money, + THROW_WALLET_EXCEPTION_IF(total_needed_money > unlocked_balance_subtotal || min_fee + fixed_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money, unlocked_balance_subtotal, needed_money, 0); } @@ -11155,7 +11221,8 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; std::vector* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; @@ -11276,18 +11343,21 @@ std::vector wallet2::create_transactions_2(std::vector 0 && !dsts.empty() && estimate_tx_weight(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), clsag) < 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)); - tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations); + const bool subtract_fee_from_this_dest = subtract_fee_from_outputs.count(destination_index); + tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations,subtract_fee_from_this_dest); dsts[0].amount -= available_amount; available_amount = 0; } @@ -11320,10 +11390,15 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector test_ptx.fee) { - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, + size_t fee_tries; + for (fee_tries = 0; fee_tries < 10 && needed_fee > test_ptx.fee; ++fee_tries) { + tx_dsts = tx.get_adjusted_dsts(needed_fee); + transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, tx_params); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(test_ptx.tx, txBlob.size(), base_fee, fee_percent, fixed_fee, fee_quantization_mask); @@ -11380,6 +11474,9 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector dsts,*/ + transfer_selected_rct( tx_dsts, /* NOMOD std::vector dsts,*/ tx.selected_transfers, /* const std::list selected_transfers */ fake_outs_count, /* CONST size_t fake_outputs_count, */ tx.outs, /* MOD std::vector> &outs, */ @@ -11469,25 +11567,40 @@ std::vector wallet2::create_transactions_2(std::vector &ptx_vector, std::vector dsts) const +bool wallet2::sanity_check(const std::vector &ptx_vector, std::vector dsts, const unique_index_container& subtract_fee_from_outputs) const { - MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations"); + MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations, subtract_fee_from_outputs " << + (subtract_fee_from_outputs.size() ? "enabled" : "disabled")); hw::device &hwdev = m_account.get_device(); THROW_WALLET_EXCEPTION_IF(ptx_vector.empty(), error::wallet_internal_error, "No transactions"); + THROW_WALLET_EXCEPTION_IF(!subtract_fee_from_outputs.empty() && ptx_vector.size() != 1, + error::wallet_internal_error, "feature subtractfeefrom not supported for split transactions"); + + // For destinations from where the fee is subtracted, the required amount has to be at least + // target amount - (tx fee / num_subtractable + 1). +1 since fee might not be evenly divisble by + // the number of subtractble destinations. For non-subtractable destinations, we need at least + // the target amount. + const size_t num_subtractable_dests = subtract_fee_from_outputs.size(); + const uint64_t fee0 = ptx_vector[0].fee; + const uint64_t subtractable_fee_deduction = fee0 / std::max(num_subtractable_dests, 1) + 1; // check every party in there does receive at least the required amount std::unordered_map> required; - for (const auto &d: dsts) + for (size_t i = 0; i < dsts.size(); ++i) { - required[d.addr].first += d.amount; + const cryptonote::tx_destination_entry& d = dsts[i]; + const bool dest_is_subtractable = subtract_fee_from_outputs.count(i); + const uint64_t fee_deduction = dest_is_subtractable ? subtractable_fee_deduction : 0; + const uint64_t required_amount = d.amount - std::min(fee_deduction, d.amount); + required[d.addr].first += required_amount; required[d.addr].second = d.is_subaddress; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 3a6a22269df..69dcdb17f40 100755 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -397,6 +397,7 @@ namespace tools using transfer_details = wallet::transfer_details; using transfer_container = std::vector; + using unique_index_container = std::set; using pending_tx = wallet::pending_tx; using unsigned_tx_set = wallet::unsigned_tx_set; using signed_tx_set = wallet::signed_tx_set; @@ -756,14 +757,14 @@ namespace tools bool parse_unsigned_tx_from_str(std::string_view unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const fs::path& signed_filename, std::vector& ptx, std::function accept_func = NULL); bool parse_tx_from_str(std::string_view signed_tx_st, std::vector &ptx, std::function accept_func); - std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra_base, uint32_t subaddr_account, std::set subaddr_indices, cryptonote::beldex_construct_tx_params &tx_params); + std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra_base, uint32_t subaddr_account, std::set subaddr_indices, cryptonote::beldex_construct_tx_params &tx_params, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, cryptonote::txtype tx_type = cryptonote::txtype::standard); std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, cryptonote::txtype tx_type = cryptonote::txtype::standard); std::vector create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, cryptonote::txtype tx_type = cryptonote::txtype::standard); std::vector create_transactions_burn(const std::vector &ki, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, cryptonote::txtype tx_type = cryptonote::txtype::coin_burn); - bool sanity_check(const std::vector &ptx_vector, std::vector dsts) const; + bool sanity_check(const std::vector &ptx_vector, std::vector dsts, const unique_index_container& subtract_fee_from_outputs = {}) const; void cold_tx_aux_import(const std::vector& ptx, const std::vector& tx_device_aux); void cold_sign_tx(const std::vector& ptx_vector, signed_tx_set &exported_txs, std::vector const &dsts_info, std::vector & tx_device_aux); uint64_t cold_key_image_sync(uint64_t &spent, uint64_t &unspent); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 4fc629e7389..54c19d773ef 100755 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -84,6 +84,7 @@ namespace tools // tx_sum_overflow // tx_too_big // zero_destination + // subtract_fee_from_bad_index // wallet_rpc_error * // daemon_busy // no_connection_to_daemon @@ -747,6 +748,14 @@ namespace tools uint64_t m_tx_weight_limit; }; //---------------------------------------------------------------------------------------------------- + struct zero_amount: public transfer_error + { + explicit zero_amount(std::string&& loc) + : transfer_error(std::move(loc), "destination amount is zero") + { + } + }; + //---------------------------------------------------------------------------------------------------- struct zero_destination : public transfer_error { explicit zero_destination(std::string&& loc) @@ -755,6 +764,15 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct subtract_fee_from_bad_index : public transfer_error + { + explicit subtract_fee_from_bad_index(std::string&& loc, long bad_index) + : transfer_error(std::move(loc), + "subtractfeefrom: bad index: " + std::to_string(bad_index) + " (indexes are 0-based)") + { + } + }; + //---------------------------------------------------------------------------------------------------- struct wallet_rpc_error : public wallet_logic_error { const std::string& request() const { return m_request; } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index c23fd8374c0..078aeed88fd 100755 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -986,10 +986,10 @@ namespace tools return hex_tx_keys(ptx.tx_key, ptx.additional_tx_keys); } //------------------------------------------------------------------------------------------------------------------------------ - template + template void wallet_rpc_server::fill_response(std::vector &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, bool flash, - Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata) + bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, bool flash, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images) { for (const auto & ptx : ptx_vector) { @@ -1000,6 +1000,24 @@ namespace tools // Compute amount leaving wallet in tx. By convention dests does not include change outputs fill(amount, total_amount(ptx)); fill(fee, ptx.fee); + + // add amounts by destination + tools::wallet_rpc::amounts_list abd; + for (const auto& dst : ptx.dests) + abd.amounts.push_back(dst.amount); + fill(amounts_by_dest, abd); + + // add spent key images + tools::wallet_rpc::key_image_list key_image_list; + bool all_are_txin_to_key = std::all_of(ptx.tx.vin.begin(), ptx.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, cryptonote::txin_to_key, in, false); + key_image_list.key_images.push_back(tools::type_to_hex(in.k_image)); + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, ptx.tx); + fill(spent_key_images, key_image_list); + } if (m_wallet->multisig()) @@ -1050,7 +1068,7 @@ namespace tools if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED}; cryptonote::beldex_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, cryptonote::txtype::standard, priority); - std::vector ptx_vector = m_wallet->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, tx_params); + std::vector ptx_vector = m_wallet->create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, tx_params, req.subtract_fee_from_outputs); if (ptx_vector.empty()) throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "No transaction created"}; @@ -1059,8 +1077,8 @@ namespace tools if (ptx_vector.size() != 1) throw wallet_rpc_error{error_code::TX_TOO_LARGE, "Transaction would be too large. try /transfer_split."}; - fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, - res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata); + fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images); } return res; } @@ -1125,6 +1143,7 @@ namespace tools req.get_tx_key, res.tx_key, res.amount, + res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, @@ -1134,7 +1153,8 @@ namespace tools req.get_tx_hex, res.tx_blob, req.get_tx_metadata, - res.tx_metadata); + res.tx_metadata, + res.spent_key_images); return res; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1165,8 +1185,8 @@ namespace tools if (ptx_vector.empty()) throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, "No transaction created"}; - fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, - res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list); + fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list); } return res; } @@ -1405,8 +1425,8 @@ namespace tools std::vector ptx_vector = m_wallet->create_unmixable_sweep_transactions(); - fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, false /*flash*/, - res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list); + fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, false /*flash*/, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list); return {}; } @@ -1444,8 +1464,8 @@ namespace tools uint32_t priority = convert_priority(req.priority); std::vector ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, cryptonote::TX_OUTPUT_DECOYS, req.unlock_time, priority, extra, req.account_index, subaddr_indices); - fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, - res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list); + fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list); } return res; } @@ -1484,8 +1504,8 @@ namespace tools if (ptx.selected_transfers.size() > 1) throw wallet_rpc_error{error_code::UNKNOWN_ERROR, "The transaction uses multiple inputs, which is not supposed to happen"}; - fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, - res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata); + fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, priority == tx_priority_flash, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images); } return res; } @@ -3050,8 +3070,8 @@ namespace { std::vector ptx_vector = {stake_result.ptx}; - fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, false /*flash*/, - res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata); + fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, false /*flash*/, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images); return res; } @@ -3076,8 +3096,8 @@ namespace { throw wallet_rpc_error{error_code::TX_NOT_POSSIBLE, register_result.msg}; std::vector ptx_vector = {register_result.ptx}; - fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, false /*flash*/, - res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata); + fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, false /*flash*/, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, res.spent_key_images); return res; } @@ -3165,6 +3185,7 @@ namespace { req.get_tx_key, res.tx_key, res.amount, + res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, @@ -3174,7 +3195,8 @@ namespace { req.get_tx_hex, res.tx_blob, req.get_tx_metadata, - res.tx_metadata); + res.tx_metadata, + res.spent_key_images); return res; } @@ -3199,6 +3221,7 @@ namespace { req.get_tx_key, res.tx_key, res.amount, + res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, @@ -3208,7 +3231,8 @@ namespace { req.get_tx_hex, res.tx_blob, req.get_tx_metadata, - res.tx_metadata); + res.tx_metadata, + res.spent_key_images); return res; } @@ -3248,6 +3272,7 @@ namespace { req.get_tx_key, res.tx_key, res.amount, + res.amounts_by_dest, res.fee, res.multisig_txset, res.unsigned_txset, @@ -3257,7 +3282,8 @@ namespace { req.get_tx_hex, res.tx_blob, req.get_tx_metadata, - res.tx_metadata); + res.tx_metadata, + res.spent_key_images); return res; } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 700c302c065..961f91bb6df 100755 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -181,10 +181,10 @@ namespace tools // Safely and cleanly closes the currently open wallet (if one is open) void close_wallet(bool save_current); - template + template void fill_response(std::vector &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, bool flash, - Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata); + bool get_tx_key, Ts& tx_key, Tu &amount, Ta &amounts_by_dest, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, bool flash, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, Tk &spent_key_images); cryptonote::address_parse_info extract_account_addr(cryptonote::network_type nettype, std::string_view addr_or_url); diff --git a/src/wallet/wallet_rpc_server_commands_defs.cpp b/src/wallet/wallet_rpc_server_commands_defs.cpp index 2045d27faf6..facd445de16 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.cpp +++ b/src/wallet/wallet_rpc_server_commands_defs.cpp @@ -193,11 +193,19 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_HEIGHT::response) KV_SERIALIZE(immutable_height) KV_SERIALIZE_MAP_CODE_END() +KV_SERIALIZE_MAP_CODE_BEGIN(key_image_list) + KV_SERIALIZE(key_images) +KV_SERIALIZE_MAP_CODE_END() + +KV_SERIALIZE_MAP_CODE_BEGIN(amounts_list) + KV_SERIALIZE(amounts) +KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(TRANSFER::request) KV_SERIALIZE(destinations) KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE_OPT(subtract_fee_from_outputs, decltype(subtract_fee_from_outputs)()) KV_SERIALIZE(priority) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) @@ -212,11 +220,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(TRANSFER::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() @@ -243,11 +253,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(TRANSFER_SPLIT::response) KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) + KV_SERIALIZE_OPT(amounts_by_dest_list, decltype(amounts_by_dest_list)()) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images_list) KV_SERIALIZE_MAP_CODE_END() @@ -325,11 +337,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SWEEP_DUST::response) KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) + KV_SERIALIZE_OPT(amounts_by_dest_list, decltype(amounts_by_dest_list)()) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images_list) KV_SERIALIZE_MAP_CODE_END() @@ -359,11 +373,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SWEEP_ALL::response) KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) + KV_SERIALIZE_OPT(amounts_by_dest_list, decltype(amounts_by_dest_list)()) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images_list) KV_SERIALIZE_MAP_CODE_END() @@ -385,11 +401,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(SWEEP_SINGLE::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() @@ -1045,11 +1063,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(STAKE::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() @@ -1066,11 +1086,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(REGISTER_MASTER_NODE::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() @@ -1161,11 +1183,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(BNS_BUY_MAPPING::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() @@ -1205,11 +1229,13 @@ KV_SERIALIZE_MAP_CODE_BEGIN(BNS_UPDATE_MAPPING::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() @@ -1315,10 +1341,12 @@ KV_SERIALIZE_MAP_CODE_BEGIN(COIN_BURN::response) KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) + KV_SERIALIZE_OPT(amounts_by_dest, decltype(amounts_by_dest)()) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) KV_SERIALIZE(unsigned_txset) + KV_SERIALIZE(spent_key_images) KV_SERIALIZE_MAP_CODE_END() } diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index d1ce2b4459e..a8114f71de1 100755 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -394,6 +394,22 @@ namespace tools::wallet_rpc { }; }; + struct key_image_list + { + std::list key_images; + + KV_MAP_SERIALIZABLE + }; + + struct amounts_list + { + std::list amounts; + + bool operator==(const amounts_list& other) const { return amounts == other.amounts; } + + KV_MAP_SERIALIZABLE + }; + BELDEX_RPC_DOC_INTROSPECT // Send beldex to a number of recipients. To preview the transaction fee, set do_not_relay to true and get_tx_metadata to true. // Submit the response using the data in get_tx_metadata in the RPC call, relay_tx. @@ -406,6 +422,7 @@ namespace tools::wallet_rpc { std::list destinations; // Array of destinations to receive BELDEX. uint32_t account_index; // (Optional) Transfer from this account index. (Defaults to 0) std::set subaddr_indices; // (Optional) Transfer from this set of subaddresses. (Defaults to 0) + std::set subtract_fee_from_outputs; uint32_t priority; // Set a priority for the transaction. Accepted values are: 1 for unimportant or 5 for flash. (0 and 2-4 are accepted for backwards compatibility and are equivalent to 5) uint64_t unlock_time; // Number of blocks before the beldex can be spent (0 to use the default lock time). std::string payment_id; // (Optional) Random 64-character hex string to identify a transaction. @@ -422,11 +439,13 @@ namespace tools::wallet_rpc { std::string tx_hash; // Publicly searchable transaction hash. std::string tx_key; // Transaction key if get_tx_key is true, otherwise, blank string. uint64_t amount; // Amount transferred for the transaction. + amounts_list amounts_by_dest; uint64_t fee; // Fee charged for the txn. std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if get_tx_metadata is true. std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; @@ -466,11 +485,13 @@ namespace tools::wallet_rpc { std::list tx_hash_list; // The tx hashes of every transaction. std::list tx_key_list; // The transaction keys for every transaction. std::list amount_list; // The amount transferred for every transaction. + std::list amounts_by_dest_list; std::list fee_list; // The amount of fees paid for every transaction. std::list tx_blob_list; // The tx as hex string for every transaction. std::list tx_metadata_list; // List of transaction metadata needed to relay the transactions later. std::string multisig_txset; // The set of signing keys used in a multisig transaction (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + std::list spent_key_images_list; KV_MAP_SERIALIZABLE }; @@ -597,11 +618,13 @@ namespace tools::wallet_rpc { std::list tx_hash_list; // The tx hashes of every transaction. std::list tx_key_list; // The transaction keys for every transaction. std::list amount_list; // The amount transferred for every transaction. + std::list amounts_by_dest_list; std::list fee_list; // The amount of fees paid for every transaction. std::list tx_blob_list; // The tx as hex string for every transaction. std::list tx_metadata_list; // List of transaction metadata needed to relay the transactions later. std::string multisig_txset; // The set of signing keys used in a multisig transaction (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + std::list spent_key_images_list; KV_MAP_SERIALIZABLE }; @@ -644,11 +667,13 @@ namespace tools::wallet_rpc { std::list tx_hash_list; // The tx hashes of every transaction. std::list tx_key_list; // The transaction keys for every transaction. std::list amount_list; // The amount transferred for every transaction. + std::list amounts_by_dest_list; std::list fee_list; // The amount of fees paid for every transaction. std::list tx_blob_list; // The tx as hex string for every transaction. std::list tx_metadata_list; // List of transaction metadata needed to relay the transactions later. std::string multisig_txset; // The set of signing keys used in a multisig transaction (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + std::list spent_key_images_list; KV_MAP_SERIALIZABLE }; @@ -681,11 +706,13 @@ namespace tools::wallet_rpc { std::string tx_hash; // The tx hashes of the transaction. std::string tx_key; // The tx key of the transaction. uint64_t amount; // The amount transfered in atomic units. + amounts_list amounts_by_dest; uint64_t fee; // The fee paid in atomic units. std::string tx_blob; // The tx as hex string. std::string tx_metadata; // Transaction metadata needed to relay the transaction later. std::string multisig_txset; // The set of signing keys used in a multisig transaction (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; @@ -2042,11 +2069,13 @@ BELDEX_RPC_DOC_INTROSPECT std::string tx_hash; // Publicly searchable transaction hash. std::string tx_key; // Transaction key if `get_tx_key` is `true`, otherwise, blank string. uint64_t amount; // Amount transferred for the transaction in atomic units. + amounts_list amounts_by_dest; uint64_t fee; // Value in atomic units of the fee charged for the tx. std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if `get_tx_metadata` is `true`. std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; @@ -2074,11 +2103,13 @@ BELDEX_RPC_DOC_INTROSPECT std::string tx_hash; // Publicly searchable transaction hash. std::string tx_key; // Transaction key if get_tx_key is true, otherwise, blank string. uint64_t amount; // Amount transferred for the transaction in atomic units. + amounts_list amounts_by_dest; uint64_t fee; // Value in atomic units of the fee charged for the tx. std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if `get_tx_metadata` is `true`. std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; @@ -2256,11 +2287,13 @@ For more information on updating and signing see the BNS_UPDATE_MAPPING document std::string tx_hash; // Publicly searchable transaction hash. std::string tx_key; // Transaction key if `get_tx_key` is `true`, otherwise, blank string. uint64_t amount; // Amount transferred for the transaction in atomic units. + amounts_list amounts_by_dest; uint64_t fee; // Value in atomic units of the fee charged for the tx. std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if `get_tx_metadata` is `true`. std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; @@ -2339,11 +2372,13 @@ If signing is performed externally then you must first encrypt the `value` (if b std::string tx_hash; // Publicly searchable transaction hash. std::string tx_key; // Transaction key if `get_tx_key` is `true`, otherwise, blank string. uint64_t amount; // Amount transferred for the transaction in atomic units. + amounts_list amounts_by_dest; uint64_t fee; // Value in atomic units of the fee charged for the tx. std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if `get_tx_metadata` is `true`. std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; @@ -2539,11 +2574,13 @@ This command is only required if the open wallet is one of the owners of a BNS r std::string tx_hash; // Publicly searchable transaction hash. std::string tx_key; // Transaction key if get_tx_key is true, otherwise, blank string. uint64_t amount; // Amount transferred for the transaction. + amounts_list amounts_by_dest; uint64_t fee; // Fee charged for the txn. std::string tx_blob; // Raw transaction represented as hex string, if get_tx_hex is true. std::string tx_metadata; // Set of transaction metadata needed to relay this transfer later, if get_tx_metadata is true. std::string multisig_txset; // Set of multisig transactions in the process of being signed (empty for non-multisig). std::string unsigned_txset; // Set of unsigned tx for cold-signing purposes. + key_image_list spent_key_images; KV_MAP_SERIALIZABLE }; From 59615296aa32f9929ba13657793318993864e105 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 5 Sep 2025 12:40:53 +0530 Subject: [PATCH 158/182] wallet: handled double_spend_seen in get_transactions --- src/rpc/core_rpc_server.cpp | 2 +- src/wallet/wallet2.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9fe9f3a9cdb..85f430fb17c 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1072,7 +1072,7 @@ namespace cryptonote::rpc { e["relayed"] = (bool) meta.relayed; e["received_timestamp"] = meta.receive_time; e["flash"] = ptx_it->second.flash; - e["double_spend_seen"] = meta.double_spend_seen; + e["double_spend_seen"] = meta.double_spend_seen ? true : false; if (meta.do_not_relay) e["do_not_relay"] = true; if (meta.last_relayed_time) e["last_relayed_time"] = meta.last_relayed_time; if (meta.kept_by_block) e["kept_by_block"] = (bool) meta.kept_by_block; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 46b398f8931..a526ad6f3ba 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3288,7 +3288,7 @@ std::vector wallet2::get_pool_state(bool refreshed) [tx_hash](const std::pair &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_txs.push_back({std::move(tx), tx_hash, tx_entry["double_spend_seen"].get() != 0, tx_entry["flash"].get()}); + process_txs.push_back({std::move(tx), tx_hash, tx_entry["double_spend_seen"].get(), tx_entry["flash"].get()}); } else { From 589c90f6439a98b69c63b8c020f13e425c1c6730 Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Fri, 26 Sep 2025 15:07:53 +0530 Subject: [PATCH 159/182] wallet: Handle new hardfork rules --- src/wallet/wallet2.cpp | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 446216dca6f..b192cea3a72 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -808,7 +808,7 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra uint64_t estimate_tx_weight(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool clsag, bool bulletproof_plus) { size_t size = estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, clsag, bulletproof_plus); - if (n_outputs > 2) //need to check this condition "check" later + if (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) size_t log_padded_outputs = 2; @@ -10284,7 +10284,9 @@ void wallet2::transfer_selected_rct(std::vector= hf::hf20_bulletproof_plus) + ? (use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0) ? 4 : 3) + : (use_fork_rules(cryptonote::feature::CLSAG, 0) ? 3 : 2) }; ptx.construction_data.dests = dsts; // record which subaddress indices are being used as inputs @@ -11040,7 +11042,7 @@ std::vector wallet2::create_transactions_2(std::vector= hf::hf20_bulletproof_plus) ? use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0) : false; const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_params.tx_type); @@ -11775,17 +11777,17 @@ std::vector wallet2::create_transactions_burn(const std::ve uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit(); std::vector> outs; + auto hf_version = get_hard_fork_version(); + THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); + const bool clsag = use_fork_rules(feature::CLSAG, 0); - const bool bulletproof_plus = use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0); + const bool bulletproof_plus = (*hf_version >= hf::hf20_bulletproof_plus) ? use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0) : false; const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); uint64_t fixed_fee = 0; - auto hf_version = get_hard_fork_version(); - THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); - beldex_construct_tx_params beldex_tx_params = tools::wallet2::construct_params(*hf_version, tx_type, priority); uint64_t burn_fixed = 0, burn_percent = 0; @@ -11971,17 +11973,17 @@ std::vector wallet2::create_transactions_from(const crypton uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit(); std::vector> outs; + auto hf_version = get_hard_fork_version(); + THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); + const bool clsag = use_fork_rules(feature::CLSAG, 0); - const bool bulletproof_plus = use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0); + const bool bulletproof_plus = (*hf_version >= hf::hf20_bulletproof_plus) ? use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0) : false; const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); const uint64_t fee_quantization_mask = get_fee_quantization_mask(); uint64_t fixed_fee = 0; - auto hf_version = get_hard_fork_version(); - THROW_WALLET_EXCEPTION_IF(!hf_version, error::get_hard_fork_version_error, "Failed to query current hard fork version"); - beldex_construct_tx_params beldex_tx_params = tools::wallet2::construct_params(*hf_version, tx_type, priority); uint64_t burn_fixed = 0, burn_percent = 0; // Swap these out because we don't want them present for building intermediate temporary tx @@ -12203,13 +12205,17 @@ void wallet2::cold_sign_tx(const std::vector& ptx_vector, signed_tx_ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev); CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface"); + auto hf_version = get_hard_fork_version(); + CHECK_AND_ASSERT_THROW_MES(hf_version, "Failed to query hard fork"); + hw::tx_aux_data aux_data; hw::wallet_shim wallet_shim; setup_shim(&wallet_shim, this); aux_data.tx_recipients = dsts_info; - aux_data.bp_version = use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS , 0) ? 4 : 3; - auto hf_version = get_hard_fork_version(); - CHECK_AND_ASSERT_THROW_MES(hf_version, "Failed to query hard fork"); + aux_data.bp_version = (*hf_version >= hf::hf20_bulletproof_plus) + ? (use_fork_rules(cryptonote::feature::BULLETPROOF_PLUS, 0) ? 4 : 3) + : (use_fork_rules(cryptonote::feature::CLSAG, 0) ? 3 : 2); + aux_data.hard_fork = static_cast(*hf_version); dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data); tx_device_aux = aux_data.tx_device_aux; @@ -12264,6 +12270,8 @@ bool wallet2::use_fork_rules(hf version, uint64_t early_blocks) const if (!m_node_rpc_proxy.get_height(height)) THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); + LOG_PRINT_L2("Version is v" << (unsigned)version << " rules"); + LOG_PRINT_L2("earliest_height is h" << (unsigned)earliest_height << " height"); if (!m_node_rpc_proxy.get_earliest_height(static_cast(version), earliest_height)) THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); From c8942f4db5ec25922976fe73c44574424476fa02 Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Mon, 29 Sep 2025 14:31:10 +0530 Subject: [PATCH 160/182] Add RingCT type Bulletproof+ support for staking --- src/cryptonote_core/blockchain.cpp | 4 ++-- src/cryptonote_core/master_node_list.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index b37da2ec0a6..c470f68859b 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3157,7 +3157,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context return false; } - // allow bulletproofs plus + // Don't allow bulletproofs plus before V20 if (hf_version < cryptonote::feature::BULLETPROOF_PLUS) { if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { const bool bulletproof_plus = rct::is_rct_bulletproof_plus(tx.rct_signatures.type); @@ -3170,7 +3170,7 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context } } - // forbid bulletproofs + // forbid bulletproofs from v20 if (hf_version >= cryptonote::feature::BULLETPROOF_PLUS) { if (tx.version >= txversion::v4_tx_types && tx.is_transfer()) { const bool bulletproof = rct::is_rct_bulletproof(tx.rct_signatures.type); diff --git a/src/cryptonote_core/master_node_list.cpp b/src/cryptonote_core/master_node_list.cpp index b6ee2b9ad4b..816f3723ae2 100755 --- a/src/cryptonote_core/master_node_list.cpp +++ b/src/cryptonote_core/master_node_list.cpp @@ -394,6 +394,7 @@ namespace master_nodes case rct::RCTType::Bulletproof: case rct::RCTType::Bulletproof2: case rct::RCTType::CLSAG: + case rct::RCTType::BulletproofPlus: money_transferred = rct::decodeRctSimple(tx.rct_signatures, rct::sk2rct(scalar1), i, mask, hwdev); break; case rct::RCTType::Full: From 14af7e32386effeca2270411fa39f7916d510080 Mon Sep 17 00:00:00 2001 From: jeflin frank Date: Tue, 7 Oct 2025 11:57:20 +0530 Subject: [PATCH 161/182] fix resolve address --- src/wallet/wallet2.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b192cea3a72..d30bd726834 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -6509,9 +6509,9 @@ std::optional wallet2::resolve_address(std::string address, uint64_ {"name_hash", b64_hashed_name} }; auto [success, addr_response] = resolve(req_params); - if (success && addr_response["encrypted_value"]) + if (success && addr_response.contains("encrypted_value") && addr_response["encrypted_value"].is_string()) { - std::optional addr_info = bns::encrypted_wallet_value_to_info(name, addr_response["encrypted_value"], addr_response["nonce"]); + std::optional addr_info = bns::encrypted_wallet_value_to_info(name, addr_response["encrypted_value"].get(), addr_response["nonce"].get()); if (addr_info) { info = std::move(*addr_info); @@ -8873,7 +8873,7 @@ static bns_prepared_args prepare_tx_extra_beldex_name_system_values(wallet2 cons { if (reason) { - *reason = "Signature requested when preparing ONS TX, but this wallet is not the owner of the record owner=" + rowner; + *reason = "Signature requested when preparing BNS TX, but this wallet is not the owner of the record owner=" + rowner; if (rbackup_owner.empty()) *reason += ", backup_owner=" + rbackup_owner; } return result; @@ -12270,8 +12270,6 @@ bool wallet2::use_fork_rules(hf version, uint64_t early_blocks) const if (!m_node_rpc_proxy.get_height(height)) THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); - LOG_PRINT_L2("Version is v" << (unsigned)version << " rules"); - LOG_PRINT_L2("earliest_height is h" << (unsigned)earliest_height << " height"); if (!m_node_rpc_proxy.get_earliest_height(static_cast(version), earliest_height)) THROW_WALLET_EXCEPTION(tools::error::no_connection_to_daemon, __func__); From 53948b5a23a0c819293486c5356932dd9fda70c1 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 8 Oct 2025 11:07:31 +0530 Subject: [PATCH 162/182] Fix: static build for macos --- CMakeLists.txt | 2 +- Makefile | 2 +- cmake/StaticBuild.cmake | 21 +++++++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35a7ef8599b..437a6e1c050 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ cmake_minimum_required(VERSION 3.10) message(STATUS "CMake version ${CMAKE_VERSION}") # Has to be set before `project()`, and ignored on non-macos: -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "macOS deployment target (Apple clang only)") +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") project(beldex VERSION 6.0.0 diff --git a/Makefile b/Makefile index a5bee7fa890..bd9ea03be5f 100755 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ release-static: release-full-static: mkdir -p $(builddir)/release-static - cd $(builddir)/release-static && cmake -DBUILD_STATIC_DEPS=ON -DCMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) + cd $(builddir)/release-static && cmake -DBUILD_STATIC_DEPS=ON -DUSE_LTO=OFF -DRANDOMX_ENABLE_JIT=OFF -DCMAKE_BUILD_TYPE=Release $(topdir) && $(MAKE) release-full-static-archive: release-full-static cd $(builddir)/release-static && $(MAKE) strip_binaries && $(MAKE) create_archive diff --git a/cmake/StaticBuild.cmake b/cmake/StaticBuild.cmake index 7bc1d3d9d94..bea813828aa 100755 --- a/cmake/StaticBuild.cmake +++ b/cmake/StaticBuild.cmake @@ -5,12 +5,12 @@ set(LOCAL_MIRROR "" CACHE STRING "local mirror path/URL for lib downloads") -set(BOOST_VERSION 1.87.0 CACHE STRING "boost version") -set(BOOST_MIRROR ${LOCAL_MIRROR} https://boostorg.jfrog.io/artifactory/main/release/${BOOST_VERSION}/source +set(BOOST_VERSION 1.83.0 CACHE STRING "boost version") +set(BOOST_MIRROR ${LOCAL_MIRROR} https://archives.boost.io/release/${BOOST_VERSION}/source CACHE STRING "boost download mirror(s)") string(REPLACE "." "_" BOOST_VERSION_ ${BOOST_VERSION}) set(BOOST_SOURCE boost_${BOOST_VERSION_}.tar.bz2) -set(BOOST_HASH SHA256=af57be25cb4c4f4b413ed692fe378affb4352ea50fbe294a11ef548f4d527d89 +set(BOOST_HASH SHA256=6478edfe2f3305127cffe8caf73ea0176c53769f4bf1585be237eb30798c3b8e CACHE STRING "boost source hash") set(NCURSES_VERSION 6.2 CACHE STRING "ncurses version") @@ -79,11 +79,11 @@ set(ZMQ_SOURCE zeromq-${ZMQ_VERSION}.tar.gz) set(ZMQ_HASH SHA512=e198ef9f82d392754caadd547537666d4fba0afd7d027749b3adae450516bcf284d241d4616cad3cb4ad9af8c10373d456de92dc6d115b037941659f141e7c0e CACHE STRING "libzmq source hash") -set(ZLIB_VERSION 1.2.11 CACHE STRING "zlib version") -set(ZLIB_MIRROR ${LOCAL_MIRROR} https://zlib.net/fossils +set(ZLIB_VERSION 1.3.1 CACHE STRING "zlib version") +set(ZLIB_MIRROR ${LOCAL_MIRROR} https://github.com/madler/zlib/releases/download/v${ZLIB_VERSION} CACHE STRING "zlib mirror(s)") -set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.gz) -set(ZLIB_HASH SHA512=73fd3fff4adeccd4894084c15ddac89890cd10ef105dd5e1835e1e9bbb6a49ff229713bd197d203edfa17c2727700fce65a2a235f07568212d820dca88b528ae +set(ZLIB_SOURCE zlib-${ZLIB_VERSION}.tar.xz) +set(ZLIB_HASH SHA256=38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32 CACHE STRING "zlib source hash") set(CURL_VERSION 7.76.1 CACHE STRING "curl version") @@ -479,6 +479,11 @@ endif() build_external(sodium) add_static_target(sodium sodium_external libsodium.a) + +if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) + set(zmq_patch PATCH_COMMAND patch -p1 -i ${PROJECT_SOURCE_DIR}/utils/build_scripts/libzmq-mingw-closesocket.patch) +endif() + set(zmq_cross_host "${cross_host}") if(IOS AND cross_host MATCHES "-ios$") # zmq doesn't like "-ios" for the host, so replace it with -darwin @@ -587,7 +592,7 @@ endif() add_static_target(CURL::libcurl curl_external libcurl.a) set(libcurl_link_libs zlib) if(CMAKE_CROSSCOMPILING AND ARCH_TRIPLET MATCHES mingw) - list(APPEND libcurl_link_libs ws2_32;bcrypt) + list(APPEND libcurl_link_libs ws2_32) elseif(APPLE) list(APPEND libcurl_link_libs "-framework SystemConfiguration") endif() From 2ea2bdfc2654b3b5da997530ca8b93de20f505d6 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Thu, 16 Oct 2025 13:15:05 +0530 Subject: [PATCH 163/182] Refactor:bootstrap daemon to use new RPC format --- src/daemon/command_parser_executor.cpp | 22 +-- src/daemon/command_parser_executor.h | 2 +- src/daemon/command_server.cpp | 12 +- src/daemon/daemon.cpp | 2 +- src/daemon/rpc_command_executor.cpp | 63 +++---- src/daemon/rpc_command_executor.h | 8 +- src/rpc/CMakeLists.txt | 2 +- src/rpc/bootstrap_daemon.cpp | 188 +++++++++++--------- src/rpc/bootstrap_daemon.h | 108 +++++------ src/rpc/core_rpc_server.cpp | 165 +++++++++-------- src/rpc/core_rpc_server.h | 38 ++-- src/rpc/core_rpc_server_binary_commands.cpp | 2 +- src/rpc/core_rpc_server_binary_commands.h | 2 +- src/rpc/core_rpc_server_command_parser.cpp | 7 + src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 31 ++-- 16 files changed, 344 insertions(+), 309 deletions(-) diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index debafb82595..49077c1f21f 100755 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -845,17 +845,17 @@ bool command_parser_executor::check_blockchain_pruning(const std::vector& args) -// { -// const size_t args_count = args.size(); -// if (args_count < 1 || args_count > 3) -// return false; - -// return m_executor.set_bootstrap_daemon( -// args[0] != "none" ? args[0] : std::string(), -// args_count > 1 ? args[1] : std::string(), -// args_count > 2 ? args[2] : std::string()); -// } +bool command_parser_executor::set_bootstrap_daemon(const std::vector& args) +{ + const size_t args_count = args.size(); + if (args_count < 1 || args_count > 3) + return false; + + return m_executor.set_bootstrap_daemon( + args[0] != "none" ? args[0] : std::string(), + args_count > 1 ? args[1] : std::string(), + args_count > 2 ? args[2] : std::string()); +} bool command_parser_executor::flush_cache(const std::vector& args) { diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 9f9803fcc89..daa780be24c 100755 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -143,7 +143,7 @@ class command_parser_executor final bool print_mn_state_changes(const std::vector &args); - // bool set_bootstrap_daemon(const std::vector& args); + bool set_bootstrap_daemon(const std::vector& args); bool flush_cache(const std::vector& args); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 57dcdc97823..ae3b3a5bd3b 100755 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -330,12 +330,12 @@ void command_server::init_commands(cryptonote::rpc::core_rpc_server* rpc_server) , "print_mn_state_changes [end height]" , "Query the state changes between the range, omit the last argument to scan until the current block" ); - // m_command_lookup.set_handler( - // "set_bootstrap_daemon" - // , [this](const auto &x) { return m_parser.set_bootstrap_daemon(x); } - // , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])" - // , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." - // ); + m_command_lookup.set_handler( + "set_bootstrap_daemon" + , [this](const auto &x) { return m_parser.set_bootstrap_daemon(x); } + , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])" + , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." + ); m_command_lookup.set_handler( "flush_cache" , [this](const auto &x) { return m_parser.flush_cache(x); } diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index b64b1aade00..8a9c0901856 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -44,7 +44,7 @@ #include "rpc/common/rpc_args.h" #include "rpc/http_server.h" #include "rpc/omq_server.h" -// #include "rpc/bootstrap_daemon.h" +#include "rpc/bootstrap_daemon.h" #include "cryptonote_protocol/quorumnet.h" #include "cryptonote_core/uptime_proof.h" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index bf431e7f8f3..8b17eca784f 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -510,7 +510,7 @@ bool rpc_command_executor::show_status() { uint64_t height = info["height"].get(); uint64_t net_height = std::max(info["target_height"].get(), height); - // std::string bootstrap_msg; + std::string bootstrap_msg; std::ostringstream str; str << "Height: " << height; @@ -524,16 +524,16 @@ bool rpc_command_executor::show_status() { if (height < net_height) str << ", syncing"; - // if (info.value("was_bootstrap_ever_used", false)) - // { - // str << ", bootstrap " << info["bootstrap_daemon_address"].get(); - // if (info.value("untrusted", false)){ - // auto hwb = info["height_without_bootstrap"].get(); - // str << fmt::format(", local height: {} ({:.1f}%)", hwb, get_sync_percentage(hwb, net_height)); - // } - // else - // str << " was used"; - // } + if (info.value("was_bootstrap_ever_used", false)) + { + str << ", bootstrap " << info["bootstrap_daemon_address"].get(); + if (info.value("untrusted", false)){ + auto hwb = info["height_without_bootstrap"].get(); + str << fmt::format(", local height: {} ({:.1f}%)", hwb, get_sync_percentage(hwb, net_height)); + } + else + str << " was used"; + } auto hf_version = hfinfo["version"].get(); if (hf_version < cryptonote::feature::POS && !has_mining_info) @@ -2565,25 +2565,28 @@ bool rpc_command_executor::check_blockchain_pruning() return true; } -// bool rpc_command_executor::set_bootstrap_daemon( -// const std::string &address, -// const std::string &username, -// const std::string &password) -// { -// SET_BOOTSTRAP_DAEMON::request req{}; -// req.address = address; -// req.username = username; -// req.password = password; - -// SET_BOOTSTRAP_DAEMON::response res{}; -// if (!invoke(std::move(req), res, "Failed to set bootstrap daemon to: " + address)) -// return false; - -// tools::success_msg_writer() -// << "Successfully set bootstrap daemon address to " -// << (!req.address.empty() ? req.address : "none"); -// return true; -// } +bool rpc_command_executor::set_bootstrap_daemon( + const std::string &address, + const std::string &username,\ + const std::string &password) +{ + try { + invoke(json{ + {"address", address}, + {"username", username}, + {"password", password} + }); + } catch (const std::exception& e) { + tools::fail_msg_writer() + << "Failed to set bootstrap daemon to: " << address << ": " << e.what(); + return false; + } + + tools::success_msg_writer() + << "Successfully set bootstrap daemon addres= to " + << (!address.empty() ? address : "none"); + return true; +} bool rpc_command_executor::version() { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index cb87076e28b..7048ae11b6c 100755 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -244,10 +244,10 @@ class rpc_command_executor final { bool print_net_stats(); - // bool set_bootstrap_daemon( - // const std::string &address, - // const std::string &username, - // const std::string &password); + bool set_bootstrap_daemon( + const std::string &address, + const std::string &username, + const std::string &password); bool flush_cache(bool bad_txs, bool invalid_blocks); diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index c7b6c30a71c..1670dd6fd74 100755 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -36,7 +36,7 @@ add_library(rpc_commands ) add_library(rpc - # bootstrap_daemon.cpp + bootstrap_daemon.cpp core_rpc_server.cpp ) diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index 8ce1f4c3460..eec01f065d4 100755 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -1,86 +1,102 @@ -// #include "bootstrap_daemon.h" - -// #include - -// #include "common/string_util.h" -// #include "crypto/crypto.h" -// #include "cryptonote_core/cryptonote_core.h" -// #include "epee/misc_log_ex.h" - -// #undef BELDEX_DEFAULT_LOG_CATEGORY -// #define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon" - -// namespace cryptonote -// { - -// bootstrap_daemon::bootstrap_daemon(std::function()> get_next_public_node) -// : m_get_next_public_node(get_next_public_node) -// { -// } - -// bootstrap_daemon::bootstrap_daemon(const std::string &address, const std::optional> &credentials) -// : bootstrap_daemon(nullptr) -// { -// if (!set_server(address, credentials)) -// { -// throw std::runtime_error("invalid bootstrap daemon address or credentials"); -// } -// } - -// std::string bootstrap_daemon::address() const noexcept -// { -// return m_http_client.get_base_url(); -// } - -// std::optional bootstrap_daemon::get_height() -// { -// // FIXME -// throw std::runtime_error{"FIXME"}; - -// /* -// // query bootstrap daemon's height -// rpc::GET_HEIGHT::response res{}; -// if (!invoke({}, res)) -// { -// return std::nullopt; -// } - -// if (res.status != cryptonote::rpc::STATUS_OK) -// { -// return std::nullopt; -// } - -// return res.height; -// */ -// } - -// bool bootstrap_daemon::set_server(std::string url, const std::optional> &credentials /* = std::nullopt */) -// { -// if (!tools::starts_with(url, "http://") && !tools::starts_with(url, "https://")) -// url.insert(0, "http://"); -// m_http_client.set_base_url(std::move(url)); -// if (credentials) -// m_http_client.set_auth(credentials->first, credentials->second); -// else -// m_http_client.set_auth(); - -// MINFO("Changed bootstrap daemon address to " << url); -// return true; -// } - - -// bool bootstrap_daemon::switch_server_if_needed() -// { -// if (!m_failed || !m_get_next_public_node) -// return true; - -// const std::optional address = m_get_next_public_node(); -// if (address) { -// m_failed = false; -// return set_server(*address); -// } - -// return false; -// } - -// } +#include "bootstrap_daemon.h" + +#include + +#include "common/string_util.h" +#include "crypto/crypto.h" +#include "cryptonote_core/cryptonote_core.h" +#include "epee/misc_log_ex.h" + +#undef BELDEX_DEFAULT_LOG_CATEGORY +#define BELDEX_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon" + +namespace cryptonote +{ + bool bootstrap_daemon::invoke_json(std::string_view method, const nlohmann::json& req, nlohmann::json& res) + { + if (!switch_server_if_needed()) + return false; + + try { + res = m_http_client.json_rpc(method, req); + } + catch (const std::exception& e) { + MWARNING("bootstrap daemon JSON request failed: " << e.what()); + set_failed(); + return false; + } + + return true; + } + + bootstrap_daemon::bootstrap_daemon(std::function()> get_next_public_node) + : m_get_next_public_node(get_next_public_node) + { + } + + bootstrap_daemon::bootstrap_daemon(const std::string &address, const std::optional> &credentials) + : bootstrap_daemon(nullptr) + { + if (!set_server(address, credentials)) + { + throw std::runtime_error("invalid bootstrap daemon address or credentials"); + } + } + + std::string bootstrap_daemon::address() const noexcept + { + return m_http_client.get_base_url(); + } + + std::optional bootstrap_daemon::get_height() + { + // FIXME + throw std::runtime_error{"FIXME"}; + + /* + // query bootstrap daemon's height + rpc::GET_HEIGHT::response res{}; + if (!invoke({}, res)) + { + return std::nullopt; + } + + if (res.status != cryptonote::rpc::STATUS_OK) + { + return std::nullopt; + } + + return res.height; + */ + } + + bool bootstrap_daemon::set_server(std::string url, const std::optional> &credentials /* = std::nullopt */) + { + if (!tools::starts_with(url, "http://") && !tools::starts_with(url, "https://")) + url.insert(0, "http://"); + m_http_client.set_base_url(std::move(url)); + if (credentials) + m_http_client.set_auth(credentials->first, credentials->second); + else + m_http_client.set_auth(); + + MINFO("Changed bootstrap daemon address to " << url); + return true; + } + + + bool bootstrap_daemon::switch_server_if_needed() + { + if (!m_failed || !m_get_next_public_node) + return true; + + const std::optional address = m_get_next_public_node(); + if (address) { + m_failed = false; + return set_server(*address); + } + + return false; + } + +} diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 96c8226a5fd..8b83fd04de6 100755 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -1,53 +1,55 @@ -// #pragma once - -// #include -// #include - -// #include "rpc/http_client.h" -// #include "rpc/core_rpc_server_commands_defs.h" -// #include "rpc/core_rpc_server_binary_commands.h" -// namespace cryptonote -// { - -// class bootstrap_daemon -// { -// public: -// bootstrap_daemon(std::function()> get_next_public_node); -// bootstrap_daemon(const std::string &address, const std::optional> &credentials); - -// std::string address() const noexcept; -// std::optional get_height(); -// // Called when a request has failed either internally or for some external reason; the next -// // request will attempt to use a different bootstrap server (if configured). -// void set_failed() { m_failed = true; } - -// template , int> = 0> -// bool invoke(const typename RPC::request& req, typename RPC::response& res) -// { -// if (!switch_server_if_needed()) -// return false; - -// try { -// if constexpr (std::is_base_of_v) -// res = m_http_client.binary(RPC::names().front(), req); -// else -// res = m_http_client.json_rpc(RPC::names().front(), req); -// } catch (const std::exception& e) { -// MWARNING("bootstrap daemon request failed: " << e.what()); -// set_failed(); -// return false; -// } -// return true; -// } - -// private: -// bool set_server(std::string address, const std::optional> &credentials = std::nullopt); -// bool switch_server_if_needed(); - -// private: -// rpc::http_client m_http_client; -// std::function()> m_get_next_public_node; -// bool m_failed = false; -// }; - -// } +#pragma once + +#include +#include + +#include "rpc/http_client.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/core_rpc_server_binary_commands.h" +namespace cryptonote +{ + + class bootstrap_daemon + { + public: + bootstrap_daemon(std::function()> get_next_public_node); + bootstrap_daemon(const std::string &address, const std::optional> &credentials); + + std::string address() const noexcept; + std::optional get_height(); + // Called when a request has failed either internally or for some external reason; the next + // request will attempt to use a different bootstrap server (if configured). + void set_failed() { m_failed = true; } + // New JSON-based bootstrap invocation (for new RPC format) + bool invoke_json(std::string_view method, const nlohmann::json& req, nlohmann::json& res); + + template , int> = 0> + bool invoke(const typename RPC::request& req, typename RPC::response& res) + { + if (!switch_server_if_needed()) + return false; + + try { + if constexpr (std::is_base_of_v) + res = m_http_client.binary(RPC::names().front(), req); + else + res = m_http_client.json_rpc(RPC::names().front(), req); + } catch (const std::exception& e) { + MWARNING("bootstrap daemon request failed: " << e.what()); + set_failed(); + return false; + } + return true; + } + + private: + bool set_server(std::string address, const std::optional> &credentials = std::nullopt); + bool switch_server_if_needed(); + + private: + rpc::http_client m_http_client; + std::function()> m_get_next_public_node; + bool m_failed = false; + }; + +} diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 85f430fb17c..212bca33256 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -41,7 +41,7 @@ #include #include "epee/net/network_throttle.hpp" #include "common/string_util.h" -// #include "bootstrap_daemon.h" +#include "bootstrap_daemon.h" #include "crypto/crypto.h" #include "cryptonote_basic/hardfork.h" #include "cryptonote_basic/tx_extra.h" @@ -150,7 +150,7 @@ namespace cryptonote::rpc { const std::unordered_map> rpc_commands = register_rpc_commands(rpc::core_rpc_types{}, rpc::core_rpc_binary_types{}); - /*const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { + const command_line::arg_descriptor core_rpc_server::arg_bootstrap_daemon_address = { "bootstrap-daemon-address" , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced." , "" @@ -160,14 +160,14 @@ namespace cryptonote::rpc { "bootstrap-daemon-login" , "Specify username:password for the bootstrap daemon login" , "" - };*/ + }; //----------------------------------------------------------------------------------- void core_rpc_server::init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden) { - // command_line::add_arg(desc, arg_bootstrap_daemon_address); - // command_line::add_arg(desc, arg_bootstrap_daemon_login); + command_line::add_arg(desc, arg_bootstrap_daemon_address); + command_line::add_arg(desc, arg_bootstrap_daemon_login); cryptonote::rpc_args::init_options(desc, hidden); } //------------------------------------------------------------------------------------------------------------------------------ @@ -177,48 +177,47 @@ namespace cryptonote::rpc { ) : m_core(cr) , m_p2p(p2p) - // , m_should_use_bootstrap_daemon(false) - // , m_was_bootstrap_ever_used(false) - // {} - // bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username_password) - // { - // std::string_view username, password; - // if (auto loc = username_password.find(':'); loc != std::string::npos) - // { - // username = username_password.substr(0, loc); - // password = username_password.substr(loc + 1); - // } - // return set_bootstrap_daemon(address, username, password); - // } - // //------------------------------------------------------------------------------------------------------------------------------ - // bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password) - // { - // std::optional> credentials; - // if (!username.empty() || !password.empty()) - // credentials.emplace(username, password); + , m_should_use_bootstrap_daemon(false) + , m_was_bootstrap_ever_used(false) + {} + bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username_password) + { + std::string_view username, password; + if (auto loc = username_password.find(':'); loc != std::string::npos) + { + username = username_password.substr(0, loc); + password = username_password.substr(loc + 1); + } + return set_bootstrap_daemon(address, username, password); + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password) + { + std::optional> credentials; + if (!username.empty() || !password.empty()) + credentials.emplace(username, password); - // std::unique_lock lock{m_bootstrap_daemon_mutex}; + std::unique_lock lock{m_bootstrap_daemon_mutex}; - // if (address.empty()) - // m_bootstrap_daemon.reset(); - // else - // m_bootstrap_daemon = std::make_unique(address, credentials); + if (address.empty()) + m_bootstrap_daemon.reset(); + else + m_bootstrap_daemon = std::make_unique(address, credentials); - // m_should_use_bootstrap_daemon = (bool) m_bootstrap_daemon; + m_should_use_bootstrap_daemon = (bool) m_bootstrap_daemon; - // return true; - // } - // //------------------------------------------------------------------------------------------------------------------------------ - // void core_rpc_server::init(const boost::program_options::variables_map& vm) - // { - // if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), - // command_line::get_arg(vm, arg_bootstrap_daemon_login))) - // { - // MERROR("Failed to parse bootstrap daemon address"); - // } - // m_was_bootstrap_ever_used = false; - // } - {} + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + void core_rpc_server::init(const boost::program_options::variables_map& vm) + { + if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), + command_line::get_arg(vm, arg_bootstrap_daemon_login))) + { + MERROR("Failed to parse bootstrap daemon address"); + } + m_was_bootstrap_ever_used = false; + } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_ready() { @@ -254,25 +253,21 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_INFO& info, rpc_context context) { PERF_TIMER(on_get_info); - /* FIXME - if (use_bootstrap_daemon_if_necessary(req, res)) + if (use_bootstrap_daemon_if_necessary({}, info.response)) { - if (context.admin) - { - auto [height, top_hash] = m_core.get_blockchain_top(); - res.height_without_bootstrap = height; - ++*res.height_without_bootstrap; // turn top block height into blockchain height - res.was_bootstrap_ever_used = true; - - std::shared_lock lock{m_bootstrap_daemon_mutex}; - if (m_bootstrap_daemon.get() != nullptr) + if (context.admin) { - res.bootstrap_daemon_address = m_bootstrap_daemon->address(); + auto [height, top_hash] = m_core.get_blockchain_top(); + info.response["height_without_bootstrap"] = height + 1; + info.response["was_bootstrap_ever_used"] = true; + + std::shared_lock lock{m_bootstrap_daemon_mutex}; + if (m_bootstrap_daemon) + info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); } - } - return res; + return; } - */ + auto [top_height, top_hash] = m_core.get_blockchain_top(); auto& bs = m_core.get_blockchain_storage(); @@ -356,11 +351,11 @@ namespace cryptonote::rpc { } info.response["free_space"] = m_core.get_free_space(); - // if (std::shared_lock lock{m_bootstrap_daemon_mutex}; m_bootstrap_daemon) { - // info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); - // info.response["height_without_bootstrap"] = height; - // info.response["was_bootstrap_ever_used"] = m_was_bootstrap_ever_used; - // } + if (std::shared_lock lock{m_bootstrap_daemon_mutex}; m_bootstrap_daemon) { + info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); + info.response["height_without_bootstrap"] = height; + info.response["was_bootstrap_ever_used"] = m_was_bootstrap_ever_used; + } } if (m_core.offline()) @@ -1456,6 +1451,23 @@ namespace cryptonote::rpc { // return res; // } //------------------------------------------------------------------------------------------------------------------------------ + void core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_context context){ + PERF_TIMER(on_set_bootstrap_daemon); + const auto& req = bootstrap_daemon.request; + + if (!set_bootstrap_daemon(req.address, req.username, req.password)) + { + // If setting failed, throw an RPC error + throw rpc_error{ERROR_WRONG_PARAM, + "Failed to set bootstrap daemon to address = " + req.address}; + } + + // On success, populate the response + bootstrap_daemon.response["status"] = STATUS_OK; + bootstrap_daemon.response["address"] = req.address.empty() ? "none" : req.address; + } + //------------------------------------------------------------------------------------------------------------------------------ + void core_rpc_server::invoke(STOP_DAEMON& stop_daemon, rpc_context context) { PERF_TIMER(on_stop_daemon); @@ -1587,7 +1599,7 @@ namespace cryptonote::rpc { /// All the common (untemplated) code for use_bootstrap_daemon_if_necessary. Returns a held lock /// if we need to bootstrap, an unheld one if we don't. - /*std::unique_lock core_rpc_server::should_bootstrap_lock() + std::unique_lock core_rpc_server::should_bootstrap_lock() { // TODO - support bootstrapping via a remote LMQ RPC; requires some argument fiddling @@ -1646,22 +1658,23 @@ namespace cryptonote::rpc { // The RPC type must have a `bool untrusted` member. // template - bool core_rpc_server::use_bootstrap_daemon_if_necessary(const typename RPC::request& req, typename RPC::response& res) + bool core_rpc_server::use_bootstrap_daemon_if_necessary(const nlohmann::json& req, nlohmann::json& res) { - res.untrusted = false; // If compilation fails here then the type being instantiated doesn't support using a bootstrap daemon - auto bs_lock = should_bootstrap_lock(); - if (!bs_lock) - return false; + res["untrusted"] = false; - std::string command_name{RPC::names().front()}; + auto bs_lock = should_bootstrap_lock(); + if (!bs_lock) + return false; // No bootstrap daemon available - if (!m_bootstrap_daemon->invoke(req, res)) - throw std::runtime_error{"Bootstrap request failed"}; + std::string command_name{RPC::names().front()}; - m_was_bootstrap_ever_used = true; - res.untrusted = true; - return true; - }*/ + if (!m_bootstrap_daemon->invoke_json(command_name, req, res)) + throw std::runtime_error{"Bootstrap request failed"}; + + m_was_bootstrap_ever_used = true; + res["untrusted"] = true; + return true; + } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context) { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 70dcec0ea68..7a7ce92c001 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -56,9 +56,9 @@ namespace boost::program_options { class variables_map; } -// namespace cryptonote { -// class bootstrap_daemon; -// } +namespace cryptonote { + class bootstrap_daemon; +} namespace cryptonote::rpc { // FIXME: temporary shim for converting RPC methods @@ -112,8 +112,8 @@ namespace cryptonote::rpc { class core_rpc_server { public: - // static const command_line::arg_descriptor arg_bootstrap_daemon_address; - // static const command_line::arg_descriptor arg_bootstrap_daemon_login; + static const command_line::arg_descriptor arg_bootstrap_daemon_address; + static const command_line::arg_descriptor arg_bootstrap_daemon_login; core_rpc_server( core& cr @@ -121,7 +121,7 @@ namespace cryptonote::rpc { ); static void init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden); - // void init(const boost::program_options::variables_map& vm); + void init(const boost::program_options::variables_map& vm); /// Returns a reference to the owning cryptonote core object core& get_core() { return m_core; } @@ -196,6 +196,7 @@ namespace cryptonote::rpc { void invoke(BNS_RESOLVE& resolve, rpc_context context); void invoke(BNS_LOOKUP& lookup, rpc_context context); void invoke(BNS_VALUE_DECRYPT& value_decrypt, rpc_context context); + void invoke(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_context context); @@ -209,10 +210,6 @@ namespace cryptonote::rpc { GET_OUTPUTS_BIN::response invoke(GET_OUTPUTS_BIN::request&& req, rpc_context context); GET_TRANSACTION_POOL_HASHES_BIN::response invoke(GET_TRANSACTION_POOL_HASHES_BIN::request&& req, rpc_context context); GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response invoke(GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::request&& req, rpc_context context); - - // FIXME: unconverted JSON RPC endpoints: - // SET_BOOTSTRAP_DAEMON::response invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context); - #if defined(BELDEX_ENABLE_INTEGRATION_TEST_HOOKS) void on_relay_uptime_and_votes() { @@ -263,21 +260,22 @@ namespace cryptonote::rpc { //utils uint64_t get_block_reward(const block& blk); - // bool set_bootstrap_daemon(const std::string &address, std::string_view username_password); - // bool set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password); + bool set_bootstrap_daemon(const std::string &address, std::string_view username_password); + bool set_bootstrap_daemon(const std::string &address, std::string_view username, std::string_view password); void fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash, bool get_tx_hashes); - // std::unique_lock should_bootstrap_lock(); + std::unique_lock should_bootstrap_lock(); - // template - // bool use_bootstrap_daemon_if_necessary(const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res); + // JSON version (new) + template + bool use_bootstrap_daemon_if_necessary(const nlohmann::json& req, nlohmann::json& res); core& m_core; nodetool::node_server >& m_p2p; - // std::shared_mutex m_bootstrap_daemon_mutex; - // std::atomic m_should_use_bootstrap_daemon; - // std::unique_ptr m_bootstrap_daemon; - // std::chrono::system_clock::time_point m_bootstrap_height_check_time; - // bool m_was_bootstrap_ever_used; + std::shared_mutex m_bootstrap_daemon_mutex; + std::atomic m_should_use_bootstrap_daemon; + std::unique_ptr m_bootstrap_daemon; + std::chrono::system_clock::time_point m_bootstrap_height_check_time; + bool m_was_bootstrap_ever_used; }; } // namespace cryptonote::rpc diff --git a/src/rpc/core_rpc_server_binary_commands.cpp b/src/rpc/core_rpc_server_binary_commands.cpp index 3d3bd6b92a1..96c9d6f7953 100644 --- a/src/rpc/core_rpc_server_binary_commands.cpp +++ b/src/rpc/core_rpc_server_binary_commands.cpp @@ -209,6 +209,6 @@ KV_SERIALIZE_MAP_CODE_END() KV_SERIALIZE_MAP_CODE_BEGIN(GET_OUTPUT_DISTRIBUTION_BIN::response) KV_SERIALIZE(status) KV_SERIALIZE(distributions) - // KV_SERIALIZE(untrusted) + KV_SERIALIZE(untrusted) KV_SERIALIZE_MAP_CODE_END() } diff --git a/src/rpc/core_rpc_server_binary_commands.h b/src/rpc/core_rpc_server_binary_commands.h index 53e8537df8f..6750928c92a 100644 --- a/src/rpc/core_rpc_server_binary_commands.h +++ b/src/rpc/core_rpc_server_binary_commands.h @@ -288,7 +288,7 @@ namespace cryptonote::rpc { { std::string status; // General RPC error code. "OK" means everything looks good. std::vector distributions; // - // bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). + bool untrusted; // States if the result is obtained using the bootstrap mode, and is therefore not trusted (`true`), or when the daemon is fully synced (`false`). KV_MAP_SERIALIZABLE }; diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 7f8dd707c89..3413bf07fc6 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -206,6 +206,13 @@ namespace cryptonote::rpc { "level", required{set_log_level.request.level}); } + void parse_request(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_input in) { + get_values(in, + "address", bootstrap_daemon.request.address, + "password",bootstrap_daemon.request.password, + "username",bootstrap_daemon.request.username); + } + void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in) { get_values(in, "categories", required{set_log_categories.request.categories}); diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index a808e8f2758..4ddc20f2c9f 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -52,6 +52,7 @@ namespace cryptonote::rpc { void parse_request(SET_LIMIT& limit, rpc_input in); void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); + void parse_request(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_input in); void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 3eacca4c161..c222ccac65a 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1179,23 +1179,17 @@ namespace cryptonote::rpc { } request; }; - // BELDEX_RPC_DOC_INTROSPECT - // // Set the bootstrap daemon to use for data on the blockchain whilst syncing the chain. - // struct SET_BOOTSTRAP_DAEMON : RPC_COMMAND - // { - // static constexpr auto names() { return NAMES("set_bootstrap_daemon"); } - // struct request - // { - - // std::string address; - // std::string username; - // std::string password; - - // KV_MAP_SERIALIZABLE - // }; - - // struct response : STATUS {}; - // }; + // Set the bootstrap daemon to use for data on the blockchain whilst syncing the chain. + struct SET_BOOTSTRAP_DAEMON : RPC_COMMAND + { + static constexpr auto names() { return NAMES("set_bootstrap_daemon"); } + struct request_parameters + { + std::string address; + std::string username; + std::string password; + } request; + }; /// RPC: daemon/stop_daemon /// @@ -2737,7 +2731,8 @@ namespace cryptonote::rpc { SUBMIT_TRANSACTION, SYNC_INFO, TEST_TRIGGER_P2P_RESYNC, - TEST_TRIGGER_UPTIME_PROOF + TEST_TRIGGER_UPTIME_PROOF, + SET_BOOTSTRAP_DAEMON >; using FIXME_old_rpc_types = tools::type_list< RELAY_TX From 5e1ff2747fce1a518e9ce012f4c77f5e7c905ea9 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 16 Oct 2025 16:46:26 +0530 Subject: [PATCH 164/182] Fix: bootstrap-daemon-address and rpc executor for set_bootstrap_daemon --- src/daemon/daemon.cpp | 11 ++++ src/daemon/rpc_command_executor.cpp | 34 ++++++----- src/rpc/bootstrap_daemon.cpp | 30 ++------- src/rpc/bootstrap_daemon.h | 17 +++++- src/rpc/core_rpc_server.cpp | 71 ++++++++++------------ src/rpc/core_rpc_server.h | 7 ++- src/rpc/core_rpc_server_command_parser.cpp | 8 +-- src/rpc/core_rpc_server_command_parser.h | 2 +- 8 files changed, 92 insertions(+), 88 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 8a9c0901856..b80ba643187 100755 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -121,6 +121,10 @@ daemon::daemon(boost::program_options::variables_map vm_) : if (!p2p->init(vm)) throw std::runtime_error("Failed to initialize p2p server."); + MGINFO("- rpc"); + if (!rpc->init(vm)) + throw std::runtime_error("Failed to initialize rpc server."); + // Handle circular dependencies protocol->set_p2p_endpoint(p2p.get()); core->set_cryptonote_protocol(protocol.get()); @@ -235,6 +239,13 @@ daemon::~daemon() http_rpc_admin.reset(); } + MGINFO("- rpc"); + try { + rpc->deinit(); + } catch (const std::exception& e) { + MERROR("Failed to deinitialize rpc: " << e.what()); + } + MGINFO("- p2p"); try { p2p->deinit(); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 8b17eca784f..3d826072390 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -2567,25 +2567,27 @@ bool rpc_command_executor::check_blockchain_pruning() bool rpc_command_executor::set_bootstrap_daemon( const std::string &address, - const std::string &username,\ + const std::string &username, const std::string &password) { - try { - invoke(json{ - {"address", address}, - {"username", username}, - {"password", password} - }); - } catch (const std::exception& e) { - tools::fail_msg_writer() - << "Failed to set bootstrap daemon to: " << address << ": " << e.what(); - return false; - } - tools::success_msg_writer() - << "Successfully set bootstrap daemon addres= to " - << (!address.empty() ? address : "none"); - return true; + auto maybe_set_bootstrap = try_running([&] { + json params{ + {"address", address}, + {"username", username}, + {"password", password} + }; + return invoke(std::move(params)); }, "Failed to query master node state changes"); + + if (!maybe_set_bootstrap) + return false; + + auto set_bootstrap_daemon = *maybe_set_bootstrap; + + tools::success_msg_writer() + << "Successfully set bootstrap daemon address to " + << (!address.empty() ? address : "none"); + return true; } bool rpc_command_executor::version() diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index eec01f065d4..7f6a6761485 100755 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -12,23 +12,7 @@ namespace cryptonote { - bool bootstrap_daemon::invoke_json(std::string_view method, const nlohmann::json& req, nlohmann::json& res) - { - if (!switch_server_if_needed()) - return false; - - try { - res = m_http_client.json_rpc(method, req); - } - catch (const std::exception& e) { - MWARNING("bootstrap daemon JSON request failed: " << e.what()); - set_failed(); - return false; - } - return true; - } - bootstrap_daemon::bootstrap_daemon(std::function()> get_next_public_node) : m_get_next_public_node(get_next_public_node) { @@ -50,24 +34,20 @@ namespace cryptonote std::optional bootstrap_daemon::get_height() { - // FIXME - throw std::runtime_error{"FIXME"}; - - /* // query bootstrap daemon's height - rpc::GET_HEIGHT::response res{}; - if (!invoke({}, res)) + rpc::GET_HEIGHT get_height; + if (!invoke_json({}, get_height.response)) { return std::nullopt; } - if (res.status != cryptonote::rpc::STATUS_OK) + if (get_height.response["status"] != cryptonote::rpc::STATUS_OK) { return std::nullopt; } - return res.height; - */ + return get_height.response["height"].get(); + } bool bootstrap_daemon::set_server(std::string url, const std::optional> &credentials /* = std::nullopt */) diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 8b83fd04de6..4013502ca57 100755 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -20,8 +20,23 @@ namespace cryptonote // Called when a request has failed either internally or for some external reason; the next // request will attempt to use a different bootstrap server (if configured). void set_failed() { m_failed = true; } + // New JSON-based bootstrap invocation (for new RPC format) - bool invoke_json(std::string_view method, const nlohmann::json& req, nlohmann::json& res); + template , int> = 0> + bool invoke_json(const nlohmann::json& req, nlohmann::json& res) + { + if (!switch_server_if_needed()) + return false; + + try { + res = m_http_client.json_rpc(RPC::names().front(), req); + } catch (const std::exception& e) { + MWARNING("bootstrap daemon request failed: " << e.what()); + set_failed(); + return false; + } + return true; + } template , int> = 0> bool invoke(const typename RPC::request& req, typename RPC::response& res) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 212bca33256..e340c3dd990 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -180,6 +180,7 @@ namespace cryptonote::rpc { , m_should_use_bootstrap_daemon(false) , m_was_bootstrap_ever_used(false) {} + bool core_rpc_server::set_bootstrap_daemon(const std::string &address, std::string_view username_password) { std::string_view username, password; @@ -209,14 +210,21 @@ namespace cryptonote::rpc { return true; } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::init(const boost::program_options::variables_map& vm) + bool core_rpc_server::init(const boost::program_options::variables_map& vm) { if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), command_line::get_arg(vm, arg_bootstrap_daemon_login))) { MERROR("Failed to parse bootstrap daemon address"); + return false; } m_was_bootstrap_ever_used = false; + return true; + } + //--------------------------------------------------------------------------------- + bool core_rpc_server::deinit() + { + return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_ready() @@ -253,19 +261,20 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_INFO& info, rpc_context context) { PERF_TIMER(on_get_info); + if (use_bootstrap_daemon_if_necessary({}, info.response)) { - if (context.admin) - { - auto [height, top_hash] = m_core.get_blockchain_top(); - info.response["height_without_bootstrap"] = height + 1; - info.response["was_bootstrap_ever_used"] = true; + if (context.admin) + { + auto [height, top_hash] = m_core.get_blockchain_top(); + info.response["height_without_bootstrap"] = ++height; // turn top block height into blockchain height + info.response["was_bootstrap_ever_used"] = true; - std::shared_lock lock{m_bootstrap_daemon_mutex}; - if (m_bootstrap_daemon) - info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); - } - return; + std::shared_lock lock{m_bootstrap_daemon_mutex}; + if (m_bootstrap_daemon) + info.response["bootstrap_daemon_address"] = m_bootstrap_daemon->address(); + } + return; } auto [top_height, top_hash] = m_core.get_blockchain_top(); @@ -1438,22 +1447,10 @@ namespace cryptonote::rpc { stats.response["status"] = STATUS_OK; } - // //------------------------------------------------------------------------------------------------------------------------------ - // SET_BOOTSTRAP_DAEMON::response core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON::request&& req, rpc_context context) - // { - // PERF_TIMER(on_set_bootstrap_daemon); - - // if (!set_bootstrap_daemon(req.address, req.username, req.password)) - // throw rpc_error{ERROR_WRONG_PARAM, "Failed to set bootstrap daemon to address = " + req.address}; - - // SET_BOOTSTRAP_DAEMON::response res{}; - // res.status = STATUS_OK; - // return res; - // } //------------------------------------------------------------------------------------------------------------------------------ - void core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_context context){ + void core_rpc_server::invoke(SET_BOOTSTRAP_DAEMON& set_bootstrap, rpc_context context){ PERF_TIMER(on_set_bootstrap_daemon); - const auto& req = bootstrap_daemon.request; + const auto& req = set_bootstrap.request; if (!set_bootstrap_daemon(req.address, req.username, req.password)) { @@ -1463,8 +1460,8 @@ namespace cryptonote::rpc { } // On success, populate the response - bootstrap_daemon.response["status"] = STATUS_OK; - bootstrap_daemon.response["address"] = req.address.empty() ? "none" : req.address; + set_bootstrap.response["status"] = STATUS_OK; + set_bootstrap.response["address"] = req.address.empty() ? "none" : req.address; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1660,20 +1657,18 @@ namespace cryptonote::rpc { template bool core_rpc_server::use_bootstrap_daemon_if_necessary(const nlohmann::json& req, nlohmann::json& res) { - res["untrusted"] = false; - - auto bs_lock = should_bootstrap_lock(); - if (!bs_lock) - return false; // No bootstrap daemon available + res["untrusted"] = false; // If compilation fails here then the type being instantiated doesn't support using a bootstrap daemon - std::string command_name{RPC::names().front()}; + auto bs_lock = should_bootstrap_lock(); + if (!bs_lock) + return false; // No bootstrap daemon available - if (!m_bootstrap_daemon->invoke_json(command_name, req, res)) - throw std::runtime_error{"Bootstrap request failed"}; + if (!m_bootstrap_daemon->invoke_json(req, res)) + throw std::runtime_error{"Bootstrap request failed"}; - m_was_bootstrap_ever_used = true; - res["untrusted"] = true; - return true; + m_was_bootstrap_ever_used = true; + res["untrusted"] = true; + return true; } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context) diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 7a7ce92c001..fa8c2220919 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -121,7 +121,8 @@ namespace cryptonote::rpc { ); static void init_options(boost::program_options::options_description& desc, boost::program_options::options_description& hidden); - void init(const boost::program_options::variables_map& vm); + bool init(const boost::program_options::variables_map& vm); + bool deinit(); /// Returns a reference to the owning cryptonote core object core& get_core() { return m_core; } @@ -196,7 +197,7 @@ namespace cryptonote::rpc { void invoke(BNS_RESOLVE& resolve, rpc_context context); void invoke(BNS_LOOKUP& lookup, rpc_context context); void invoke(BNS_VALUE_DECRYPT& value_decrypt, rpc_context context); - void invoke(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_context context); + void invoke(SET_BOOTSTRAP_DAEMON& set_bootstrap, rpc_context context); @@ -267,7 +268,7 @@ namespace cryptonote::rpc { // JSON version (new) template - bool use_bootstrap_daemon_if_necessary(const nlohmann::json& req, nlohmann::json& res); + bool use_bootstrap_daemon_if_necessary(const nlohmann::json& req, nlohmann::json& res); core& m_core; nodetool::node_server >& m_p2p; diff --git a/src/rpc/core_rpc_server_command_parser.cpp b/src/rpc/core_rpc_server_command_parser.cpp index 3413bf07fc6..a63ac5e0685 100644 --- a/src/rpc/core_rpc_server_command_parser.cpp +++ b/src/rpc/core_rpc_server_command_parser.cpp @@ -206,11 +206,11 @@ namespace cryptonote::rpc { "level", required{set_log_level.request.level}); } - void parse_request(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_input in) { + void parse_request(SET_BOOTSTRAP_DAEMON& set_bootstrap_daemon, rpc_input in) { get_values(in, - "address", bootstrap_daemon.request.address, - "password",bootstrap_daemon.request.password, - "username",bootstrap_daemon.request.username); + "address", required{set_bootstrap_daemon.request.address}, + "password", set_bootstrap_daemon.request.password, + "username", set_bootstrap_daemon.request.username); } void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in) { diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 4ddc20f2c9f..3f365bd6604 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -52,7 +52,7 @@ namespace cryptonote::rpc { void parse_request(SET_LIMIT& limit, rpc_input in); void parse_request(SET_LOG_CATEGORIES& set_log_categories, rpc_input in); void parse_request(SET_LOG_LEVEL& set_log_level, rpc_input in); - void parse_request(SET_BOOTSTRAP_DAEMON& bootstrap_daemon, rpc_input in); + void parse_request(SET_BOOTSTRAP_DAEMON& set_bootstrap_daemon, rpc_input in); void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); From 75210b3a59625f4f5c854848d57ccdebf3eb584f Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 16 Oct 2025 18:59:45 +0530 Subject: [PATCH 165/182] Fix: bootstrap for GET_HEIGHT, HARD_FORK_INFO, GET_FEE_ESTIMATE --- src/rpc/core_rpc_server.cpp | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e340c3dd990..5196b11cae3 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -239,10 +239,10 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_HEIGHT& get_height, rpc_context context) { PERF_TIMER(on_get_height); - /* FIXME - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - */ + + if (use_bootstrap_daemon_if_necessary({}, get_height.response)) + return; + auto [height, hash] = m_core.get_blockchain_top(); ++height; // block height to chain height get_height.response["status"] = STATUS_OK; @@ -1882,10 +1882,15 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(HARD_FORK_INFO& hfinfo, rpc_context context) { PERF_TIMER(on_hard_fork_info); - /* - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - */ + + json params{ + {"version", hfinfo.request.version}, + {"height", hfinfo.request.height} + }; + + if (use_bootstrap_daemon_if_necessary(params, hfinfo.response)) + return; + const auto& blockchain = m_core.get_blockchain_storage(); auto version = hfinfo.request.version > 0 ? static_cast(hfinfo.request.version) : @@ -2127,9 +2132,13 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_FEE_ESTIMATE& get_fee_estimate, rpc_context context) { PERF_TIMER(on_get_fee_estimate); - //TODO handle bootstrap daemon in new RPC format - //if (use_bootstrap_daemon_if_necessary(req, res)) - //return res; + + json params{ + {"grace_blocks", get_fee_estimate.request.grace_blocks} + }; + + if (use_bootstrap_daemon_if_necessary(params, get_fee_estimate.response)) + return; auto fees = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(get_fee_estimate.request.grace_blocks); get_fee_estimate.response["fee_per_byte"] = fees.first; From 0e9d19a6cca241941ef753c3a82e856567c4e61b Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Thu, 16 Oct 2025 20:29:51 +0530 Subject: [PATCH 166/182] Fix: bootstrap for GET_TRANSACTIONS, IS_KEY_IMAGE_SPENT, GET_OUTS --- src/rpc/core_rpc_server.cpp | 50 ++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5196b11cae3..42becf29668 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -566,10 +566,19 @@ namespace cryptonote::rpc { //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_OUTPUTS& get_outputs, rpc_context context) { + PERF_TIMER(on_get_outs); - //TODO this bootstrap daemon call to work for new RPC design - //if (use_bootstrap_daemon_if_necessary(req, res)) - //return; + json params{ + {"get_txid", get_outputs.request.get_txid}, + {"as_tuple", get_outputs.request.as_tuple}, + {"output_indices", json::array()} + }; + + for (const auto& h: get_outputs.request.output_indices) + params["output_indices"].push_back(tools::type_to_hex(h)); + + if (use_bootstrap_daemon_if_necessary(params, get_outputs.response)) + return; if (!context.admin && get_outputs.request.output_indices.size() > GET_OUTPUTS::MAX_COUNT) { get_outputs.response["status"] = "Too many outs requested"; @@ -888,11 +897,22 @@ namespace cryptonote::rpc { //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_TRANSACTIONS& get, rpc_context context) { - PERF_TIMER(on_get_transactions); - /* - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - */ + PERF_TIMER(on_get_transactions); + json params{ + {"tx_hashes", json::array()}, + {"memory_pool",get.request.memory_pool}, + {"tx_extra",get.request.tx_extra}, + {"tx_extra_raw",get.request.tx_extra_raw}, + {"data",get.request.data}, + {"split",get.request.split}, + {"prune",get.request.prune} + }; + for (const auto& h: get.request.tx_hashes) + params["tx_hashes"].push_back(tools::type_to_hex(h)); + + if (use_bootstrap_daemon_if_necessary(params, get.response)) + return; + std::unordered_set missed_txs; using split_tx = std::tuple; std::vector txs; @@ -1126,10 +1146,16 @@ namespace cryptonote::rpc { { PERF_TIMER(on_is_key_image_spent); - /* - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - */ + json params{ + {"key_images", json::array()} + }; + + for (const auto& h: spent.request.key_images) + params["key_images"].push_back(tools::type_to_hex(h)); + + if (use_bootstrap_daemon_if_necessary(params, spent.response)) + return; + spent.response["status"] = STATUS_FAILED; std::vector blockchain_spent; From ef4430c62a630b54fd904d289c91d5bd96aeb384 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 17 Oct 2025 10:02:40 +0530 Subject: [PATCH 167/182] Fix: bootstrap for BINARY commands --- src/rpc/core_rpc_server.cpp | 67 +++++++++++++++++-------- src/rpc/core_rpc_server.h | 3 ++ src/rpc/core_rpc_server_commands_defs.h | 2 +- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 42becf29668..f8668b5a807 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -414,8 +414,8 @@ namespace cryptonote::rpc { GET_BLOCKS_BIN::response res{}; PERF_TIMER(on_get_blocks); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; std::vector, std::vector > > > bs; @@ -470,8 +470,8 @@ namespace cryptonote::rpc { GET_ALT_BLOCKS_HASHES_BIN::response res{}; PERF_TIMER(on_get_alt_blocks_hashes); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; std::vector blks; @@ -498,8 +498,8 @@ namespace cryptonote::rpc { GET_BLOCKS_BY_HEIGHT_BIN::response res{}; PERF_TIMER(on_get_blocks_by_height); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; res.status = "Failed"; res.blocks.clear(); @@ -532,8 +532,8 @@ namespace cryptonote::rpc { GET_HASHES_BIN::response res{}; PERF_TIMER(on_get_hashes); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; res.start_height = req.start_height; if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, res.start_height, res.current_height, false)) @@ -551,8 +551,8 @@ namespace cryptonote::rpc { GET_OUTPUTS_BIN::response res{}; PERF_TIMER(on_get_outs_bin); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; if (!context.admin && req.outputs.size() > GET_OUTPUTS_BIN::MAX_COUNT) res.status = "Too many outs requested"; @@ -638,8 +638,8 @@ namespace cryptonote::rpc { GET_TX_GLOBAL_OUTPUTS_INDEXES_BIN::response res{}; PERF_TIMER(on_get_indexes); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); if(!r) @@ -1187,11 +1187,15 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(SUBMIT_TRANSACTION& tx, rpc_context context) { PERF_TIMER(on_submit_transaction); - /* - if (use_bootstrap_daemon_if_necessary(req, res)) - return res; - */ + // json params{ + // {"tx_as_hex", tx.request.tx}, + // {"flash", tx.request.flash} + // }; + // std::cout << "Submitting tx: " << params.dump() << std::endl; + // if (use_bootstrap_daemon_if_necessary(params, tx.response)) + // return; + if (!check_core_ready()) { tx.response["status"] = STATUS_BUSY; return; @@ -1418,8 +1422,8 @@ namespace cryptonote::rpc { GET_TRANSACTION_POOL_HASHES_BIN::response res{}; PERF_TIMER(on_get_transaction_pool_hashes); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; std::vector tx_pool_hashes; m_core.get_pool().get_transaction_hashes(tx_pool_hashes, context.admin, req.flashed_txs_only); @@ -1508,8 +1512,8 @@ namespace cryptonote::rpc { PERF_TIMER(on_get_output_blacklist_bin); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; try { @@ -1696,6 +1700,24 @@ namespace cryptonote::rpc { res["untrusted"] = true; return true; } + + template + bool core_rpc_server::use_bootstrap_daemon_if_necessary(const typename RPC::request& req, typename RPC::response& res) + { + res.untrusted = false; // If compilation fails here then the type being instantiated doesn't support using a bootstrap daemon + auto bs_lock = should_bootstrap_lock(); + if (!bs_lock) + return false; + + std::string command_name{RPC::names().front()}; + + if (!m_bootstrap_daemon->invoke(req, res)) + throw std::runtime_error{"Bootstrap request failed"}; + + m_was_bootstrap_ever_used = true; + res.untrusted = true; + return true; + } //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_LAST_BLOCK_HEADER& get_last_block_header, rpc_context context) { @@ -2500,8 +2522,9 @@ namespace cryptonote::rpc { res.status = "Binary only call"; return res; } - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + + if (use_bootstrap_daemon_if_necessary(req, res)) + return res; try { diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index fa8c2220919..a9a5b332625 100755 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -270,6 +270,9 @@ namespace cryptonote::rpc { template bool use_bootstrap_daemon_if_necessary(const nlohmann::json& req, nlohmann::json& res); + template + bool use_bootstrap_daemon_if_necessary(const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res); + core& m_core; nodetool::node_server >& m_p2p; std::shared_mutex m_bootstrap_daemon_mutex; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index c222ccac65a..e46635a64f8 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -443,7 +443,7 @@ namespace cryptonote::rpc { /// - `"flash"` -- the flash transaction failed (see `flash_status`) struct SUBMIT_TRANSACTION : PUBLIC, LEGACY { - static constexpr auto names() { return NAMES("submit_transaction", "send_raw_transaction", "sendrawtransaction"); } + static constexpr auto names() { return NAMES("send_raw_transaction", "sendrawtransaction", "submit_transaction"); } struct request_parameters { From 2de2ac50db80c34ed9df096f97992be34c2297ed Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 17 Oct 2025 17:50:15 +0530 Subject: [PATCH 168/182] rpc: integrate bootstrap daemon support in RPCs --- src/rpc/core_rpc_server.cpp | 100 +++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f8668b5a807..2c1993c5f7c 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1436,9 +1436,8 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_TRANSACTION_POOL_HASHES& get_transaction_pool_hashes, rpc_context context) { PERF_TIMER(on_get_transaction_pool_hashes); - //TODO handle bootstrap daemon with RPC - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + if (use_bootstrap_daemon_if_necessary({}, get_transaction_pool_hashes.response)) + return; std::vector tx_hashes; m_core.get_pool().get_transaction_hashes(tx_hashes, context.admin); @@ -1449,9 +1448,11 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_TRANSACTION_POOL_STATS& stats, rpc_context context) { PERF_TIMER(on_get_transaction_pool_stats); - //TODO handle bootstrap daemon - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + json params{ + {"include_unrelayed", stats.request.include_unrelayed} + }; + if (use_bootstrap_daemon_if_necessary(params, stats.response)) + return; auto txpool = m_core.get_pool().get_transaction_stats(stats.request.include_unrelayed); json pool_stats{ @@ -1723,6 +1724,14 @@ namespace cryptonote::rpc { { PERF_TIMER(on_get_last_block_header); + json params{ + {"fill_pow_hash", get_last_block_header.request.fill_pow_hash}, + {"get_tx_hashes", get_last_block_header.request.get_tx_hashes} + }; + + if (use_bootstrap_daemon_if_necessary(params, get_last_block_header.response)) + return; + if(!check_core_ready()) { get_last_block_header.response["status"] = STATUS_BUSY; @@ -1747,7 +1756,19 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HASH& get_block_header_by_hash, rpc_context context) { PERF_TIMER(on_get_block_header_by_hash); + + json params{ + {"hash",get_block_header_by_hash.request.hash}, + {"hashes", json::array()}, + {"fill_pow_hash",get_block_header_by_hash.request.fill_pow_hash}, + {"get_tx_hashes",get_block_header_by_hash.request.get_tx_hashes} + }; + for (const auto& h: get_block_header_by_hash.request.hashes) + params["hashes"].push_back(h); + if (use_bootstrap_daemon_if_necessary(params, get_block_header_by_hash.response)) + return; + auto get = [this, &get_block_header_by_hash, admin=context.admin](const std::string &hash, block_header_response &block_header) { crypto::hash block_hash; if (!tools::hex_to_type(hash, block_hash)) @@ -1782,6 +1803,15 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_BLOCK_HEADERS_RANGE& get_block_headers_range, rpc_context context) { PERF_TIMER(on_get_block_headers_range); + json params{ + {"start_height", get_block_headers_range.request.start_height}, + {"end_height", get_block_headers_range.request.end_height}, + {"fill_pow_hash", get_block_headers_range.request.fill_pow_hash}, + {"get_tx_hashes", get_block_headers_range.request.get_tx_hashes} + }; + if (use_bootstrap_daemon_if_necessary(params, get_block_headers_range.response)) + return; + const uint64_t bc_height = m_core.get_current_blockchain_height(); uint64_t start_height = get_block_headers_range.request.start_height; uint64_t end_height = get_block_headers_range.request.end_height; @@ -1820,6 +1850,19 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_BLOCK_HEADER_BY_HEIGHT& get_block_header_by_height, rpc_context context) { PERF_TIMER(on_get_block_header_by_height); + + json params{ + {"height",get_block_header_by_height.request.height}, + {"heights", json::array()}, + {"fill_pow_hash",get_block_header_by_height.request.fill_pow_hash}, + {"get_tx_hashes",get_block_header_by_height.request.get_tx_hashes} + }; + for (const auto& h: get_block_header_by_height.request.heights) + params["heights"].push_back(h); + + if (use_bootstrap_daemon_if_necessary(params, get_block_header_by_height.response)) + return; + auto get = [this, curr_height=m_core.get_current_blockchain_height(), pow=get_block_header_by_height.request.fill_pow_hash && context.admin, tx_hashes=get_block_header_by_height.request.get_tx_hashes] (uint64_t height, block_header_response& bhr) { if (height >= curr_height) @@ -1856,6 +1899,14 @@ namespace cryptonote::rpc { uint64_t block_height; bool orphan = false; crypto::hash block_hash; + json params{ + {"hash", get_block.request.hash}, + {"height", get_block.request.height}, + {"fill_pow_hash", get_block.request.fill_pow_hash} + }; + if (use_bootstrap_daemon_if_necessary(params, get_block.response)) + return; + if (!get_block.request.hash.empty()) { if (!tools::hex_to_type(get_block.request.hash, block_hash)) @@ -2083,8 +2134,19 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_OUTPUT_HISTOGRAM& get_output_histogram, rpc_context context) { PERF_TIMER(on_get_output_histogram); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + json params{ + {"amounts", json::array()}, + {"min_count", get_output_histogram.request.min_count}, + {"max_count", get_output_histogram.request.max_count}, + {"unlocked", get_output_histogram.request.unlocked}, + {"recent_cutoff", get_output_histogram.request.recent_cutoff} + }; + + for (const auto& amt : get_output_histogram.request.amounts) + params["amounts"].push_back(amt); + + if (use_bootstrap_daemon_if_necessary(params, get_output_histogram.response)) + return; if (!context.admin && get_output_histogram.request.recent_cutoff > 0 && get_output_histogram.request.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) { @@ -2128,9 +2190,8 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_VERSION& version, rpc_context context) { PERF_TIMER(on_get_version); - //TODO how replace bootstrap daemon - //if (use_bootstrap_daemon_if_necessary(req, res)) - //return res; + if (use_bootstrap_daemon_if_necessary({}, version.response)) + return; version.response["version"] = pack_version(VERSION); version.response["status"] = STATUS_OK; @@ -2235,6 +2296,8 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_LIMIT& limit, rpc_context context) { PERF_TIMER(on_get_limit); + if (use_bootstrap_daemon_if_necessary({}, limit.response)) + return; limit.response = { {"limit_down", epee::net_utils::connection_basic::get_rate_down_limit()}, @@ -2477,8 +2540,19 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(GET_OUTPUT_DISTRIBUTION& get_output_distribution, rpc_context context) { PERF_TIMER(on_get_output_distribution); - // if (use_bootstrap_daemon_if_necessary(req, res)) - // return res; + json params{ + {"amounts", json::array()}, + {"from_height", get_output_distribution.request.from_height}, + {"to_height", get_output_distribution.request.to_height}, + {"cumulative", get_output_distribution.request.cumulative} + }; + + for (const auto& amt : get_output_distribution.request.amounts) + params["amounts"].push_back(amt); + + if (use_bootstrap_daemon_if_necessary(params, get_output_distribution.response)) + return; + try { // 0 is placeholder for the whole chain From 0a071cde0953deca33e6d05c0149a377789d18b5 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Wed, 22 Oct 2025 18:56:35 +0530 Subject: [PATCH 169/182] Added BNS and quorum RPC responses using bootstrap daemon Added use_bootstrap_daemon_if_necessary() calls for BNS and quorum-related RPC methods to support fallback via bootstrap daemon when local node is still syncing. - BNS_NAMES_TO_OWNERS - BNS_OWNERS_TO_NAMES - BNS_RESOLVE - BNS_LOOKUP - BNS_DECRYPT - GET_QUORUM_STATE - GET_CHECKPOINTS - GET_MN_STATE_CHANGES --- src/rpc/core_rpc_server.cpp | 90 +++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 9 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 2c1993c5f7c..972570a6c17 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2654,6 +2654,17 @@ namespace cryptonote::rpc { { PERF_TIMER(on_get_quorum_state); + json params; + if (get_quorum_state.request.start_height.has_value()) + params["start_height"] = *get_quorum_state.request.start_height; + if (get_quorum_state.request.end_height.has_value()) + params["end_height"] = *get_quorum_state.request.end_height; + if (get_quorum_state.request.quorum_type.has_value()) + params["quorum_type"] = *get_quorum_state.request.quorum_type; + + if (use_bootstrap_daemon_if_necessary(params, get_quorum_state.response)) + return; + const auto& quorum_type = get_quorum_state.request.quorum_type; auto is_requested_type = [&quorum_type](master_nodes::quorum_type type) { @@ -3251,6 +3262,17 @@ namespace cryptonote::rpc { if (!context.admin) check_quantity_limit(get_checkpoints.request.count, GET_CHECKPOINTS::MAX_COUNT); + json params; + if (get_checkpoints.request.start_height.has_value()) + params["start_height"] = *get_checkpoints.request.start_height; + if (get_checkpoints.request.end_height.has_value()) + params["end_height"] = *get_checkpoints.request.end_height; + if (get_checkpoints.request.count.has_value()) + params["count"] = *get_checkpoints.request.count; + + if (use_bootstrap_daemon_if_necessary(params, get_checkpoints.response)) + return; + auto& start = get_checkpoints.request.start_height; auto& end = get_checkpoints.request.end_height; auto count = get_checkpoints.request.count.value_or(GET_CHECKPOINTS::NUM_CHECKPOINTS_TO_QUERY_BY_DEFAULT); @@ -3281,6 +3303,14 @@ namespace cryptonote::rpc { //------------------------------------------------------------------------------------------------------------------------------ void core_rpc_server::invoke(GET_MN_STATE_CHANGES& get_mn_state_changes, rpc_context context) { + json params; + params["start_height"] = get_mn_state_changes.request.start_height; + if (get_mn_state_changes.request.end_height.has_value()) + params["end_height"] = *get_mn_state_changes.request.end_height; + + if (use_bootstrap_daemon_if_necessary(params, get_mn_state_changes.response)) + return; + using blob_t = cryptonote::blobdata; using block_pair_t = std::pair; std::vector blocks; @@ -3402,9 +3432,17 @@ namespace cryptonote::rpc { void core_rpc_server::invoke(BNS_NAMES_TO_OWNERS& names_to_owners, rpc_context context) { if (!context.admin) - { check_quantity_limit(names_to_owners.request.name_hash.size(), BNS_NAMES_TO_OWNERS::MAX_REQUEST_ENTRIES); - } + + json params{ + {"name_hash", json::array()}, + {"include_expired", names_to_owners.request.include_expired}, + }; + for (const auto& name_hash: names_to_owners.request.name_hash) + params["name_hash"].push_back(name_hash); + + if (use_bootstrap_daemon_if_necessary(params, names_to_owners.response)) + return; std::optional height = m_core.get_current_blockchain_height(); auto hf_version = get_network_version(nettype(), *height); @@ -3446,6 +3484,16 @@ namespace cryptonote::rpc { { if (!context.admin) check_quantity_limit(owners_to_names.request.entries.size(), BNS_OWNERS_TO_NAMES::MAX_REQUEST_ENTRIES); + + json params{ + {"entries", json::array()}, + {"include_expired", owners_to_names.request.include_expired}, + }; + for (const auto& name_hash: owners_to_names.request.entries) + params["entries"].push_back(name_hash); + + if (use_bootstrap_daemon_if_necessary(params, owners_to_names.response)) + return; std::unordered_map owner_to_request_index; std::vector owners; @@ -3516,6 +3564,13 @@ namespace cryptonote::rpc { if (!name_hash) throw rpc_error{ERROR_WRONG_PARAM, "Unable to resolve BNS address: invalid 'name_hash' value '" + req.name_hash + "'"}; + json params{ + {"type", resolve.request.type}, + {"name_hash", *name_hash}, + }; + + if (use_bootstrap_daemon_if_necessary(params, resolve.response)) + return; auto hf_version = m_core.get_blockchain_storage().get_network_version(); auto type = static_cast(req.type); @@ -3535,7 +3590,12 @@ namespace cryptonote::rpc { { std::string name = tools::lowercase_ascii_string(std::move(lookup.request.name)); - + json params{ + {"name", lookup.request.name} + }; + if (use_bootstrap_daemon_if_necessary(params, lookup.response)) + return; + BNS_NAMES_TO_OWNERS name_to_owner{}; name_to_owner.request.name_hash.push_back(bns::name_to_base64_hash(name)); invoke(name_to_owner, context); @@ -3560,14 +3620,18 @@ namespace cryptonote::rpc { {"wallet", "encrypted_wallet_value"}, {"eth_addr", "encrypted_eth_addr_value"}}) { - if (!entries[key].empty()) { - BNS_VALUE_DECRYPT value_decrypt; - value_decrypt.request = {name, type, entries[key]}; - invoke(value_decrypt, context); - lookup.response[type + "_value"] = value_decrypt.response["value"]; + if (entries.contains(key) && !entries[key].get().empty()) { + BNS_VALUE_DECRYPT value_decrypt; + value_decrypt.request = {name, type, entries[key].get()}; + try { + invoke(value_decrypt, context); + lookup.response[type + "_value"] = value_decrypt.response["value"]; + } catch (const rpc_error& e) { + MERROR("Value decryption failed for type " << type << ": " << e.what()); + } } + } } - } lookup.response["status"] = STATUS_OK; } @@ -3581,6 +3645,14 @@ namespace cryptonote::rpc { // Validate encrypted value // // --------------------------------------------------------------------------------------------- + json params{ + {"name", req.name}, + {"type", req.type}, + {"encrypted_value", req.encrypted_value}, + }; + if (use_bootstrap_daemon_if_necessary(params, value_decrypt.response)) + return; + if (req.encrypted_value.size() % 2 != 0) throw rpc_error{ERROR_INVALID_VALUE_LENGTH, "Value length not divisible by 2, length=" + std::to_string(req.encrypted_value.size())}; From 11b15e18dc752b73d70605561a135c4d2754f5f7 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 24 Oct 2025 17:29:44 +0530 Subject: [PATCH 170/182] Added bootstrap daemon support to submit_transaction RPC --- src/rpc/core_rpc_server.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 972570a6c17..7c348bd7c8d 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1188,13 +1188,12 @@ namespace cryptonote::rpc { { PERF_TIMER(on_submit_transaction); - // json params{ - // {"tx_as_hex", tx.request.tx}, - // {"flash", tx.request.flash} - // }; - // std::cout << "Submitting tx: " << params.dump() << std::endl; - // if (use_bootstrap_daemon_if_necessary(params, tx.response)) - // return; + json params{ + {"tx_as_hex", oxenc::to_hex(tx.request.tx)}, + {"flash", tx.request.flash} + }; + if (use_bootstrap_daemon_if_necessary(params, tx.response)) + return; if (!check_core_ready()) { tx.response["status"] = STATUS_BUSY; @@ -3645,14 +3644,6 @@ namespace cryptonote::rpc { // Validate encrypted value // // --------------------------------------------------------------------------------------------- - json params{ - {"name", req.name}, - {"type", req.type}, - {"encrypted_value", req.encrypted_value}, - }; - if (use_bootstrap_daemon_if_necessary(params, value_decrypt.response)) - return; - if (req.encrypted_value.size() % 2 != 0) throw rpc_error{ERROR_INVALID_VALUE_LENGTH, "Value length not divisible by 2, length=" + std::to_string(req.encrypted_value.size())}; From dc940154c265bd26542b391055061ff889807e11 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Sat, 25 Oct 2025 12:02:30 +0530 Subject: [PATCH 171/182] Version bump v7.0.0 --- CMakeLists.txt | 2 +- src/cryptonote_basic/hardfork.cpp | 1 + src/cryptonote_core/master_node_rules.h | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 437a6e1c050..617d5ee3494 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ message(STATUS "CMake version ${CMAKE_VERSION}") set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (Apple clang only)") project(beldex - VERSION 6.0.0 + VERSION 7.0.0 LANGUAGES CXX C) set(BELDEX_RELEASE_CODENAME "Hermes") diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index 3cb4c6a5bac..b080e9811c9 100755 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -58,6 +58,7 @@ static constexpr std::array testnet_hard_forks = hard_fork{hf::hf17_POS, 0, 169960, 1636391696 }, // Monday, November 8, 2021 5:14:56 PM hard_fork{hf::hf18_bns, 0, 1251330, 1701063000 }, // Monday, November 27, 2023 5:30:00 AM hard_fork{hf::hf19_enhance_bns, 0, 1997558, 1723447800 }, // Monday, Aug 12, 2024 7:30:00 AM + hard_fork{hf::hf20_bulletproof_plus, 0, 3262180, 1761388200 }, // Saturday, Oct 25, 2025 10:30:00 AM }; static constexpr std::array devnet_hard_forks = diff --git a/src/cryptonote_core/master_node_rules.h b/src/cryptonote_core/master_node_rules.h index a8712f6b9cd..53fe3a9c3bf 100755 --- a/src/cryptonote_core/master_node_rules.h +++ b/src/cryptonote_core/master_node_rules.h @@ -223,8 +223,8 @@ namespace master_nodes { // blocks out of sync and sending something that it thinks is legit. inline constexpr uint64_t VOTE_OR_TX_VERIFY_HEIGHT_BUFFER = 5; - inline constexpr std::array MIN_STORAGE_SERVER_VERSION{{2, 3, 0}}; - inline constexpr std::array MIN_BELNET_VERSION{{0, 9, 7}}; + inline constexpr std::array MIN_STORAGE_SERVER_VERSION{{2, 4, 0}}; + inline constexpr std::array MIN_BELNET_VERSION{{0, 9, 8}}; // The minimum accepted version number, broadcasted by Master Nodes via uptime proofs for each hardfork struct proof_version @@ -236,6 +236,7 @@ namespace master_nodes { }; inline constexpr proof_version MIN_UPTIME_PROOF_VERSIONS[] = { + proof_version{{cryptonote::hf::hf20_bulletproof_plus, 0}, {7,0,0}, {0,9,8}, {2,4,0}}, proof_version{{cryptonote::hf::hf19_enhance_bns, 0}, {6,0,0}, {0,9,7}, {2,3,0}}, proof_version{{cryptonote::hf::hf18_bns, 0}, {5,0,0}, {0,9,7}, {2,3,0}}, proof_version{{cryptonote::hf::hf17_POS, 0}, {4,0,0}, {0,9,5}, {2,2,0}}, From 4950edcad1dfaeb6cb5447097e0c7d6a784d1436 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Wed, 29 Oct 2025 15:24:42 +0530 Subject: [PATCH 172/182] fix: handle status command when daemon runs as a bootstrap node --- src/daemon/rpc_command_executor.cpp | 30 +++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 3d826072390..2f6cb259ec1 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -490,20 +490,22 @@ bool rpc_command_executor::show_status() { if (!maybe_master_keys) return false; - my_mn_key = (*maybe_master_keys)["master_node_pubkey"]; - - auto maybe_mns = try_running([&] { return invoke(json{{"master_node_pubkeys", json::array({my_mn_key})}}); }, "Failed to retrieve master node info"); - - if (maybe_mns) { - if (auto it = maybe_mns->find("master_node_states"); it != maybe_mns->end() && it->is_array() && it->size() > 0) { - auto& state = it->front(); - my_mn_registered = true; - my_mn_staked = state["total_contributed"].get() >= state["staking_requirement"].get(); - my_mn_active = state["active"].get(); - my_decomm_remaining = state["earned_downtime_blocks"].get(); - my_mn_last_uptime = state["last_uptime_proof"].get(); - my_reason_all = state.value("last_decommission_reason_consensus_all", 0); - my_reason_any = state.value("last_decommission_reason_consensus_any", 0); + if (maybe_master_keys->contains("master_node_pubkey")) { + my_mn_key = (*maybe_master_keys)["master_node_pubkey"].get(); + + auto maybe_mns = try_running([&] { return invoke(json{{"master_node_pubkeys", json::array({my_mn_key})}}); }, "Failed to retrieve master node info"); + + if (maybe_mns) { + if (auto it = maybe_mns->find("master_node_states"); it != maybe_mns->end() && it->is_array() && it->size() > 0) { + auto& state = it->front(); + my_mn_registered = true; + my_mn_staked = state["total_contributed"].get() >= state["staking_requirement"].get(); + my_mn_active = state["active"].get(); + my_decomm_remaining = state["earned_downtime_blocks"].get(); + my_mn_last_uptime = state["last_uptime_proof"].get(); + my_reason_all = state.value("last_decommission_reason_consensus_all", 0); + my_reason_any = state.value("last_decommission_reason_consensus_any", 0); + } } } } From 40e549eb97596c13374070424de33a7639192553 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 29 Oct 2025 15:29:07 +0530 Subject: [PATCH 173/182] Fix: Windows build --- src/common/perf_timer.h | 1 + src/crypto/oaes_lib.c | 12 +++++++-- utils/build_scripts/drone-static-upload.sh | 30 +++++++++++----------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index f0e0c71b1b1..a7ad36a1a22 100755 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include "epee/misc_log_ex.h" diff --git a/src/crypto/oaes_lib.c b/src/crypto/oaes_lib.c index a8490e58516..cb33e359e57 100755 --- a/src/crypto/oaes_lib.c +++ b/src/crypto/oaes_lib.c @@ -473,7 +473,11 @@ static void oaes_get_seed( char buf[RANDSIZ + 1] ) char * _test = NULL; gettimeofday(&timer, NULL); - gmTimer = gmtime( &timer.tv_sec ); + + // Convert tv_sec to time_t for gmtime + time_t t = (time_t)timer.tv_sec; + gmTimer = gmtime(&t); + _test = (char *) calloc( sizeof( char ), timer.tv_usec/1000 ); sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d", gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday, @@ -492,7 +496,11 @@ static uint32_t oaes_get_seed(void) uint32_t _ret = 0; gettimeofday(&timer, NULL); - gmTimer = gmtime( &timer.tv_sec ); + + // Convert tv_sec to time_t for gmtime + time_t t = (time_t)timer.tv_sec; + gmTimer = gmtime(&t); + _test = (char *) calloc( sizeof( char ), timer.tv_usec/1000 ); _ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday + gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.tv_usec/1000 + diff --git a/utils/build_scripts/drone-static-upload.sh b/utils/build_scripts/drone-static-upload.sh index 738bb4f652b..1346424d31c 100755 --- a/utils/build_scripts/drone-static-upload.sh +++ b/utils/build_scripts/drone-static-upload.sh @@ -5,19 +5,19 @@ -set -o errexit +# set -o errexit -if [ -z "$SSH_KEY" ]; then - echo -e "\n\n\n\e[31;1mUnable to upload artifact: SSH_KEY not set\e[0m" - # Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds - exit 0 -fi +# if [ -z "$SSH_KEY" ]; then +# echo -e "\n\n\n\e[31;1mUnable to upload artifact: SSH_KEY not set\e[0m" +# # Just warn but don't fail, so that this doesn't trigger a build failure for untrusted builds +# exit 0 +# fi -echo "$SSH_KEY" >ssh_key +# echo "$SSH_KEY" >ssh_key -set -o xtrace # Don't start tracing until *after* we write the ssh key +# set -o xtrace # Don't start tracing until *after* we write the ssh key -chmod 600 ssh_key +# chmod 600 ssh_key branch_or_tag=${DRONE_BRANCH:-${DRONE_TAG:-unknown}} @@ -49,12 +49,12 @@ for p in "${upload_dirs[@]}"; do -mkdir $dir_tmp" done -sftp -i ssh_key -b - -o StrictHostKeyChecking=off drone@beldex.rocks < Date: Thu, 30 Oct 2025 11:16:10 +0530 Subject: [PATCH 174/182] fix: my_bns() wallet_api json error --- src/wallet/api/wallet.cpp | 52 +++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index d4396f6b6c6..b7a06fb45ab 100755 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -2303,48 +2303,40 @@ std::vector* WalletImpl::MyBns() const for (auto const &entry : result["entries"]) { std::string_view name; - std::string value_bchat, value_wallet, value_belnet, value_eth_addr; + std::string value_bchat, value_wallet, value_belnet, value_eth; if (auto got = cache.find(entry["name_hash"]); got != cache.end()) { name = got->second.name; - // BCHAT - { - bns::mapping_value mv; - const auto type = bns::mapping_type::bchat; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_bchat_value"].get()), &mv) && mv.decrypt(name, type)) - value_bchat = mv.to_readable_value(nettype, type); - } - // ETH_ADDRESS - { - bns::mapping_value mv; - const auto type = bns::mapping_type::eth_addr; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_eth_addr_value"].get()), &mv) && mv.decrypt(name, type)) - value_eth_addr = mv.to_readable_value(nettype, type); - } - // WALLET - { - bns::mapping_value mv; - const auto type = bns::mapping_type::wallet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_wallet_value"].get()), &mv) && mv.decrypt(name, type)) - value_wallet = mv.to_readable_value(nettype, type); - } - // BELNET - { + + auto decrypt_value = [&](std::string_view key, bns::mapping_type type, std::string& out) { + auto it = entry.find(key); + if (it != entry.end() && !it->empty()) + { bns::mapping_value mv; - const auto type = bns::mapping_type::belnet; - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(entry["encrypted_belnet_value"].get()), &mv) && mv.decrypt(name, type)) - value_belnet = mv.to_readable_value(nettype, type); - } + const auto& hex_str = it->get_ref(); + if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(hex_str), &mv) && + mv.decrypt(name, type)) + { + out = mv.to_readable_value(nettype, type); + } + } + }; + + decrypt_value("encrypted_bchat_value", bns::mapping_type::bchat, value_bchat); + decrypt_value("encrypted_wallet_value", bns::mapping_type::wallet, value_wallet); + decrypt_value("encrypted_belnet_value", bns::mapping_type::belnet, value_belnet); + decrypt_value("encrypted_eth_addr_value", bns::mapping_type::eth_addr, value_eth); } + auto &info = my_bns->emplace_back(); info.name_hash = entry["name_hash"]; info.name = name.empty() ? "(none)" : std::string(name); info.value_bchat = value_bchat.empty() ? "(none)" : value_bchat; info.value_wallet = value_wallet.empty() ? "(none)" : value_wallet; info.value_belnet = value_belnet.empty() ? "(none)" : value_belnet; - info.value_eth_addr = value_eth_addr.empty() ? "(none)" : value_eth_addr; + info.value_eth_addr = value_eth.empty() ? "(none)" : value_eth; info.owner = entry["owner"]; - if (auto got = entry.find("backup_owner"); got != entry.end()) + if (entry.contains("backup_owner") && !entry["backup_owner"].is_null()) info.backup_owner = entry["backup_owner"]; else info.backup_owner = "(none)"; From 18bacfbefcf98cf45030c68f52e885ced37a32ff Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Thu, 30 Oct 2025 14:16:06 +0530 Subject: [PATCH 175/182] Fix: handling the owners_to_names response --- src/wallet/api/wallet.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index b7a06fb45ab..22cf052ba7a 100755 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -2307,18 +2307,17 @@ std::vector* WalletImpl::MyBns() const if (auto got = cache.find(entry["name_hash"]); got != cache.end()) { name = got->second.name; - auto decrypt_value = [&](std::string_view key, bns::mapping_type type, std::string& out) { auto it = entry.find(key); if (it != entry.end() && !it->empty()) { - bns::mapping_value mv; - const auto& hex_str = it->get_ref(); - if (bns::mapping_value::validate_encrypted(type, oxenc::from_hex(hex_str), &mv) && - mv.decrypt(name, type)) - { - out = mv.to_readable_value(nettype, type); - } + bns::mapping_value mv; + const auto& hex_str = it->get_ref(); + if (!hex_str.empty() && bns::mapping_value::validate_encrypted(type, oxenc::from_hex(hex_str), &mv) && + mv.decrypt(name, type)) + { + out = mv.to_readable_value(nettype, type); + } } }; @@ -2342,10 +2341,11 @@ std::vector* WalletImpl::MyBns() const info.backup_owner = "(none)"; info.update_height = entry["update_height"]; info.expiration_height = entry["expiration_height"]; - info.encrypted_bchat_value = entry["encrypted_bchat_value"].empty() ? "(none)" : entry["encrypted_bchat_value"]; - info.encrypted_wallet_value = entry["encrypted_wallet_value"].empty() ? "(none)" : entry["encrypted_wallet_value"]; - info.encrypted_belnet_value = entry["encrypted_belnet_value"].empty() ? "(none)" : entry["encrypted_belnet_value"]; - info.encrypted_eth_addr_value = entry["encrypted_eth_addr_value"].empty() ? "(none)" : entry["encrypted_eth_addr_value"]; + + info.encrypted_bchat_value = entry["encrypted_bchat_value"].get().empty() ? "(none)" : entry["encrypted_bchat_value"]; + info.encrypted_wallet_value = entry["encrypted_wallet_value"].get().empty() ? "(none)" : entry["encrypted_wallet_value"]; + info.encrypted_belnet_value = entry["encrypted_belnet_value"].get().empty() ? "(none)" : entry["encrypted_belnet_value"]; + info.encrypted_eth_addr_value = entry["encrypted_eth_addr_value"].get().empty() ? "(none)" : entry["encrypted_eth_addr_value"]; } return my_bns; } From 01faaf2b8204ea4682557f622683b549017913bf Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Mon, 3 Nov 2025 10:54:33 +0530 Subject: [PATCH 176/182] fix: cannot use push_back() with object for bns owners_to_names --- src/wallet/api/wallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 22cf052ba7a..fd84ffffd32 100755 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1068,7 +1068,7 @@ int WalletImpl::countBns() auto w = wallet(); nlohmann::json req_params{ - {"entries", {}} + {"entries", nlohmann::json::array()} }; for (uint32_t index = 0; index < w->get_num_subaddresses(0); ++index) @@ -2282,7 +2282,7 @@ std::vector* WalletImpl::MyBns() const auto w = wallet(); nlohmann::json req_params{ - {"entries", {}} + {"entries", nlohmann::json::array()} }; std::unordered_map cache = w->get_bns_cache(); From 7da11c64d201f9712b1213627cce48acd7cce94d Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Mon, 3 Nov 2025 15:56:53 +0530 Subject: [PATCH 177/182] fix(wallet): avoid JSON parsing error when daemon sends response to the wallet --- src/rpc/core_rpc_server.cpp | 4 +- src/wallet/wallet2.cpp | 94 ++++++++++++++++++------------------- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 7c348bd7c8d..799aca942b2 100755 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1238,7 +1238,9 @@ namespace cryptonote::rpc { if (!m_core.handle_incoming_tx(tx.request.tx, tvc, tx_pool_options::new_tx()) || tvc.m_verifivation_failed || !tvc.m_should_be_relayed) { tx.response["status"] = STATUS_FAILED; - auto reason = print_tx_verification_context(tvc); + const vote_verification_context& vvc = tvc.m_vote_ctx; + std::string reason = print_tx_verification_context(tvc); + reason += print_vote_verification_context(vvc); LOG_PRINT_L0("[on_submit_transaction]: " << (tvc.m_verifivation_failed ? "tx verification failed" : "Failed to process tx") << reason); tx.response["reason"] = std::move(reason); tx.response["reason_codes"] = tx_verification_failure_codes(tvc); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d30bd726834..3df14e83d79 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -210,51 +210,51 @@ namespace { return false; } - std::string get_text_reason(const nlohmann::json& res, cryptonote::transaction const *tx, bool flash) - { - if (flash) { - return res["reason"].get(); - } - else { - std::ostringstream os; - - const auto tvc = res["tvc"]; - if (auto got = tvc.find("m_verbose_error"); got != tvc.end()) os << res["tvc"]["m_verbose_error"].get() << "\n"; - if (auto got = tvc.find("m_verifivation_failed"); got != tvc.end()) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection - if (auto got = tvc.find("m_verifivation_impossible"); got != tvc.end()) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain - if (auto got = tvc.find("m_should_be_relayed"); got == tvc.end()) os << "TX should NOT be relayed, "; - if (auto got = tvc.find("m_added_to_pool"); got != tvc.end()) os << "TX added to pool, "; - if (auto got = tvc.find("m_low_mixin"); got != tvc.end()) os << "Insufficient mixin, "; - if (auto got = tvc.find("m_double_spend"); got != tvc.end()) os << "Double spend TX, "; - if (auto got = tvc.find("m_invalid_input"); got != tvc.end()) os << "Invalid inputs, "; - if (auto got = tvc.find("m_invalid_output"); got != tvc.end()) os << "Invalid outputs, "; - if (auto got = tvc.find("m_too_few_outputs"); got != tvc.end()) os << "Need at least 2 outputs, "; - if (auto got = tvc.find("m_too_big"); got != tvc.end()) os << "TX too big, "; - if (auto got = tvc.find("m_overspend"); got != tvc.end()) os << "Overspend, "; - if (auto got = tvc.find("m_fee_too_low"); got != tvc.end()) os << "Fee too low, "; - if (auto got = tvc.find("m_invalid_version"); got != tvc.end()) os << "TX has invalid version, "; - if (auto got = tvc.find("m_invalid_type"); got != tvc.end()) os << "TX has invalid type, "; - if (auto got = tvc.find("m_key_image_locked_by_mnode"); got != tvc.end()) os << "Key image is locked by master node, "; - if (auto got = tvc.find("m_key_image_blacklisted"); got != tvc.end()) os << "Key image is blacklisted on the master node network, "; - - const auto m_vote_ctx = tvc["m_vote_ctx"]; - if (auto got = m_vote_ctx.find("m_validator_index_out_of_bounds"); got != m_vote_ctx.end()) os << "Validator index out of bounds"; - if (auto got = m_vote_ctx.find("m_signature_not_valid"); got != m_vote_ctx.end()) os << "Signature not valid, "; - if (auto got = m_vote_ctx.find("m_added_to_pool"); got != m_vote_ctx.end()) os << "Added to pool, "; - if (auto got = m_vote_ctx.find("m_not_enough_votes"); got != m_vote_ctx.end()) os << "Not enough votes, "; - if (auto got = m_vote_ctx.find("m_incorrect_voting_group"); got != m_vote_ctx.end()) os << "Incorrect voting group specified,"; - if (auto got = m_vote_ctx.find("m_votes_not_sorted"); got != m_vote_ctx.end()) os << "Votes are not stored in ascending order"; - - if (tx) - os << "TX Version: " << tx->version << ", Type: " << tx->type; - - std::string buf = os.str(); - if (buf.size() >= 2 && buf[buf.size() - 2] == ',') - buf.resize(buf.size() - 2); - - return buf; - } - } + // std::string get_text_reason(const nlohmann::json& res, cryptonote::transaction const *tx, bool flash) + // { + // if (flash) { + // return res["reason"].get(); + // } + // else { + // std::ostringstream os; + + // const auto tvc = res["tvc"]; + // if (auto got = tvc.find("m_verbose_error"); got != tvc.end()) os << res["tvc"]["m_verbose_error"].get() << "\n"; + // if (auto got = tvc.find("m_verifivation_failed"); got != tvc.end()) os << "Verification failed, connection should be dropped, "; //bad tx, should drop connection + // if (auto got = tvc.find("m_verifivation_impossible"); got != tvc.end()) os << "Verification impossible, related to alt chain, "; //the transaction is related with an alternative blockchain + // if (auto got = tvc.find("m_should_be_relayed"); got == tvc.end()) os << "TX should NOT be relayed, "; + // if (auto got = tvc.find("m_added_to_pool"); got != tvc.end()) os << "TX added to pool, "; + // if (auto got = tvc.find("m_low_mixin"); got != tvc.end()) os << "Insufficient mixin, "; + // if (auto got = tvc.find("m_double_spend"); got != tvc.end()) os << "Double spend TX, "; + // if (auto got = tvc.find("m_invalid_input"); got != tvc.end()) os << "Invalid inputs, "; + // if (auto got = tvc.find("m_invalid_output"); got != tvc.end()) os << "Invalid outputs, "; + // if (auto got = tvc.find("m_too_few_outputs"); got != tvc.end()) os << "Need at least 2 outputs, "; + // if (auto got = tvc.find("m_too_big"); got != tvc.end()) os << "TX too big, "; + // if (auto got = tvc.find("m_overspend"); got != tvc.end()) os << "Overspend, "; + // if (auto got = tvc.find("m_fee_too_low"); got != tvc.end()) os << "Fee too low, "; + // if (auto got = tvc.find("m_invalid_version"); got != tvc.end()) os << "TX has invalid version, "; + // if (auto got = tvc.find("m_invalid_type"); got != tvc.end()) os << "TX has invalid type, "; + // if (auto got = tvc.find("m_key_image_locked_by_mnode"); got != tvc.end()) os << "Key image is locked by master node, "; + // if (auto got = tvc.find("m_key_image_blacklisted"); got != tvc.end()) os << "Key image is blacklisted on the master node network, "; + + // const auto m_vote_ctx = tvc["m_vote_ctx"]; + // if (auto got = m_vote_ctx.find("m_validator_index_out_of_bounds"); got != m_vote_ctx.end()) os << "Validator index out of bounds"; + // if (auto got = m_vote_ctx.find("m_signature_not_valid"); got != m_vote_ctx.end()) os << "Signature not valid, "; + // if (auto got = m_vote_ctx.find("m_added_to_pool"); got != m_vote_ctx.end()) os << "Added to pool, "; + // if (auto got = m_vote_ctx.find("m_not_enough_votes"); got != m_vote_ctx.end()) os << "Not enough votes, "; + // if (auto got = m_vote_ctx.find("m_incorrect_voting_group"); got != m_vote_ctx.end()) os << "Incorrect voting group specified,"; + // if (auto got = m_vote_ctx.find("m_votes_not_sorted"); got != m_vote_ctx.end()) os << "Votes are not stored in ascending order"; + + // if (tx) + // os << "TX Version: " << tx->version << ", Type: " << tx->type; + + // std::string buf = os.str(); + // if (buf.size() >= 2 && buf[buf.size() - 2] == ',') + // buf.resize(buf.size() - 2); + + // return buf; + // } + // } size_t get_num_outputs(const std::vector &dsts, const std::vector &transfers, const std::vector &selected_transfers, const beldex_construct_tx_params& tx_params) { @@ -6955,9 +6955,9 @@ void wallet2::commit_tx(pending_tx& ptx, bool flash) auto daemon_send_resp = m_http_client.json_rpc("send_raw_transaction", send_transaction_params); THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] == rpc::STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); if (flash) - THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] != rpc::STATUS_OK, error::tx_flash_rejected, ptx.tx, get_rpc_status(daemon_send_resp["status"]), get_text_reason(daemon_send_resp, &ptx.tx, flash)); + THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] != rpc::STATUS_OK, error::tx_flash_rejected, ptx.tx, get_rpc_status(daemon_send_resp["status"]), daemon_send_resp["reason"].is_string() ? daemon_send_resp["reason"].get() : "Daemon provided no reason"); else - THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] != rpc::STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp["status"]), get_text_reason(daemon_send_resp, &ptx.tx, flash)); + THROW_WALLET_EXCEPTION_IF(daemon_send_resp["status"] != rpc::STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp["status"]), daemon_send_resp["reason"].is_string() ? daemon_send_resp["reason"].get() : "Daemon provided no reason"); // sanity checks for (size_t idx: ptx.selected_transfers) { From 588a08f10a3aae3115ed0c26c3cad237a186a7c3 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Wed, 5 Nov 2025 15:43:51 +0530 Subject: [PATCH 178/182] Fix: beldexd print_mn_status version print --- src/daemon/rpc_command_executor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2f6cb259ec1..b91b9526f09 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1731,7 +1731,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net stream << indent2 << "Storage Server / Belnet Router versions: " << show_component_version(entry["storage_server_version"], "Storage Server") << " / " - << show_component_version(entry["storage_server_version"], "Belnet") + << show_component_version(entry["belnet_version"], "Belnet") << "\n"; // From ac45b36ef517ad2c22ef1363b885cd558c3be773 Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Thu, 6 Nov 2025 12:30:57 +0530 Subject: [PATCH 179/182] fix(print_mn_status): handle empty version string check --- src/daemon/rpc_command_executor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index b91b9526f09..519446ca9e6 100755 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1724,7 +1724,7 @@ static void append_printable_master_node_list_entry(cryptonote::network_type net // NOTE: Component Versions // auto show_component_version = [] (const json& j, std::string_view name) { - if (!j.is_array() || j.front().get() == 0) + if (!j.is_array() || j.empty()) return "("s + std::string{name} + " ping not yet received)"s; return tools::join(".", j.get>()); }; From 20b5de0d9c1b60978c815dc3e41b4828a071eef9 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 14 Nov 2025 12:02:36 +0530 Subject: [PATCH 180/182] feat: add new hard fork 'Obscura' --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 617d5ee3494..96ced0222c1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "macOS deployment target (App project(beldex VERSION 7.0.0 LANGUAGES CXX C) -set(BELDEX_RELEASE_CODENAME "Hermes") +set(BELDEX_RELEASE_CODENAME "Obscura") # String value to append to the full version string; this is intended to easily identify whether a # binary was build from the release or development branches. This should be permanently set to an From ff800e2077e29c3314f6ad512cc0f7bf6aec5146 Mon Sep 17 00:00:00 2001 From: victor-tucci Date: Fri, 14 Nov 2025 12:21:00 +0530 Subject: [PATCH 181/182] feat: add Bulletproof+ hard fork activation params (HF20) --- src/cryptonote_basic/hardfork.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cryptonote_basic/hardfork.cpp b/src/cryptonote_basic/hardfork.cpp index b080e9811c9..1ff8ce357f1 100755 --- a/src/cryptonote_basic/hardfork.cpp +++ b/src/cryptonote_basic/hardfork.cpp @@ -45,6 +45,7 @@ static constexpr std::array mainnet_hard_forks = hard_fork{hf::hf17_POS, 0, 742421, 1636320540 }, hard_fork{hf::hf18_bns, 0, 2986890, 1706506200 }, // Monday, January 29, 2024 5:30:00 AM (UTC) hard_fork{hf::hf19_enhance_bns, 0, 3546545, 1725514200 }, // Thursday, September 5, 2024 5:30:00 AM (UTC) + hard_fork{hf::hf20_bulletproof_plus, 0, 4939540, 1765105200 }, // Sunday, December 7, 2025 4:30:00 AM (UTC) }; static constexpr std::array testnet_hard_forks = From 5d7a37c425d6f1f8823db69dc94a9e82e0b6012b Mon Sep 17 00:00:00 2001 From: Tore-tto Date: Fri, 14 Nov 2025 15:21:02 +0530 Subject: [PATCH 182/182] feat(rpc): enable relay_tx endpoint --- CMakeLists.txt | 2 +- src/rpc/core_rpc_server_command_parser.h | 1 + src/rpc/core_rpc_server_commands_defs.h | 5 +---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 96ced0222c1..34063c84ec5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -535,7 +535,7 @@ else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ARCH_FLAG}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ARCH_FLAG}") - set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wwrite-strings -Wno-error=extra -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-unused-variable -Wno-error=unused-variable -Wno-error=uninitialized") + set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wwrite-strings -Wno-error=extra -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-unused-variable -Wno-error=unused-variable -Wno-error=uninitialized -Wno-error=attributes -Wno-error=unused-but-set-variable") option(WARNINGS_AS_ERRORS "Enable warning as errors" OFF) if(NOT MINGW AND WARNINGS_AS_ERRORS) diff --git a/src/rpc/core_rpc_server_command_parser.h b/src/rpc/core_rpc_server_command_parser.h index 3f365bd6604..5ff74e646fe 100644 --- a/src/rpc/core_rpc_server_command_parser.h +++ b/src/rpc/core_rpc_server_command_parser.h @@ -56,4 +56,5 @@ namespace cryptonote::rpc { void parse_request(START_MINING& start_mining, rpc_input in); void parse_request(STORAGE_SERVER_PING& storage_server_ping, rpc_input in); void parse_request(SUBMIT_TRANSACTION& tx, rpc_input in); + void parse_request(RELAY_TX& tx, rpc_input in); } \ No newline at end of file diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index e46635a64f8..cfe4e08bad4 100755 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -2732,10 +2732,7 @@ namespace cryptonote::rpc { SYNC_INFO, TEST_TRIGGER_P2P_RESYNC, TEST_TRIGGER_UPTIME_PROOF, - SET_BOOTSTRAP_DAEMON - >; - using FIXME_old_rpc_types = tools::type_list< + SET_BOOTSTRAP_DAEMON, RELAY_TX >; - } // namespace cryptonote::rpc

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�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ò|JNNeorg9#NOQ;I6bZbY32J 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 z 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 DMS;N- delta 38 ncmex4OLPh21n21uPOL1`?^&>Vim wallet2::create_transactions_2(std::vector wallet2::create_transactions_burn(const std::ve std::vector> outs; const bool clsag = use_fork_rules(feature::CLSAG, 0); - const bool bulletproof_plus = use_fork_rules(HF_VERSION_BULLETPROOF_PLUS(), 0); + const bool bulletproof_plus = use_fork_rules(feature::BULLETPROOF_PLUS, 0); const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); @@ -11747,7 +11747,7 @@ std::vector wallet2::create_transactions_burn(const std::ve pending_tx test_ptx; const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers, beldex_tx_params); - needed_fee = estimate_fee(tx.selected_transfers.size(), fake_outs_count, num_outputs, extra_base.size(), clsag, base_fee, fee_percent, fixed_fee, fee_quantization_mask); + needed_fee = estimate_fee(tx.selected_transfers.size(), fake_outs_count, num_outputs, extra_base.size(), clsag, bulletproof_plus, base_fee, fee_percent, fixed_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) @@ -11860,7 +11860,7 @@ std::vector wallet2::create_transactions_from(const crypton std::vector> outs; const bool clsag = use_fork_rules(feature::CLSAG, 0); - const bool bulletproof_plus = use_fork_rules(HF_VERSION_BULLETPROOF_PLUS(), 0); + const bool bulletproof_plus = use_fork_rules(feature::BULLETPROOF_PLUS, 0); const rct::RCTConfig rct_config{rct::RangeProofType::PaddedBulletproof, bulletproof_plus ? 4 : 3}; const auto base_fee = get_base_fees(); const uint64_t fee_percent = get_fee_percent(priority, tx_type); @@ -12095,7 +12095,7 @@ void wallet2::cold_sign_tx(const std::vector& 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_BULLETPROOF_PLUS , 0) ? 4 : 3; + aux_data.bp_version = use_fork_rules(feature::BULLETPROOF_PLUS , 0) ? 4 : 3; auto hf_version = get_hard_fork_version(); CHECK_AND_ASSERT_THROW_MES(hf_version, "Failed to query hard fork"); aux_data.hard_fork = static_cast(*hf_version); From a362ea78f3edf68ad1cea2f980c5b011c1ddc032 Mon Sep 17 00:00:00 2001 From: abdevil Date: Tue, 22 Jul 2025 02:24:02 -0500 Subject: [PATCH 147/182] Enhance commitment calculation for Bulletproof+ --- src/blockchain_db/blockchain_db.cpp | 11 +++++++++-- src/wallet.zip | Bin 347513 -> 0 bytes 2 files changed, 9 insertions(+), 2 deletions(-) delete mode 100644 src/wallet.zip diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 314c8242ed8..518a9291dbf 100755 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -161,9 +161,16 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair } else { + + rct::key commitment; + if (tx.version > cryptonote::txversion::v2_ringct) + { + commitment = tx.rct_signatures.outPk[i].mask; + if (rct::is_rct_bulletproof_plus(tx.rct_signatures.type)) + commitment = rct::scalarmult8(commitment); + } amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time, - tx.version >= cryptonote::txversion::v2_ringct ? &tx.rct_signatures.outPk[i].mask : NULL); - //to check with sarav + tx.version >= cryptonote::txversion::v2_ringct ? &commitment : NULL); } } diff --git a/src/wallet.zip b/src/wallet.zip deleted file mode 100644 index 2395d8ea57ba4195d548d16eac06165dc465c0a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 347513 zcmagFV~j3L)UNroZQHhO+qR9}wr$(CZQHi?Zrkpj_k1(S$;l)$mDEaAQn|B!)t#!f zE=3s-P-wva8oKb$+W)ioe*y#m7vOGWYis7hpr#5108#wwYpL|#boGD%0D=CmL;n*L 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%0DVbKk`4@Z}58rRA# zpP|<~6Y7J?Vti;pru5bUJtUJNMQ6s8UhgR;42_ooDqK%+Di~GN7i|ew1^e)e`$Ch* zxOCOQkwee>QQu~L`B+-2NC9%TX~;ESb{(>HHODO;N&A>Icb}9YipkH?mmWA}m+L_@ z>IZxPcm|%;$s$V_x-OOJk;5O5HkFI0VmSm(;Dx__%143M;STK;UeSV55(e!+C*%^1 z-9Wnm7==-R8(_Hp&LkZRFjz@z3Klqleh|s9ZgLQ#8jXoyV7l%3wC62`Pot?OIL6WK zs@W1TtqZxrYk=o40fzFipCn(Y!bzSHJv2j3G8^FRjydj*X3bLg@(@UhVlIL{-914T zMc6shG?EJ*Gc{>)U#$w#2wjO!6@luae zul^gvp@#l^Mcyk|dP35EGL3ET z#d!6=cvikZT7YU$MX96%oSmr~p4uB%=~9Bj@fn)eIOUCiSc0k73KNxlS1Zhf#_Fyad zFy!|mneP;=dyuq{=GCl;u5f|Nu#{eJ1x7ILI*9xIAMz!zqoq&Auis6@KkZS|MTU^j z3nN>D9j}Cx-H_RP15il_F8`)1P@BD7WND6!;J=sJY{U=?NJa_Ba?;Vm%>CObuIt<} zPO|TW^c|1J0nATn2kwR&-NFfr_$(#3=C0^lai}pCc3&AK zpQxl9ExjTqPQB;p;(*#7a2#B2d_cyM4c|(U0PIE zk_j5@RQh{T_)rT05pMeMVXgx0)~GqhBC?{9DXD z8W8w(0+FR@~A!djfXGXTn3Pu@}MDO$68OIJ*d`G&}ccUATcB^vz@=P~=-t3MCa=vzIzYb!%o% zx^-)uB>&zde!2ox**(ms_73Tx?h^gES+ zT6k_!Y*Ga+64-||fPL}PouXYkP)szNn`8;9_F?anvX~5`XUvlm^czQ7$2`@PRmN!L zZCIJ-8J|EYH|7OsnQbNuX`+d*ilBiAsFq_!Tu3*Pwkk`2!_O-2REd>=>=5Z3^93!f2!;K+*)~eHpT1{nD{LgZWG+s}-m`VuNP&wA| z)QA2d54k~AoD-0xf?|-+4Y71DP^VMSst;|Jo^H&igo*R7vYt~gBNO}+2=vKKUJR_4 zpx9y+nL2P)tm>>2RMY*W3I0o{`B*|_R1vqKgccq`D=7shzu!+@H}UVML4r-T*|lnq zHZ&*_eko?sVazM2^+a@~5XN^fH&DoLPoc{Ar;!M47G)~j5#UxM3v_WM{a z3!H)TFQP>MH260~c|Y{dToL6hqujAb@?=z6GZcY_qWp#xqIEh1=&F=9vIaJM8-`Q~ zs=@@+bTSsM#xMjO!%p!C;H;{FYHAoM9-sAnoRoSEbdu3ic~Jm~{EjH}mAUEfbzMwD zgCvbXcZdg}8#=9Z>Pzx62y{@ae*dPUUN7^I0>ZoieMg4${=_7?Q;$nuDAQdhrW#(y zg8H8HJCANkYT7WvXS-CFJRaqXkE-=@fsPezXZkJ6md-MlX*P+8W{HS9a(LdIY{Su% zh4UN_Z^;6+suG=Dn$Y}MGKHEwO<){v&t*^-MgrXKk63!WZX;FA1q4-s^5_bDdd-uH zaqEF*Nx0|(Q2#~+Kn)Rk^iVLQ@u3U~;n2}o0420t6#z>?C66AkuM+5j9_e74DnP>; znRf)g8ek#+wt-r8^JrGJNdmKmUe&z4`*>3l*6deM3bY}z+l!&viMvV3!Sq>~W5)+_ z5Uy^FQZMZYBp#Kq^Z1m2Vc7aKXGnU?1#qr)qA>E_T;4h@A)>j1xtrNgVJA)T!dNNDJFcR6JVrGh+xodZ6HOAKozb)P>G6{(`Q|@2?yNjl{2iS>|F}j5F5Ptv$;7N`cx-~=k?3+cYV=EaR{-1yy$LP#4 zvu8JI-(X1~J6B`HVFJag6qwWw1=&@Wu&ApCqmUOm3F3sGT~g^i%9UgIip>=?5}_GS z&*sIEYkpdkmTuOa&9qz`#g9Fzr^oAJxQwNuP6Tk6O{csA5Bh!n27yq-C>4_PWcQO* zvslbhTka0M9X2>xhe?Q5LYe#$=bM$(QN+hRl{CSz|G!q*px~c0GDeecT})Bi{wV1Y z19(T@hjnII5qv-2;vcZDKREi(VV@AIdCc0XDltJN_&+L`^?q;J3`=Aw>^6;Ua3E2i z2q>gp>5|^q^Q`Yq_)6j$3b)fnD1=K4ZLNk3?*RL;xwqVY2j#|5h2+iATvXNKHYloH zx;viR>zPP?uG9xVqHoq1!_acniku?TyUfaL9w&Jizli>ONo0w3?^SG=@VOSphPYeO zg^mXxx#{vTd+kuoS2Fy3@5GL;Gqcjts!13NFmn(d`r^r?FqYS#Vx*h^N6m{}C8u#0 zeXe;C@I9Qa<=9qkf3fBr*A?`)7f09UGXp$gBPQxQyNtkv2E)oZhmenPk^Bn^R77d+ zj&cf3yWTNak!aaP3Jr_HM}fZizsj1&5$)q){I4{l*r{h%ZW#KveJUuYY?gC6idsul zXm549jTM*mi{*<^vxo+UMc-7}-3Av+v*SI7i!?c5=`&$5p^Rdm&XKk+yaE&TTQl%u zhW1HNZP)SY)ufukxwKi=_Gsf$+oyT|q*;TbrEKiTY(G0XUTGS>Ec94i0qOM) z(XiN^yib)5tI{T&RHNks^ddV%_ZM{bByuW?GB}y#qdrnQA0wh|N|46;>~LB05j zFA_(1|6|#@*x`(7e?@P#CaLnjx=lu=P)tk>KshC>fYCe4XO&d{TIl_qOd9t6#mW4A z=XRN+{j80J$5=pER+KY_b_yp;z19XuMvlYjS5{ejcg z@fI%5o_Sf^tuz5J;%cxCd)8FZ|a$v$JNFRqFps80Xz(T)&$bZq!bnmgDHO`5kH1*=_?v%CB?8TvV{Y9@G)ax8hQZL*gaM zN8P2=h`6C;Bl56C<=;-8?MEG~+K>4}XE$}lTrEC@a1mxN+Y{qtZjTvBm`9$)kX7;I zx}PbjfcCE$60#Jr5TDPAs3_ItB%w|dQBYCjA0TcQq@~5vak_JO>B;VmHRI;9R_Hoc za4g;AH+?DEt6|-KJjjqI&8Lyhd>CEG<~$d=aDUT|;^h0Bzn0S8kc8&I=T9x8ceiNC zS$EE1<)D(ADtpNj6hyn=9Dd4#u>46G}Cni}CC)RHSJd zRNY5)O|-d7Kz5A}rQw7_mprt0Xe%~z7xrurV9nsuW?E$-@f>WyXDd()w0(X9Vila) zq=2*18FH}ya($XFG_;lGi- zvD1iN5M3aV(P*WDckC~TqMi6-?6h|7({W#N7_=I2i&_#if z17g$j-!kxh>kXjo@)8x}vC#Li3b|RPL2yNI=nTbLCkJ!@o31o9N+^Z+XexbZWoy0w zdG(ng6Vke#hY`(k*DdJbNA(@Ce5+qHIPDa+wp@$*G~if((^CrBR+#?-!XJo543y>o+$7vPTuGE9@Os z2N8`uc8Ev4Qg;)6W@ph`@neuONf*hzaHqHQf0?3{il0-ekg1DoAMMLK-Cr?$w>9ps z1GH{UowxF?QYww-D8hPQ-r)k3^2vPxjYc3nB2miot0dT843r#Cj@Q9Gg2I)pjbhWg zL6y-*l6@C2$Yd>wWgcML@zarj--LT%iZv zx7)`f)p~yuQX`99etPckWz!SlgIwzp4KSVbXKw#nyZiojSF3G|b^b*Dajv1_^VSLr zEu`1GA-`9&aqORe@50TU?a+?I{R$07$6r{Z)(iZn1V0HGSxsSgFk8`KOn0XsTEEil zpPK{msmSziIZ%LO0&f%%>CP&0w$D_qhRGL3UY(=|Rr0oGy9>_jcztIAyx^&e;1+}>6ww&(O=a6ckhqY9{(cnYbu^WmX z`$!y>?-bST^R4NS=3ZJp(yc~Xo5H%f1$WebdmQz*kG$WAt_Ys)`bjB@9!T=duyEej@^?=f@6jzsg>j;EO`O z(Www5^4vX0O!>WtHM5rKbBltAZpN@mfX?X*YxWUmN1GhOqeG|sU_|AX%{sz4J%t_0 zH|!q7ls`B;x)b^x^0B6CaJzKpiI)iK(b@N5+UBKy>dtNXMtYlPm+Cx0v#=nX5+^BN zKO$6Wu-&+5pj5N?DzbxE&r7~=Z?OjEjdyR+^xyJsJ6pRWv92=EShl%d>hNrFhdyZ5onXnVdY}CoaOhOmQ^}oM2P*@f^W}GQEx@xJ>B>)S6SncA) zUzbUCFeOJKG3HtKPR*PBgb9!hd+93)(qB!XZAPn4Csgb1$a%(hzg)ORCJ%%5w;Ybj zz9*AN@9&rY+_axOi7jjv(#{$*Xm5GKJeJcY9XZ5w_&Uk+P0_}Uv_C}To&XuiWo;!{ zyUP(>^Z-OkWw1Q+E#c$vCWz=(2eP&S1FZoPIvL~jstpM zv^QQx33RyTz1uU@NQmQ_Un6Fgnd+XBHtXfGjN-dux^30*>~Whcj)w4?L@r*T@0&(| z*lRfoSlLULFq(X9L6p04Na}?)FyUlDuxaY5-H_1N(Tnbo`f)G6t%r1OlM~(Heq-nNjvYiQ~vW&TRNQdOQr|MprkrluTTNvegiY zFRR^G%g7Lcp=LiGcAd37#n<#3{C=0FxI$T zfv9t4vUyTI{LJOOUMnBfUHj@WSKiN1VQ=z$@tB$bDN zNamNXfR0l%?g(WZmJc`%B55j6y^6$nZy)oq-H^&xCOYEdBRGvHJRhP#y%Dc3Fr>`M z5&sLypR`#tM_xiZ0x7thkbKoC4YxT`@zUEXnHR0xajj~C#8_ew2CXLKA=<$xcihay zdPv#y%^M#9;<>LI+N$3KxSYL)Us%De$5zF&Xio_6S$*6AEdbBBlnjQsts}V`xD#GN zt}@aiCJLC)zI_kDtEpcH5+mq&euN?2v4TyzE9R?J8&eAVhjwk5kfI<3mWpNH{UEPj zH!W)9eUiRLKIXD=MD$d!Z=(Uy!K`$>a|mNPAUPe~*Ub0MzJqkN3N~4_RSTo#wu0pf z&z-mw&n*@oR)v^K?AdcIfGjs!$TB(|-pUwah<&J|HID^vCIR`bblgfI*GpDvZl3Wm zE7d+Yd)N*-!a-QTIa4d$jKQ~2LWZ&nv9*>=+_e>UUcU}cUsz{TK;LQFYR--0j-%^X znk_V##&KJ53s{&_i0l|Dc_L)cxoVMjbtoh>hi@9++bgZMt0kFUh8XVzyaYi(rbOc) zpyj<@0QC$-coU{`j6`-^A5&>`s{!?y7S7 z!Pb&fEi!uw_Sso#ICPmt8@YDT0+sp-DU&}lrvv3;DJ#!j@2+kPK0}gQrL%;SC-%Di zS>D3a9i2d24`iA$m~#OMMfC>3oCNmy!-RV>q4c2Tzc6!fh3b2gW(xVt#j-6PQ~ zrb!ev0+Q@dO^uCc4d>&tBwtm6W%ahFiS}QWjS0S5Y6|B?HT#z;^&^P-Ptl1Ff`P}^ zk2E_~#Ru!|a89(*Nx@n_3+=cvV#4=TQcRjTDKb_m0i}flDdDnLDdwCdK zCTcKY=W3TldQEZtd{k3&22^tOLFcbZ&9&HjZb3SQBC{xWbTOuEg?`#Z4D+&1#b#6+ zUUUtEru9w6qg{5Z-10Xp2zI~^pL4w^W(WyD^ zH%sho9am{M+o_SDl6hr99RowX02-c`aVT;li7(E)suMFFnY||1a5H6{$)RNP1c`UR zkBv}q>!)NVK!WL?L5SukS5u#83=Q2|WC(e*0Y+r@eS_JJ@;*iNJU1)P$!j&Bglkae zKo$Lh!EbpDzU%=@Ngju}5LV(y+EBC_UdOkQ~d@4U0vyhj45ip2K;z zt&Q+*Zs@V-;1g5Bz=unRfD19tmnJn_QuGLU3?j#mcQNI)2?9K{lpKYc2VlB6c6=$| z54xVxduh2_%(mI^#>!d2gBgRl;YI;Lus_~QqeZMdeH^K`LyW4?=+;m|hZ-*QCyr|j z<)9sX8JU%nP=xcktnac1$zeiI{urS9p}_4K!y}6Gh>cA$GyLJ?xc7X0!-{vlD46v= z08dK^XYf__X4&!yDeMEYJ-h7^T70R*&LfO%Wo@g|1>}?r*@fO*x^nSpY|R%)Qqbz; z+v?o=R+uK^P=8CO1y1jbA1~#!$~5CIv=tYog=~*9V-$ax1A~|~dR{cnv_7xrGl-&| z948kcVP6he@TlX7o|Hhoxk#0P%8`x9(ha(NoB;DDRCBy8_W>PrW+yY3nl`{ZM#?&l zLrWYu(s8AhjsecCh|io{&>?nZEA*uj`b_&Y>k|-Jd-s&PiYTyA)fI_EtQrL2F@m^> z^Qu_x%4a8}AY~4=t^7M!i!lQ_c+1`HSkPs-&*RP5>^LtLF0SS1&S+!6s7RTgOEuwv z;YXfo_V5kt$`(`)61MghdJ<6E(V2_1AMs5d6YF3Mda9qBfgGUdIKjF#v?TFvJSMW~ zMYez8KB$@+CzRy1z zGpP+GWnNzinQLCmrTxObFcac_LT-$E^j812%F}Vwv!4EkvnN3+`t9JClJpw4Bm63L z{E3X}7-2KpdyFx3@F9*x*N1P8C&9-^q0R3Sc12ab;{U>R?2&RXxneExFE!PG)mH>p zp7!CtcY6+<;5<*;rR63;Esx;Kj#JOy_e7PSKp7JHNb(iRc0_?%MRPC+c@s&ZCa6 zbt3qv=_#oGi?_uOJ>E-SNt{>ezIprWb7KXe#CdAc7X#VB9yM`0_=@m zPV?=zVzUGP4g@ppvQ>rL;?DjW^M3q0G@}bHS5!CiXH!i>9Y-aI`G5G*onp6LKSrSv zG@y+fuI{xv9H!D5#dVY^ZS z`HikEVr{m&R@NpT%P65-WwSTbnNAac}sU$nL7Yg0iC%#-Bf%&uJ!;ZzHqeSWxc{=FST@d+Ba>Vsb>&dQ1t9Ypgp;5 z(-p;=um;`hn`h5*k@L(5bRz%~KLd>~FMY3IPV>B^tMv0 z#LjyGjQQ8Hy$t8{&#R3PBeapC7Lr&62byegD7xA}js$F4JP$J}>iF%OZ5U*Dp*$CH z+6GUEj|~c#Rfk_s0ZJd=n>Z)QWY%q+xKjs`D$h>Imt{r}P8!*WCf3>(befR?Md|c{_uaQ&+GGjdjB1L+xL5YO$j^S_TFA`85xdT zgUY~K&zcWfbDt?%!4(LxagZcc1B6O(4;tN;5<`=Z)$~eCq;XG9UBd-p*sSnK)WbI? z2$fUl#B60Oz1S+YW{2x)A2rf0$aRhJ(q3&6;)H6&Li|c-FJ2DfhZfH#i2wS!`8w6F z;PLpFtGmCtE-^N{dPMJ|(o^(}IQY5XgHK?kTV<~F;{F(Fl)?{UH`U(bh>n$3eeAyZ z=AR~WH2cT@zFxoFEjsAdqNAft&kpM}XSEEJBfImMBE6o~*hn|l2-&rcIbI|XmfSoXZ#tPU{f#$!6TM_H(bcs(7&}_aXuR2d zSK8$P|eo$>J~eb6Dv_{`{>YC;PYBvslGC2S$O>Ag-2+_tj(?E6?0HF zKc3a>T1x7s8Pz$FXb7F!+v=h8GKk9d=|U$%W4w5s)04ZORC@-BOfu$_`RP+#t0 znMrlcV)1gD!lArDX6<$+k5r#JbrZ_!zqdd>Yq$r?cIwb0oZcM5Tm_SYwjXV3x+>yb z>lRf)I5^rUH!B+MH;-IFZiOh2gYU7KYfBj})=md6ksUZ`uc60aA{SH)AYm8BzYX8e z)4`y+Urn4%$fSAehb3+;)n(WCxK1BVoY9RGlYGQE(3|NkA2Md~DSYZo>-1)Z+#cVW z_J;ByZw#pYNl@d1s27%Uh$?aE!SMP}%p` z$6j}bv#e^L6w~|*P4e732T;|^%K?#Ig+0vg9$#qkIekD#L(4@nR99S0x`o=80Z6fxKx4yLP&1oVj<I3+Ux4lj?C$lPFiLfh%fcvvu>%N!RRz|CE-Ksy1>fC-8-MsdEW0RB7EElP2EcBK z&RH_-q!xEAEHqb*lA}TzgJL ziBLs4{7IVspNauc21=TvLb*s&DHB;iY%i~)t6~Xh@X$$WTGNtBe+78XT*ZP+urn!b zpi|hjY58|U=M%48l&`WY!L1EJnNWtfnIRqQ>=;?DeL-HQabpKfop$2m>a)Yq(N5|* zxb`JcMU^p1U|kMDfkV=Hac@|hork_b&ASQmv5wv`k_bjB3ai#m6$dn=Ou`-O_yM!N zXCeg80Z#5Ig z=%1NqNQLG!(jC$k(pC-6mmL<0>EJZ7--{!skyPcFNT-kq{+5dZd~1b#@|G!oG<*go zO8i?JfpAOdW+4>Q8JgUPEGfDUg(%lL0Lj}su_xXmnYz1a_jmqy3$Gj&xD-t06d;C| zM3`rCPb`ER6X#4OpTO2r3#I&`pUbR)UIA-!Rs4-FQ`F5L3omu)7t1v2*dayK9HF0M)o;+uja;!y{!^8~R zDySFSraKo)r4%E1?&`Tbc&Ewknf`EtizEsOXZA|6N0u5Rfp^}~1*ji+q8y5hHwVq}Tpoiff!0wL2VH9)>xg)B~nVWd6i0A6JTy-n4| zl8}WEwELd|bfxo>*s{NmwwR9OWd}=V1}m$Pj8IZIbLEC17-`*&BXh0668k#C#YQZu zNz#l3kgiz}rds}za7^hAdMvEJp7zQWpkN5>bx}KHwL{UCCmAH#0-VS!Cxq z&{tL^_&-dk#S42C zJ=@pI{_D1VV8QzeCNMPMF3u48pDoYZMl0Zrtt04eDSJh{%3bSiVU5#MSs%}vk@dm) zsa9&!IYY zUuegns9f7Wp$Td{0zZNTyMPLWQBbJ40}fQzz`DGZs?jjlO|Q*mG{WBR6qaljKO9&_JDLKg{N$`# zprU9`7IN=^oK7>Wj5MDQp0aK}5^nfTfB^H&~V4kKLYDJ8~q z*=9HhHlv#om(-n?eAK8-H^Cu-`=?Q?unO%Wy8x&dvpix*0%aY$^Mo^cYMovUOMPsV zmitCSf&E9^rs@I3RuE`MOk>^i5l~uq7{B2M7E?V}Xz_~W9xMmCeNVQiX3Jb{2(J=} zV`G3V;MyOo)jE+n)DPJu9k9~6q&Y#=92R$=K3J+;%If!am)^PZ4$+0D0P*JN1kM6D zD0^Oo&k8sb`nmjZ#-OL-aPj7(D2Qs zq#E6NM397eVjVpYS#4nBen20VDA~wWA#8?O>)y>bYAd0h8JwN^6s|Q4dQ)h!U#n!IK0w44V*`Vob2&dL~YF~0QKkF^&-jmU{Q%+wK*bION1fij}nz-fFEwOod;Pk}L4 z7#)jivx7 zh{T73KB+JiO;sVVZekr-4@OK;k=wJ zDEjBJhZ0CWoAT=5m_jNJ$tli|4&|n^gzp-O@suPdWkdOxj(^(0*u{KKz;lRUClwei z16`FGyxkkDEDA!7_W)FIBL)?W8{So5C&Xu5^O}jL139D{d1Z4&aJbWJNLhlYBk!|7 zt=={nI;diFv$b7(_+l!OaMiSj3iJ;J7kc+!w9rNh=|xw5=w*hz%!pysDYBcc@Mc?I zQo!`;qMC0D+Y`jY#xQ8O&grI8ZD3!UE#PW=QNooFN~x+X#Fde!W*qPmZYkxwoU1E} z-yGD!&+$l}nDFgF53#B;6Hhp!%c&RJDjqZ)5BBi}VS+Yvfu3P(%mrTZ$!!yd)KxFA zhhtzqKKU|;Ee#6WWE+(R+v^b}H@BRw*#S1Y_Q1^ZRzlWkSG2MCz`=Thxr_h0RDKVv zw*;$B);KL(WP@}9l`_*jR-{LbJPz6|Clz`2IrwHRzpR^jMo}VMq4Fy7kLlZWMJDW0jh~8fjG9Dj&=@Iqb z=aB8XER4Yo+7`fRjR*kp=e)Q+I5J1cy@!t|_2Hv?_b2!lBtCrf**!UH;*(D)2rCA0 z6nzRE{)CB!`A(Z+R?5TwarP-D8v%jg8CqKf9lw`lRy3>vBSE4-4C~dk0cy0uM7@?@ z8Bk5hqixQuac{|98zSQakEtgmh0C6m+JjmJe@aAlg8%vU2|9+&HQKbS=HiVE;|(e+Vk+)>~3a){LxoDp)o8{F`I3d%4~ol8}K}CW%R5Y zy&VFOML2|Uf#^DDc1iIPFmCFz6BLXP8`)%e^FIsN?V29_V+|16j9c;@MAB#&uBIbC& zXb^`oxUyt&qR4{w31eErDC9(puS+ieD%S)uN=Q=_Ej=XVkH7x<)zjCn^Jg#q`{jcNkb4bmDskiG-(0=V>r(`hU6nSJ7EV;z z#pDx6S*wpX{9r=nry#HjSEuDk-FPu)b%XI;$w0S`R3)wfukj%bSN%q(Fgz9LIxsIE^Ol=7+KKu5&m#^OB|9t%XIq>t<)BpGV z)7NkMSqWp4!IYZAj1wDdo_^l!MT63&XL`^~iw;C3PA8()cN=?vkd-j@2txg>ZL~kv zneR|qz+m*+RybxS3l(Tt7z9<97g&=-kN#a+a0(b_)D&tvN+Re@kRqvcMk4(&Q24n{ z!CE;&{Y1n`G4-Wo3GJgo`pE3!-Uz)!;}q^?Xaa@kFR@pi!#|H2@q--Kx?M-_b)o z<0d263m(>F%Th=KP%~&rE*NeXRRWhfgEOcEda!9+QU-jyhcmv(K-4s59khcOIBeK9 z%DGS$3`%V1sxEh zGcq#ZvfI_1K)R6_M+l{P`uNS8SI@rs{>{_;>!+`uyn6QCn`bXyWaGBPM=$6`WQ%l= zYP*{=IOEqP>9bH>!^$4sPRV$X8oLxT1Q2t`_W+tm0O3Vz#u%hfq0e!6{mF*sql_xd z8>ovN`$^KO;ZCne4BSuO3+9udjn0feTU{pcKzttbHiu#6#SmJ z0_;=8-o;S{)$g*g!TAj-^{X>{Ryvq+6qm_r>eY8oj+H#Tx~Sm{MkT8#<5fl!H%LAi zp;$DD(pUh&L^0sBVGR8UhMo$NjhZOj#2W5>-faOyABVt6l6}!S1X4Ki=w4RCUE?9N zA%F7n+ixGg_>e(~RPvCkKZhwIZAuVle-o!U`^p?!K4RUp;&A_*H_{sp^?A@3{f-#2UZ^WZ$0c zz*i#@?6JiDd=6|bU zd^NuHw>NLTv-dAH8+(x5${s$<%EL=In#T%BSekZtc=)R{!{`C|4d_;cXzcG1oI?Ku zfpTQ5JyQ^~%qC%GRX3*GDGDW?nSm!g+yP~ZQPTH|GRFF4tdC*O`@%SYZ z>Ju-6=a3}DyY4F_9LRk|UnH$JE#Eamc>`&tLOFVl;6n@+l64*@YHj!FNqjL5`eUs^ z;GC{5JaKBf3aZ*+Tj1(#d&wl+2~_qIxb~4s6tR>IdnLZ+taP>(t^@_h= zRMKVT8ZDMnA!9AAa-OoyiUu-4+F51EE6Y55KQw%KUk#_LIH?F!^Yftnq*6RZEwwQqi!+(Q%@rpw)do zMbBiIaLu(O>v(NwK$gQU+Fa+ni>5Mx*q1xM5y@d*+(o0Dq#}9D+sdwr(AJ^_-1!@tXV-KT!OHH*w%iwS;ME?#BG&RM*)8 zeJEKC(v<%q!;=ct9*$v$miSL64YTGaQURdqf^@q;9*tHSj84UlEL1NNvgi}p&t+5F zcMQu7F$<$lX6gpJp4NUXR~i<_vobgHob`*Swi#t6kmgD4R`AHaqq03jCS5XdFyr=d zTzdY-?C)!znsmN?&QzpsZA>Tt#B_8HYmT`ipB+UT#SHCRH&;2o(dlBOgv>h*2Kj#D0|Enl&>0~uN=^-Y zQz57@lW4T%Sdt%`_uiSij?Ug4yJzkPQv9wpGGIRse|GHe;g8<$ceF`%tz?$XRhNKf zjVNf&s;wptXZ-0w$Vj?=nSIhkG)yp(#4qz>wvH6bI#?64ptDxg#pJ2<&fPm3@#)fK z5XgH9oo`>gc%AX{wrMZDF_@QZPLWd+ANo0KMK7YXN3hL?yi#gIV164YH++EODnq{# z7|oXSKiNrdp|2dhPDkuZ;P!fLzZ#4|fR%iHaWzQsm9P=u3w74U6tf@eWzW|12! zuE9*h>GnF8`eF7ZtOUHj{OWkPZc~ukWD0_qZ`XMuLu~3~im+Gzbaqy3deV7M>LXAt zR=pX@%^B^dNd|Tv<@U33-6dzoy^eXA8TJ5qkd0rODRF?alFj$qGVoy{@2v^HiCk8Y zJiX2g%ymW>k6{6NvQ{sE*3*^V0#{i%zyFuN-22k`zCxqa&zsPLiYu8rx^(TW#m1(* zsOl}+(7-a9F6ya4Q8S0wcFhZyc-@4o%-7~tuQtF z{{R#89hRKQU>n#n6pYp zwZi-Sd9;FTZU2D_iRQ>DG8iNh$JUWrO}mHMn=&c@v^JnWY0C2FQonJk=jF{=aTzDE zMzN`XE>~S|qndQLsfs=Xa;hw~*bqTo%z@EV5VUH^pK&spAP`hgcP6ne4_U6+H4bgcEp6=%T}jT;51kM{u- z+Hz+y+qneor%CY4f&WjhyMS~%mIO ztM8r|i>V{QQy?3*`9(p#2jmQXx@AcSax`e~WbU9w%omBanpy5k?^-e(y z;a<^N_lFgfBA_6*rpe6Z1^%x-(M7@%HtHMyNO-s%r7rRDn!K+mfvla; zar*MxlmkAVhB%4F?O`}Xmw90D3=o%Kb#fN24;R=yxv|ah^ z*|7<^y^6@HF8gnB@%TOG@W?JEs2)MO#`TQh-b1+82;S94M=^hoYfXd9^R%__EfJ!#{iGQJ@0nwktO4 zp^LAy5YBfB>O+sG>o-?dtFc8;skIE_ZfaWr#M=Ogo(6ZlX&s{^XHyD&HUqdF!*leea!`I;{%M8i9fZ06b-);b>u@;O%W%~(8I=f zUUFdYv4gbnT)0PY>(mSC6qY2K?6WT9ULq*TqAZ%Ru93dvZGEyCn`n%ly!R;fTZbF8 zadvck0U4~>SV_=5FLS=!Pgb)a)ulxLQ^+>mbN5*4?Jj0Ly}o+>^2y&{=U+X0A+(&g z4DMDyE3tEp1O-_H{*XWHR{tURlZ^)Q$L6;#{!YlpXzdb@ShcZUM@@vyDAmOM8t2(x zr6&|xZ1I!ss=V3^oL}#yab31)J@X5;z^1&&Ws=1d^bRSx^|zjx*->GtSXhf+0s9Ot z9I!R}dQSpFSiCwzlKh!OsL+JF#^gq99L_<%Z_xrGXQU6ZEvq=N1e)#c**0ZG=qx$h zGQ$9OQjbBHwq@OplE|F`m`=bEG$k;lbb@*;$WsypL`)3ob~O`tvv-sFLP;f27jXHs z?la)LNkxHR=*B6)y>gMQx*qU-?;!m+FbZD>bnh@;hvK5MBpB8UW6o6dgqRZ-bRFH> zt{wcAjGNJcGhvi@V(|5znQ>|!LUR12t$hD_5k6xnmhIhanS>~5(sHe?%I@A(I7h~o z+SKoEKnA$bdKgKY^eu^uj^k!^QVUd}X;hUF=5p$`zS<&WFgJ99hUg4FM(a)DiGf)N zqle23BW~j^o0^GNm;o+gzjE+Hc^;Xd6i-@3)BsR?9`*MhxnE3-t*H41$V^|AeW7DzCxvV_lw}CR%wz*yw}-n5q#Da3>wT~D2UHAh$~R7$Sa~ldfNY3 z0gSls^B*TyD-g! zwWPx1{9c;Yp@pOvUZg&1lg(!@;48dOU~zPt&IiC2 zoY4VvFkpw0aUD%Bh*3P>j2e;_W|4DX65C7W=SuUqXGpqlHMxB>%G z*19(fLh++bJxVyv@2@yr-df~nhTQ#!?>oogPiX@c8g=g(iASDlnF z(NdXsfpdJ+;h68xU4|0NogUdQBP$Ij2~LscyK`BqggM&08mCAn zsg4qA=9t5}Y&f{u%@!5Z;|>}&LlT*@El+_NtPMJG{$Ytg0^8LZ2<6`L+Trdpq6d2G zqn5ZNdi$vRqwOo>v@j&fS(QvIpO_}IpVkL#IAi4ajUI4rti4vNTRS~ZqN)yVp+oe5 z-Kx;O=jjA~u`__%Q~P}^eo-kUc*-2GT2&hYt{l5H2R%b1OS7y9tTfs*{3mMv^^sfP ztQ376b~Z8=kdYNmubqV3g(TVnS0he-Sd*+QQ-SFKqQsyTa(3I;ka=Idro-Yh$*|Tp zwRNx3F-GTQZ;3Uw22C&3cv_Q=WY3Njk#*(jydeeP()z4H#eon)c8y$?bo19uecCv^ zftw9BWrP4fHr94zn}@N0*+Ve3NeAmoUt7wnb&*s6MIq5f4FBg*O2y$qwF#o>@t;Ol zBGYfF!vsfZcGgBI%cV}R;xeSTb)ZJf7&^KGV9AScZg%z~vR1_9*?J^Q2x<0L-<+Wm z?M_>ql*%8+ZE3hM_|am#$BQuj4S~t($Bp)A7}b8711RF2ACo0id(n<}G*ZxGhqg3v zIFYR|4(vc0I}W>J4)`uQR7-oe*{JuUjbFu()Gm!dKbiu}pJHJQ(YG4{#p0ubqFM|f zkPaY{c+nW#q>v)H*$4_;9Aj9SJ9DjZ%&6t?*`TFna~)nP6PTUly%=PcfVtPVu`0JC z1fbo7f`q;5@7oixRqmVDvBcd>%T|8LoMNBuwL;|V5mWpguB!Y|VFyVWw>?zFNn>G+ z;Jd-4S&H_XqVVp4cGcivl-=L~VY%b^0QYwW(kr%``VOql89QaN|7P7dJTEkuWsCL+ z0kxvXYKzfv;W}B)xd2bN8(q9_FO!%5qX8t91|UJ@{06hwLx;knV=?!ZWpsdwr$Dn^ zY1sjHi!B8VW6p6?Ds#5qx8mJxI(PZ^@NQ}3T;3Oyv>UH(LF6#`;ZK)O&4}~)3BP9P zsVspjOK9t&SehiV>5mH<6;{?%chH@aVfS>*s*|&FJxcI1sQj@^NU&Wl8AKXZ0nAnvle%By(lR{Ji-=x{_30~kxonF2v?G0 z>#OgcOnkt9H4EAFie_Fs;^NCAj=tQKS8T!fh8LQ#XH=_Fx(j=%qob z+*#`Es}LPz47(VMijb!Kl_@RS)jgA4Cd!I#fIJcndmsO7SoXL*7e5O#97TI>t!0j) z30}!-O0KS5itj1|9cf;BmSuBVYMZDw*<9Sl7xigWd5|9NA^{Z%(JG+X{wgq%xUS-UcUs3xeP_B2Ed+t!x= zXJ-u@V03w@aeD$fD-=~OV7cYi;Sq>>q$(6Gp-%*{WEl9Oo|mxHziaBNYdio6hzBnG zYy6ND1GKh{O?8*_&V^weC7Rg}_kZ*>%DzAf@?3ES*I$q*{)INKU8xn+8K1XHInmLM zvL`tf$ip0N%O%6hXlzV2w8~Yn5D??ZW2^CsjE8n@RFY#e74ZqN2A(@&J7 zS7b?&nsZY*o=dxWbeI|48yx`xj|$iZafXTaA8mdNx=hIUIPm1Oe>d#D7fM1VNW5)z zm53r*^=C(k{d|fhHEM87U*!=pGltg{oG-sABxt%<~7LUb-kvLP3~$e5EX59AI1c|=ODFMR|J*~ zf@KhHhtBRWs?UK`{m+tpJn_V$n(g_;VIC-!CF#Jtu4wu={AJNrAT9Ar&Ok+$qjB6r z-ZF{B54>d&KjtcW3AW&b(7{X#_PYDa4bo}d6BijzxxjipvxJla(FY>dbBa=o^8#Vq z&b=+Q0uhWJ1Na&5*p!%0QwrjQ*PzXhe}#`vOWwGQDPDP5G#!B+20Lx;zR%B+-curL|JsakEqY|Qi4zZk1{VfwRqVpY(#o+7^!b3q|3q$fuD zwdL5IjPCx;m-H2(185h2=0V!KU-KeS)JDBbmBmu5ax`HL#vD%W;6e7c&eTWPkyNDs+*tVO|88s}}hmC?%^Q z_yU6=gn^b3T#p)6ZB%_IAAH;j8A3Fwrqu%71@VBOm{k(S$1uxY;OutJY(s{8+?1ws z42OVGY^I17H8T-_3w#8_4qagtM7d_DB-8(o+n^nX$pJv5hH1IMZJDl^6#jvrdEr8h zVI}aI1#(YDZYC62x*;@PpJN7@ljE+PVB8xD#Y7k8HEO|?a?~*&4%_M+`i6%QslX_7 z0_&Zcrnr7+UZY$rn@RThac1Cq?VZ)Ebpn|B<(8R)w|Hso;ub;hqG4^%3lkBrk4+iAJQ`CDW zg7FwwF7e1T$?gp+-1nR|$-W#$q>scG!$^dRZ|K0lk{GSo|M<4ydPdhfi&kW{tn>}u3XVrxX6=f_441Vv}q-7&?D(I{{ z8(G^5?=XbT!H+~yWTWir&|UcC3wg`VbDe-uIdF4%h#likzu>m=l2oOzM-ea{1w_X{ z_8121BUuJ|8IdMqk_o(-0y{gh`LPkK^&fFY_&1f4LsPsB<0QQs8~bMPo{U}QtO`8} zz9N7aX%b@Q{>3uaa-+-0RtI}W%KC4tWrG-SgG}&72d=C{Ls5ZMe(|bd(qVSjgD-8g z7R(9zh=h5S=N9<{0--2UlG959rG(rgpTF>%-rq%$pvB^HpY1AVfxqLjf8JHr zdOmg}Bzg0BJUd_sUOfDq;t}He!5Q8@jRM{UD4z43(~+KJmuJ=NjDroYi)uB|1PeIw zHzbu5^9m9DIXGTj&y7MGAE!Z1GupL7T^1z7nSn*FNYbfRv&D998$}Y-#pkvNcJ5C$ zD4AK96pjh*b!Y~9kd5?O%KikCS}tP;ckb!fKtB7jPaY-KU_4>}8Z(t9MGSC#)G?-S z!};VFji#F~X2iq%#jeRiGE`jd%U$JU=^cHDjq^Qjn|myd{+%|~i*@k%53^VG>^Yxx z><*v(w!4EyE<1%rsFu;2JV_N3#+0@YFttn0e1XZR|#_CsOht2&M>OJOA>wS6;;;{AQCN{pX+|T}Pff zeiLg$mLEF!%{iQk;2QPLUo3T5v|)*O>fSrp{{+0>RmNjqigO+1fQKpmb+yc{1fOM{ zYfmvR#k7ucmVF;))g{zFdoR89JZy9XMEF4(AF6%!Zn*av;!Vdd;#0vh^NpoCrkeVW zHKEV7OM{j0r?LL?k)^&8EHxGBRgP|-Ncxx;TR2qBgi92EaOXn2>x^wT z(APp9HL(xpgdckA;K-xm%hOqcYhT0#BGyMdd8>X8Ld<5_vRuyA*JHoxLq__DK764X z9LhrKmLNgh6BDoFeu_eoRd&aH=od)5!hMsy>)`iUs44VK%dQ*Hd)@^SD%?Fc0(3(2 zyG$nsx{o{E7blp9Kz?q}u-s0Q9{|8V@0|XQR000O8Tn_bGj$sLk zVEq9ArRE6$5&#?kcVTR7WppohVQg$=bTTeuaBx*r2>=67<9%pQ<9%p#cnbgl1ONa4 z00aO4006vPdvn@Iw*TLsqL-<4V1|e7BvVT$=56X6Zh#0vvv2M$YP z&b)xdVJb#kjtsrP7Bl9CVj7p(qnAurDCECkny_i;c&<0H2|=0Jf)n0|i>a3+++mRj zA3O&il8K$*FAhAuA3l1)n2o}~@n~I4>)4bhbJ^vD0Bdv#tFnpZ1750 z?P2&pQ<-LhF!4sbj5#l6J}?0RVaA_4*}z%h2}DP}?M=DJbab}P3uxw)lJNpjofQ1- z#g66$QtinO(}FtTD4p^kv9lekVKYL^2#s=TCtP^8A1`z-_e?I%iIK_ovdY@rBLos8 zu&12ZqR$KdCn2m#K#T>D+!7B)1pyS&P{g1fMr=56=J1U?YxHQEEy=$Dz)l$X$Y7gLNCdf!VyfY4lk4uGj7K4MxJX%zpExVcr_8 zDi{D!W_&Q(1J=K8+}tQ#tcyz$ED8JG0IQAnH-;kC{tUc5qd6d<7N43>Ay~XA6W)H; zXqxy8tr(E1(feH1X#2+h-T@Aitkt+}TtUd)UJDgvhstZqq7ciK{BsH)TZOn6BdawE#@f zENdHAH|CYmZW=V(Aw(a|zEQ%~nSBB<73Gf&ki8pdW1uYqZ^X!wC70#iGV_u(S|3be zUP2fQ*3dV#ys31gRzBp>#xkub!vgCH>xF17JmZM zTfs;NwV_Pz7KL9cp2Z2DZX_#GpT|2e1+F*VO$hA7cHkv5Yc%1b`(@xqm<&&9>K}^> z5sG*d#Mv`4=LXbHN=2=p646NRzl9H$tX_mgVj}a!mc!lca;_1P1C}?9p4*IxEgW#P zBK9=(6EF70wd})2-Al_PMv;QOKV_D-CftY5Yy{V$SiTqVjR46+aNCK(a3j-NyydAS z;x$#NY*OtqoU+xOn3QOVV?Edy50YopIP}AzW1~`mL$0VY;E>H5#Wn7uN)_p}LO!&* zmr4WP>6>8V#0Ixg6BOY#PS{c8&nzk#QdK~$*qR-A&Oc)SW>waxkY~vKv;aZ6-c{fc@_|uA< zkZqLI#3<=|<5h3PoHJY=L()L)TT}nQcYTKKTpJ=c8X3 zTUm~P&i@QWZRwlFL($n?Be-M5j=(py5mkj1pvSPNPTw6S^U#}uuHH2nwRcQeUXbvy zP{AVMaI#4l-mf6jpd8^loXVX%LdM`CV1;Jk=-YZ|xaXsheZM1WRKjSP#@iUpTsoyZ z_xOZj*)HG>-j-I|B?n|0`CpfrJt;p zaoM=L8CW-+E34TU7+0vrj(!0EV&g8DEZmQzXEZGw0&L7C298f9jYE}SwL4TMzuPcXjng3(7f;F?C%r#;u~a!N9jK)_!5!p&nAi>_>%e z$+55#YQfDCJFXwv2}2)+?#4o)-@O}Hy$1RbP(7)>cF!4V^w%eUr3xMWZj>hKKt#91 zoC!WcE@FR%0e=>8tMR!HyrAot!N1Rf^d6`>Qo9?{7NIKliPR2atw#qY%;lUtpww~bNJkx3p)Q*- z)G(v_c+-CN4RnO9`F+=4^^7a> zinevx>HT3{8}iKJ9s8U9v&C-K7NVRm+tdtWmtUW#ecrT<3p+7;ecHiG+BPzjHXXER z3@2x=R#}HE}8r3->PSC&Ue5y8#fZJc3S=Ybb}IJo`I7ryWN`Y z%&?lByEbXz^%n6rEcwv5xg%O<@Dx=w)a%WT*J8XF9pD%$9Z8>)a|Yvk+GM*c70cxb^zPJv~EPG)e_^ zm6W9HBo+KQ-yqYSn-%KK4|`bV+~(&CZ?%CRw3_03V{q3qmKexTb?*;CW_%j-8qo4( zXPv93eK7UA2BJ;3*Xdm5_=!;ac10Mv`=G#nzK1ofVdi98zMf$L_6$cZh+l!MsVdqAp^9?c%PI<&GKb%t*H^9-s^q8VneJ`8{&A~eI) z^BoHJrOp17JXs20CeQ>wa8^}5TBa)mpGsDFTwjeRr~#acS}m7oy0Un#*YhN(%eEX( z6iZc&#G)RUs^DT_8B?KDP2VUki?VE5;~i!5y_KZ|<)s#qPh6DRQlJm$+C%YHmHzd5 zgh=O&2;GQs%04f93R{hz_BWVg|!%3C?(GZ$#gD9*Qsb6YPxxCAlfuwl#M=2 zYe4z8^U=CB`o(qfXg#b2p5huWWsSO2W`#=|I+V7jE-9E=NcRzeLt++6iUVSm{q`=; zeo<>{1l_Y=OUDW;VgHkdZgE|tlByb+tUvT(8KI9cS(i#J$$6!zy@+agc2Fcl-;d@( zz6pZm+&=O%30n za_!^lyr*900bP1Z-L3fSc1L6jhT>(b-{r}RMcR{BoO1`g908@l+(1>|JQw8`wFjAQ zB;G{WK}@H>3S!8|UO*Nizro$;vjiMcbn*gVOSmZJNC4o=*UWP^#hrWX`o*STl6lmI zspwqK{Y;R#FSF>Ncrv#aP5%3e8LenqTEh?)Bef+DlLVMa^4pvSp#W9xz)q9RaXUWE zqW0b^&KB0;xvUtUR{1naX7I0n{~NQ}x6-myuI;XA1I3sdWXi(wdZb*}@9lpudmFiPp5 z#ooTnVg0hUY)u+j`#~gHSolX!*pCs<^&Zrzs4IPE7dwRy5?;MTVG3Rvbima%#gi~a z>4%!o5n&Y3Y=N4?p-rb{+z~bVWD$L(cRf;`4c0 zvLKfc627uiNuZ;uci6!V!;hlHa`~giAMYg4=Ga#|VLqqx z$+N==4ZZPm#se@0u>EkN6BEzxYA1u*4=LJA$t-MgC)!KSCAWY(Y%7T!#DeEEfr6Fk zB}^|lj1FDl9zulR_oG1Nl04S5C|)5l3}@DqF4T6C`(u6>O7H39LiFQI-N!Lnekk=J zy5@Vj@p~rAEK$xal1B`P&^Z=Zz>~*N+)Gy0b2BoByhu!*6jL&b>XEIPdI7S?KcsQG z_{DC~4Ix~~3SDa$rh$`p!rao8WRxLb5&`)lT}5aF`!ljQE*xHADf8M=enL32u zzIhwSZc8Xz2{!giDjP_VTs#QV@kGw;rQJ5>)fy((97NDV<0k|v5t^bhYAnJu+J~%} ztz>E?lPj~glt|VGa3T4Y!pEUlc988xMQh|W7VT;b>*AEHm>rMkz8(z8_F~}~#76S8 zsOT4g<%5b?bylE}%b8m{bKP}3 zSq@!U1*+IY?IO^4A3JdpFZFJEsXj%}*Wy`|%>|GosE=bT&4xCvS3CkLmd|v^%T~el zs|5K)q_9)4C|}vd$87#FcZQ&(&dzAxF=AaSPAO=9+!~-l07bfuoY6(v`R0DnN$L_w zwku1^eglD|YEKc#k`?qWMYj#ui<(8xc|s?;CU-_AzZ_KN^Ix#;tEQNVBpbjSJlrPF zWrp&%-?UofjTI{QV9e+1GmX7v#o}DAReU0AC&OGe6B@18*PjbgU%vP9QRoHM5awHA z|HDpM9d^B$SNU?0oiHbzPG@>^^WAp_t8AZs!GM-E;YgVkhfn3JcLZa>AE+3Q>1Eak zp~6cRZ_iY)V6KPo5hC-(DUYj1%H)`OM;c2L)5kx#S9SHeEymR0JK3Wa2PR50^bRWsehYG@)*HM%A93;nlnnfAXB^Fy;^_*4dj6?q;y%#s8-qZZgI=_N_?J zd563f%9|yoDtf9y$C;a%?%MQgln~r?ja#N({xZExzH*@Lza9*_eQlr==_mMCpO^f+ zKamI{E)sPF!&gPfO&uaa9^?Oy2zFkIcufpDFUht*wh*P+SQ24~gymCPDt{7&f8;sW zOg|lxKjNV#=S7?enp^A9wW{q<7olDY&cakM`r&{rYf+tynvJ+xC>`c5Ir(k+m6T-% zGpUAE@#Sm3*Wo{I2$XB`qSNs@3Y6s(vJG>a?sfr~a~qw~zA6Vo4D=?TFX@ zm69LYu`Vb}KcCAr1^IqpiL(lg?8%-4U1sH=e2pF_;L4Kp=PiUU>9L-IsIr#IpYrFJ zaKMm`rhPjNMw9G_yi&--=%i00{<6#=pB6Ptj*sp}_!+b_BRfb+>FE5ySZJm#;-BjV**d zllvoukUvAfBFjaJ)EAqY7~S;b&s835zf`%%kK}J|mk@n9`MP#zTXZ3P`bM%&s9257 zy?*tv68(`kq3f7W_rt7R_5j3Z`ynRSjcD~6z~zpB_H>u*xZ;A{JFX?yW@@T2w*ht1Ej}`T83t!hCnTA%C z%xFZjPwPw)e4pe<}I)GQryg*`c2kq-SFhiW*#Z4zIR zSI5;d>))8&%IQhzCwNYtEcFX9;t}rmR8e0G3@CdkwUX%C$HJaUJK{FF%z!`Aue8D` zd3^3-y3AhuHGY9?fsigQhvkDrc|Wsyg-s8sB#FY_9-$IQ=(;p3HL5>vOA?_hxOHW( zI-UPUH!RP*~(?-Eb*4_38i9F~J@svj|-Z{la_7p>{S61tGSBB@FUinQX); z0X~GD!<-OJK97)rWF;ui5VO;13VvjN$Fk0ord`er%11~vlONPS7G54^$J40-EgezH zn6HFB=cs9Oa;ul8j#Q4BFRao};sUB^iX}SMEL3T#Q2Gyh@7mqQjVy|OzWNm~&)Q1U zqUc9vW@k&5vK(8<_{5JaTb|4uj}I?Jwxo_oHm94^V;tSzeo+qq1vI+Zl;y|lJzR-J zcH>b13WY*F;TmScVZbBuf`?HdO%@$k3tG_>z<{TNY=$?b-;f)^mhBzEOw3UA0BoUd zq_a+6mjp{NaKSUuYQ2V|CD?kPN*S)4Y^9ul74iU3mV~VEzEr>};J2O=cvE@tz^dGx zOkB6E@95wb6g)l7F5%Sop8aE~xhdn~JRO#2w1~2d9&5rT+IJmL{o}0AXtZV30P+zR&Yo2ep$yyWG)Zx=0h$MQ#e3 zsoK(=(f3Y^=B1Hk)gEpWt*HnC@BtsdcMphai)e=D+Ay<%Hg&d%E`x>&0->(bENyxT z8*`D5^2wk`KfLZF|2KIz-2Ug|?f>(h|K8vJudV*}`@7wpF5cML$}#)LQsdbo)Ryg| zvmAM{^LWw!T~jVP2nVco*x5mEczqEmH4@A>)%ld!<>hP$A!^2KhgMom#U_05HExCt zqeI3-#xOA1TR9&cX|eB_dQ*qpeYI&zLZF@bA}wg%?Eog?a_t{>umbwPO8|TTug?XY zZ>6~eoVTh@<-*#)CAX^1*A%SJb`+$n(mzOPWk>S8S72x7@Zini%Y#F;V_*#nJU#de zy*JQ=1%7<|^TCrpKYsR7m+{}c&<}N8BE3#F)@&B83?v) zSNrOLWew0)(qJ6f?dnA*6H3h#J%;F7fHgCW$Yg~VJu58eK}aQcBWITvtK;toPgF;j zy_>{}Rd8i-A?img8{Jd)+Ya&Pr$3te3CbLRRnsH5??FzRNgohK(c}ZInMfaKEQau+ zSwls|w+eB+^4N|i#N7!RW(jP&R-teOG!~n-YXJ(=>`)VQ)xfY_E1R%a69Iy|L9J2W zn;qFs^~%$_$)IX?#U8ukuamVr-`rI89n7{gNxu)ecR7zezN5Fo))+%?g~ZU+J^Kq+ z)aa$b2xjc-07k@p|3Vrxi+a@Uz<7zMLz7*3s{ zOcQIk)&@DLVJi}QS2>Q}C%BtQm*foM3HUpa)62YfB^AU=d;RJ)=&o4aOww-~Fpg>q z(Z;T*Ecu`|=d7rF9m;_!@ap>}>#a%cC&c~>vGiPfp6GU+e!0Nb7&>p+A&0ba?x?a%jyuxFUg5moiup(% z7#fHH(JrR{gJm&EWSwh$cr8Y!N}4LBm!lM?aZ{y^sK*yPX~!8lXE_AJ7!A?9>8o+6 zcUaoL(4;JJj;zubu3}YVeZbtPk#l*6>(o=QZ}p<-9oSQP(C@)n7SJ!OR8;@VJt2ZBiH;#>~Zu~2kpi-sM zvlW%1BF%q?K)nD@=OiksCosvQV>sz4%{(nxyRan33~}*c@hdyPqp*gAIiVoO9HBjN z^X&=YOxH=6xA#H*PWn)Y%@A&K)_dYB!|J*xL?wH8`>jq20xi_EK#`mlam6%ORLWfs zYRv}XyMfUYC!!;-B(yx$Ljji;G|Er9eW=ZM72Ts(T-+`b@jX_?P&V;1RxG&XCAF77 zS99uY1O{v$Fm7>Oj@cLl?F!P4vw&wXKR~0h_B@X?#q0K3UY7z}%I*sAj7=KBxdv>$ zrQE7+coy{i=Qv+y0(^7Cu4A`Xw808ICR!4w`As6FL6Rj3m2fLM@`^hO?LDqu2I4_U z8csaJ&Q6xa9MwG1T)>BcLSS?qySgrOG|7kKK#Pb5iFiy`hO6M5A9&9P6kv8x4v*8? z;2iFt*Syn)bZl>bycrF%WrgKJ+qa3M=0x)hHGhe(_gCSP=!St-O$?UWOx;2ou|mjt zE-n=-$KinQxXIv4It|3BO>-*iIiz62I4Y?xz&mYhoIwy}MY@La&xaYC z3DxVBd-eMrjs*@jg{A?&xqvjzk_KaklO4^!)zY`1mi}bQ<$A^Oy}RnC1BjV=@SG!N z{_i(>)4e`8eEa(4Kz~CHd52!sK`lluTL$;f87>JwfgF((TFVWMmd|i$*t3{Q z1ViKkP%rT|-VBZpu8 zUr>7oe(9qDI=pxLe;$_j^Z)=)ZfW?8+EEq);j&q>?V~N3F z^dZ#)P2LC@(#aJ>a5%%@-MKCT81x^?2k}2b5cQhV((kT2zb1UBhu%H)hX2wmOU%DP zAEh1{9dxIGZ6Yn^pZ;JK$^Lp0KbnppW)F~R4EnZa=Z4sc5@EWO*N2FC$4z%alV#T7?Kkq<3# zh(%$v6U?cCN-Cv0gyO-##PW+c=*$MtlSGXU2R2{MJ$5 zEym}^{S|9*Q{+$6H4hYMUvjXyMpEF*m&22^c3be#cQvr+!6zbWbRfD9B*mO%Yt0Co0YXztENwI8SA11N_yv~#jvd7ry8)MvRbY&xo|49rHj@GBl6O?-I^&s z63{rsU$|TSrH!`!vD?R5&U`|aY=ohQjJA?5gLrdz2LEs;_@^uOR-_twBEc*~-Z?4a zD4)(X#vNtohm6@2%Z=C~;aZ}~J;KH=+ z!?_riWX?%9K)@PuTN|DXGrZF3nT8(5sm(c!V>iulXmLy$1ZkSFML4pt)nJA#=2kIE zQUg!DjI}@eIy;zezR*lqJq3ZW19m)JBg+SeO(I!XdFu*TVp)tV zr1Y5--0OlWKq|9x05$RGz;jLrK&1O*90yjwJfh^)|MXYhEM4f!I<_eEi_XV2oZxO(#yHV z-E94Km?{W$f?VT}kCqT4zhVk{2xKOVjA(+(mRtF-aktl;6(21S!|sH&^xMfceWsm% zSuQuixQY!W;#2v}hR3Eg_w>$Asrn9B*IOK*qpqKDa5YB*b4ASVFqJeM9RIyBOHg~( zetmp`H^kb6dZl0m1qjnBLo3 z6F2-%u{ws6g@0E#3Ei5UqdkeROd=RlQ5i!hB7Rww3|-gG8Pl0~Bvs=`VZTP5bQ=t) zgo&Fm{x!|8?Mp-D4`3D-gg()Ke#^62BrLy;jsF*$>k@6A%5+Y)O`;i2(M8+OOALfswtd;blO_OSK zKBgHb4;u)Zk7i)pO+Wp|QsXy1hd@zXJKtD7gKHqXqo_o?`oSIZgkllxs;#ZeUyBSl zeR!wI-5mZ|Tw-=hxFOCf4>bc^sZcJDfJ9Kr7QdSJD$vT9W)xItGN)n7cQ*;NstE}q z#xkN1;o!9i{8i+0W>ik|Vlg<);EK$4`5@TzRvmIfU@=TIW(wQrKQF~rsMg)t$riBf zA>t8Sxd0~3x?uvgbXe%)^d$s()2*Fl^Evu~X@(AHMTxn0^c{{#NCXC(&qD)YC<+?w z?x|R5OkN_?al`TXa5e%zDlkmhejwj9j%1P^qh!GhC}-x2j*02aD+9T@tC9y> zf7(hA_^k)syAQX%<$NkoDaXtbu9gcSmi$Q;#NmmOZn&ld1lh6~i%K&V&5uifZDF7X zZ?copO~WKV(WD4-54m(E7^94hz>^1$9#so<)guQs?e49uYO!4bMe-1brzbDg+!my- z@s~?|ul}TVRPqP?@{ipv1XKI=q5l4@8P1EJ%6m_qzj^$Vb?t}6Cum|T@-0GOmIPMu z57yDMw$h*e5L6P&qo2Np4ZTg-fi&BOF%Q8aS>N)|8sDarLDTwscWoaJX*ja&ySC$9 zutudm=4HAZ=dbibF_&1;7G_)-jZdHHONl7<9hMSnp^NEXzd&3#Wc0zxI9s6YrKUxt zzQ2yEz4-MPm$M&JiCurd*$_eE9wr^Lwa0P~azHq$2uBwNhO=`GS&%ppAO$ZV)&&Y< zK{roW64XZAfXylc&-n+3ox|$*JfTJsZ|d&d-Fk5M9;|BPa^R}VxnLM@!nz%K4AI_{ zK@TmNvpLy6JCTc0S_c4kE;^Pyn$E(0c6rme`QJA=r-G8OweH=6=r(xWL^U8>DHgpVGLNp*Lq}oi2ykQ>xrn$C^Z3V(I#TL zTQhl#4K3zQ_fgx*XfnJ2-yK*6vm$WZ#mBg9I)?r92fY}7q;_9CJ^116Pf5qNme0gq zx7(JtvFO&Y987)0oA6FhzY`&b<)_?(J=Hp%>8%7S<$ImaR*fCOktD!Tq1zw;er(WK z*Of@1kwTJyHpGZ^(-&#O1K*ofF`4l>YzR_&lNh0&d-d0uI6073!AkXo__7cDD&ItI zG`F}(pgX!Kn5%C#PUQXLJ8-ZvZ@#O&JX799z3{p?zk~gZzQlJ2{$hi-Etq2LBfw-SiXzrNqz>$pRNayt~D=(Edcuzq&lRUvs~Ys zl=yCrPsmO=@H>Fyv1siZ^xKp5yYeoNfoIbh_Kdb{W*P|(d?*s}y^sHbI@3;kw`xKO zvjG_!%X*d9i(enVe);U>PvD)%V3_J);p3*&j=x`{b|1JdLX z-{Io-jj^9VG7qclSl-=#kMjGfZi))&Ni%X5CyMaF(5THKLs)3b>I3pa*Z~s&&3}rK z!3GSbUaab#_$a(@1gQFC564=0zPq86zc(F#x(-@v<8yWm-E8C~SW^;ftjigc7_=frKpt>Yb4d+(=~U5vZxgCFFhMRSR+wyUoF z<9l8#XUKDL2eV0jI@Kh5X9uj3H!*h0Rj-*_@}=Y~T;@d-^?(CG0(CUD!mwd^rB4X5 z(lF|Fba_9`A;sS%uOCA3t}`qLr|D(qJ($&+XD|S8*VRuq8C-gpjyvybf=Gb-4M5{; zXo3#RVQ2~hx9ykc>&f%-ZWtl0&|6k(8@M910n5S>MLWU9pZxjY$D>jrwW0MiC4hx6gy0D1KUZc$BT;>~zC*dX#BBkG%xT|| z^)g0HN6FC`n6A~pScOF+V+%zfDYrcaC~PR^ZIL$0B$>$APA@I%$HJ4(-Rrp*(V2NO z0|N)d9Hz%$IcP}2s%DIqig#Y8#C6kOZ0DR|G1v*Y6wJYorC4DLelP4x!3Lf+8K zNT{Ert%JJ;MIMAyFpnr>gVZSog{V2`!QV){>+OE>cMf{L(@@<0@izJVv;iw3K5p^G zL-a-00palZL;XfyTtSv?FPDIKS)pHm3HmX_AtB%`aXcDp#v1Yi_}7T~ooWu1>W0Oo zmCAQZE={YTr^yR-{s2v@FkB&TjA@$BxX_w-=QedM#+m(Gk9K-WCBnsR2V(aL7}NI0 zem1T_-U_*n=EG6Cv0=XwangtFhKQ79UgQNs2gpSX-bbw{p z*f<$Zr^CTK8=a+^50$?n76>jk+$Kn;^W$U9FzKuAIA6lHEl-DFTV{S~JWRVgG@Gt; zBEAzVft?z@k02Olu#EZ!3V~p#z5Rrnr*>gc>N$1MhtbMD+EXT$_2FL!Z+xca<-vbyKE0V0{{fQ=Lz8xc*+B1}Ob@lhrEXv_99*SC5CH%1{MD16-wb{^ zcs+Rf_-~-S_|qTj>S+vz$JrPI4Uj2HHZ#T_k&*Q(Jqnn3Em+tVG%nlXC{Wv)C{*Ei zq#_ggy?ppCQeZnaq;2)!z1rdE3P&j=0g#m`79gtn(CDE0PBT=|-b^6BONXb4CN@p0 z-o>O#(3!q(pvJ}N1@0yQfO%h!Yn7UqjAR8iz06V2%5?~fG>cq-F+U3;7a8W)Ff2+w zAUljQIeh#YoDUJH28YjH9Duk!GS&BD%Z}nOROn!++q5J^8ESrI0r7VY_1I+bxib?vC>6d6eX*$1h$y9=!SU<6jQ!k0+0x>mh!Sa$g?|_?r+dVSI;cB$m-@ zcmjbwVSeGEzIGbuYFO=ECWf-ro!H#=CdcU1V`i%taQ~Ltms73_@29 zpb3#nqa)~pMWI-}ox7QhQas5o(qeD~BE1B+TMzbC&ud}@fe$hYSn)=`#Yr~-j)}_-9Cb^7T6~lAb@o!mvy#PKk8($Kj zUCN|LByhRVlfa9Fng5nEXdLzm{VfMrW_QEHGD1ySV+}Gji5H1IO^k$O^TLbo)+SV` z4c>-m5UHR$O7UaR8rKOgoy&|Va z!N>ZZc+t;bWX7?GE{_-eVR`04s&>JOFO&p7F3(KLn8TbUaa0#gxCg+zCtg~d5E6p} zh6|;stdD30QeqP`bl(AoRziIn>`ya|`adU6vIXHwPZ?qQMPK;imu$;uCFyIU0Sc#R zh`q*vG$H`~`Q+=~4JNf|QvnnF&99h;vu0eaLAf`%?A2fqVL25 z$lIKji^M6T^Kt586C1HG4w5CgAWNqPgEp}0?i!uXLGBKOXubYywMgjb=uX^kf@Pk$ z5ySxrB-suguc|c3qSJLU(O}&Uj4pJE=?|#Trn0cC3wgCc*OX_WXder!`?_JST6PSz z3b_u5YC3(QeY2_3^K`aoix9R!swHUBLwkHBu|TE0SX#$QoL8jVQ?lFF9EP4M^mfJg z{x9D?e~$FL`up~KQTr^aaQOR56Nb{3W370 z;fm`$W7-=|E{2*OVzR7aO7IBlR*E2jGL`y|h1AR9FBE%kMe%geTzhvbIF`7HHds`pklFtl)Nmg~Ut zGeexCVc~?}eO(mvn5Y=PWR@i}MnePiakOxW7w5ytGIcLfeW4vl0|sxz213A=Nq!Oo zo?l_cf>K|H9Tu1pdQw1<4wvQ&y<*mMot+o2ULO2y@aF9=zdZk2yu2=%UxD?3q5EF{=ya+ey6qB?gP5 zKG`3Fa|xZ}jZF`uXvn4<0`q zymK|^$+SKhmOM9lw_j+c-O*LS01r@4W68JV6YLQoN;9Wzn-W)4 zaTc43FqBfaeWF1Iw>l469yx*uGm%Ykg05yGH?y1@#~Y;H6&(6km($FL`45(B9eO)nrH&2PYzUF+Nd~)7y|xt&6f>bZ#Z$| ziYiPOLilWi7}Ly+fNbckn1O?uJ-M62bs}}?5c*3 zrp}FXBPGW0Q!@9}TMf`RVP0jE-}BF}-W5FtNnI|w0x>?wwgQ|a-sUi~I;OUjn&LAfDrZU#>R2WyM2BD>t@+D|eY`UkB(1K8qM;`4=o{69jmGMewMI^f zCD)lUm!7w6jYxjVwpF7UKi3>$nD_98O~{msH*F$FT6n`|hJJ#@wH8mN!CJSHi!ai8 z<-PPdEe9%d=CrG#`C8~{tvb~tr|Z-fcTuP|oobrp-7EqMDOy;oA9-rrpq5A2)~_AQ zliGUeZEnPMYMJbuYavW+?$x#}gt;plGiaok* zMkKMA0JCkR#@6f5ase(^=fn^ z8`h{Mo)IbU{Dj#z2+?VPaG_D}h#9Wt%rzD6ahxz9wOZ?q@tF0Ci1w`&%-S!PF*tFD6Niuwr} z-qWlqxNn-BX%ZMNhQ*>p@7E+8Rfk1I9g4Htwnm9n{Get;N^-DS^6T`$(>nQP=bP>> zz2B2lw6inQ4~;d6UlZa?bao&Yc@UZ|bY(yV-igyc``8iDiD9M2}uAMCErc% zi-{=D`ApeR*2HNz0RezCEm{;;JaioPJH5_}yr-a857$8N!6JJP_KEY+&W=zq86T`= zF-an_3gz|3>ye>yU^ubc(8?pO{m71CXJpI?5gv`njrA5cjwl3!di(nMj_Q=v=!>EM zH%^SFH0zhB-l-o`wX{(?0)x-r?SbNnF%X1meLd-?Ucv2s$g51@HvQ)=^|56d`ruE( z1GEMnHJ#XyQ_skC4L3BzY@*!?e)Y6l$gRr-A-)G|1OP#`ncc_#ZwaQ|e;8_1`+xh? zG6+TWu1`%{LW2A za%vuip@+nYi!gkyO49yG-$MN7!^2-7%gP&YZ1^J$Q6I1ur(+7;#I#qOGl&OS%Ma%-+|jkcT>c0*N4st8{g`$CdjpGh=LjA$4Rvhy*{#0 z>anRjZm4pki7W)W|Mp>0tuC!Jr|EPwohKY$7QC{04S-|B7S|b-ZvqA_mA}|}o^V2q zBx-D`86!@rGQgySE$rAiQ=PyH><|q&Jj?;-mI^Z2OLMh+5CcI^K6#U1Hua3s*bJ55 z1&52bv70qcP3pKdj&v)Ni&W(aarYPLRf9uHv-rAz* zW8!CFz$TK7B+{?GD_5V7EQ3RIYBtmaT&by55)}gQYG_z}yQMt*OM86Jy(WW1Fg00E z>N}asPUenB@Hz=j_D=$K5by0qP*-vtK=(VQPfLm5cb8>8YmTDY1GrTdJJPP09urL{4y)Nc7U9sDoCKaBb!T=)E0(e3t{6Vo)I28KjLX z^j(G{yinjD@=L%DY1BrV<&~lWW~RgswWUN|zA=Gy+khk$X_2@Td)H2iz9Eq9`Rl zTh2{IYx;~y6sZmbr-UmrJZdapq^r}It%YbuxJL+)vtKAMv~X?4EHGR(>+p0^^&Ml0 zB}@oaKw*VrYKtz*c-gJ;Nxqzn@f?SOidqZ8RR!#%ZJO7kRqI^VNYGmJV~+-&DqK1W zQTgQr0r&3S*-&@Y-;atcJyt(KvP#So2aLPff*X(?Kq&nT-&(nEx&)4reKih)vH*_I z>-tSy*Xr*mX|#`&E-Ff(@Mz8!9BIkEwTJQ9alp*98fM222K|&yK|2^`~yC z!@+-HEUv=yHKBFPY(c<2EHPK+jb^ZYf>yq$ik)AH_Yx|r!?d0ZsO2Pj>JGL6VM_j) z7CFqY<3zsv97Cj_lLEbh8s?hAS8vL=#MPs)Zf@%P5Yu!bc(!g3{Q0v@lYh*DMXJO> zmS0rP+D(0$9;FjHXkChKE~KdOC6+X>u{wp@!hpuvf+VZWDoM8Czi__MwbksD`c=dj zYx~Oh-Zq9I*JYa=w?SFnsu@U~)&S8~V=p#Fp=EjcjI3hFF$HZ9fO4k0F!9y6g-1>E z^nE=>QT6wx?QwKX!gQGY>7UHS^ea{Tw5NkvYca`(haJIsP(NQ=dM@s6~4& z=LQ@iOj85uJo3=Y%P|3o4j{j;Ijw}~vS#DsznsM@Krw87K!HRXaux_!dd(Y}dO>L~ zb=zM}PV z9bHQpzQ8V#Xeu`hZkP7k!)}gsw+2LchIw#XWYq?#-59Mg`uZ!l(T339OlVNy*cutr z-+%Xrc)V~7$E)!Z-`Hxz=37hM^fD_Kfh4}uG4#UNf34{DodEOw7h`(Lk6Nb4eG#PH zZSRY17PQ@cab-tqB7^HyjymjhttI~A_y2II%{h_q)rN_s`9*ZEc@-@ewK|x)*i2N* z_OE|mp~CAZRUnt^tXNTVZ@Yha%%6|xJS)K)L@!NolCJETSgLi0i?wb(T)slxaJWI| z)!{bYn)hhFn#wVoiGRPCTJbQQOiGgmiH*I$u@%PaVwChzffun%HU(l@#cK4tFS~;^ z2edF-)~1g`>vxJIM|-xCBZd$RGQ~hrw?|3@<+L=ldO1 zmmRGpU}41=zAv`lkBc}Ey;|{XTvy5f0<}l4sAX8#0_p18Fg-B7aV=6t(4Lv-%qFE;eZqwEG&U73S&%SvClnAQt{8pU%xN zQ1YZU^PZ|=oHKk@o9=tBLE7%u+E<(UR-ch6qj+9gQBZ~nLTzpe{bW=AzPX86iF91JHYC0S(W z>7&IZO!6W}jYE?6DXX6l0eb`Pqg+K?E)`v#SWJ9TKx_jH@{e4sk}1Mb)AWLcAP}vax>$-#!H{2KnQ-` zr|Tc4cLPz^QL64E1qA7^OXnE7`K)UDesu24>U?en?O$pCQe%u-=s$E<{AjLfBp;-A+53Ov`K{ymAcy1<{~eH>WgDBr z33?ZUZ^uk-fI4IgZR0U=muzFhKLI{=8`KWSo_)3Wt&{}p^I!kV#SnrlSh4ju1;@JA zBO`w+MvI*t{m+~1L~W|#d>uUUQg!NS!nRN|ulHzq)~hlo-P#Z&qv2#&Ja8O{@Gygj zY4|#koo78$i6spp7vV*Q;lzsbK037tdx8;UtVcLsKRG<46e`(tK1p}#UjML!y*MxO z{J2e-hqZ;CJb&}}r#4OeGFg^8{v83!+NC3i6Id*KQJy{2e-kquI5%bi46hOG^v5N| zgsSan-99*2l4;ZNZG!=XmK+RN7x8vhF6R(oP9qQGkVPk5j_jn7scTkl*9Y#tv)2&m z7zH^?FGuN|5H)qDi(ns}hbfIwqpm(TILEDTV{rd7OM(Oa1yB<@)gl%GlIhhHmFegd zLUIlkm*F0kHZz(~>7OoIk0uG{xyg{cidxGfL%*TJ^dyJc0P8-M51*EPJx&|1k1TwJ zfY0?m{PlP;EKgTy!X3oG4J|JF@E|lMsMFUPNgUL1(zgw?7}KO24Wb}<3wwNn2eZa= zWHwCny$xedDl^5w|mDGbW#syMwj!8FK7NtI4C`_3PipmL; zseB~ZnP2c4$8183G^BhAOh6&hD+ii}rP8H_Wj=cc78jiaA2Bng?18OsX>-hwysaSA zA83uaZ4(OJ`gn`xp^9rAn{VH6j{+8!r}>4G*);WL@?eR<1idwbUv2R&HKv=AZJVxP z+6i`BjAjq)#O2JY9XS)WqgGi+DJHVZK#A!#y%5RNs-?5_TMi;5+pT3WX_lrKo_A3s z9fSB7_XVG0`MYK_s_=jg-ywb5MZ(aPa);!CyPwHV~Za)BpI&!HyA=n_)70 z-Mrkf-#!jXFM~3|Vcs$UVImt}S0QKXiq8GnwZ0}2!v+kf2#ba;foc6+b>(xl?7@-=rL$UItbIgvJScl-9 zl^rzW-<{h)z#eZJwQ3|zf^J4KRu&#b{0)u`gyK^;Saku649jU)^}CJ>(o#QYih>T# z+`yjn(U2H041b}MwnT$2OoM#2%2W&@>GH7x*(=E|Oc@jw~q#4Y!Y(CeN z+FwjvEVv-J&SRl=1X?6_|r6mq%Lpo5Q`(u2e(*tV?Ui18z@8HkPH8hPs%TQJ<6yJno$ zwZ(7OzrLVb->P;Uz|=Ul-0rgq;vpnr#Zb<6d&KT^V!L3AB!iI{B$2EIa(M;(o#x@z zmlDJdHKIX&bc}P=cp=|Z)f+fO5rE5aHb!bmt)Z}V)BEX5{~@p%#4XoFFclP#YWxy3 z(l-SL$5}cVm;UxTfHx>K$GfIJaCEB>{#gIO_BBk|;+s;v`WbC{{k9b6`Y6RIYlSLo z(0D)T914L1R>y6)6k;B-=4Bxd%tTjc&znZ$$4adbZ#}}NIc#j8s}MO1s)9)AQt|~j zFq6nLU$U;i=1!S_h!=M&QTFp}X7;1rp${+$Wn8)G^FA3|uV%#eWV3ho-#g`~?cn#{ zMQ(fCh?1q^7^wh>PPm8({D~!d>H^}?=W~c}nlDdIl}0>X9AMwgy}6>2XchE_ zk2s_n`};5`_5Rfk~<_>-xpON7ca* zk+hzceX`I|$_+nT=vXg*E<;whh zoG(#I#xZWwSx?Z`rsLV99T?6ZxzuY03J1@=5v}j5yL|tIGwDo&hHZ%~2n!65WcC<& zR00{LA%b)B;*x*pIeXwMHuaBR4qpE_IDDqBM@h|!AyPr=ml<)+CdC)<5DF?C4KWQ9 zG6(js2VNRa1)!{OaO)b>PJ+C4R`xiP%yI@IM?Epi<74773h8u}%|?^um~^2eYUU-v z>nln?aZCn!4IQLGDyHc;G5k``INKO8{mWN}2Rk0i=tEAI+c94cOA1*IFQndth#YYy zYFSdtc28G0Ue`mL)_Y!aFk?ju*(yrER=zAii#Y=@*o1*fpJ2dClR#xGTg&=sUyYaZ zNk*CFtw;xkuu?-48`^FilW)S(`b+`YDpz$&j-yS1vwW1Xw8&2(J2;5%teL^JKiB1->jScm8 zKbB9g;l_e73dQBVM;v1Ki*?88KbGkuz|MUmC`60eBvdK7ETrztfmrH>0UZw>5Kxjn zVNiU^Geu+xHSiah9ShQ3oes}aVz}m$)NX$;X@sz;!)Z#5Nt%TsO|kb`42Ild2PEPVd&+?Sm;o%wZyRu7NnpRUb z)z`=CIh-da8V1U6m>si98E699YMarZzDe)VVmZR344TM~aE^ejh82c8)bOaxC(A_& zq^3v_3~Eng`!c9eqEdU{TWV>8nVMSrA(E;Dkm6FE^YyNYxE0EW#tUuimi!z7Agys4bq7zNWe1IfcX2Y;7{k=sM#Vs` zrMM+_|Bnh^8dZ3aMoi!&cb@7?M^u~uV~w`)&Q{%458n6Zo-S{tHqXCeX&ePq&5t!9 zn~ge_n=#1ec*-b@tneYN7NkZQj$xzg3n%XPz3MAeT0YZT90ce_#uX`bG0ZgJivH}1 zD`G6LF6^L9#nF^vT54_}ZdU2O@}_HJoEtY--Ss!^x*`}Fp>#-&+Vw(SYM@hZpFgh? z<2K^#daf&WMpnA7n6{^wi&9@-7O;dxxv*typ&R+R*!^h&W(k7@w+M-}vWsV?k+#PX z`IJj>TcgAm$V2Rxq2oa1Eal<=0-<`8<`YcH2E!7wmU3zr0TX68rl-cP7yMXXWTnxs z!`l+}ZGg-*i18WikxgLb~J#LzB-GaU-a2Z zg1MH=(hC!Ki*ifZs;Z$SQigdgR$aQcw02Zle48*H2yg0$U3K3+G1 zy$aRiM7|qzcD)h%e!U_8(!Vw(^lC0ap5H2 zvjy+rEb{EKw&Pjkk?Sye1woP9Z${g1rXJPn0Nmc2#rJd5#&w&ouKjm2;=Md9S-F*) zS`(=zz?ZVzTCJn6#`I)rY>QzmLo^Byv(EBZ_<-Cjn=Mn1E99G_6CSjKgjAlT zRe5&dGCwvgYC-|VSTmJVF+wJN5uDR)6fI3`*eiX3EUh>JE|o@{Y?`A{s2sAOU+7Me zfb^A2$T-hSk_d2FBaFzGg=qyZrrSk&0ao0z{6gQ^E{0bnO33gG!u;wFtc?p~Rh^xn zYX&erV8>yaf-_H8#Za3iCrAgO0bB=BCgsE>Feo*2j<6^uvJDa_V2a}`ai9?vW%Vtd z1oAYv$f0xm3eFt)Kz)ZEXe1ad3k}ie%GJIT5Dd_rSd?7{A)F38U{Y%i+dxI`AFO!m z=xfeknk|w|x;sdsNuPI{J(ayz9~%8M${2L*Y-kY){rHZAKD5duIUA)O64W;1TX0I)8jhhhSm&KT(sK#s!0BXVk68pM z)>|B>7tVb8trw#w!GtoU`1-N9$9yjkg z$#aaQUTWqUbko@99km#qfx-c_GDj$HUf3GTa*4Xpiq(0siiec4 zIQxLMgxcdXz&hVpm9%pY^v=MJfU2|#I%sFFqqYcF!8sYdtwJTE+Vb0St_iHg8>V^^ zH3Lx2b?a$uv*7?Dk`O(ShQ8%Hxb`10XOHxpt#o9dC@kbaDc5dCnWCh}fdN(6is~mE zGUS)(a2j;P=oa(sxDi&joMj6nId*5FTZgxS^7j?TmR1xOn!e+;js73NYwA}fJ0C^B zuEc`#=A(I@MWpYf^LCbA&Pf?&w-DV`QI6r1=sT+8%SiNO!k&dWM9}vlpntDAk7jy% z?swD<*lU!Fot;T~nSr{0q%RYX1T6cB7-hH8S1&SZxbi^%D_gEs^;VY5y4Y4oDr@%0 zmkleu`%5sxY1%`Nb-Ox zNPg!5Fp(i#3`<+KuFL{P)$Hm34DoSxx!GM@MjOb`;Y|ODycI$5D!6x+HC5o~dV7sd z^buiotd(YAvR<(ZV~>I>HOeQT_kf@$WaWWR!G?p?$GLBgCqFIiDk|{}_aQ;LUW5VM zzlRI39gwvx zi*)z#rh}Snm53-3-wYybxGq&aakzxAwv&?cNJ-X%+2SD*MjR2{2V;7BnH5)5ub^Zs zmE5W}sxVYfXXAmoV$s0iH8N;;9O5*Vhj=&b5T+ty7nb4szGe8s0R=*94{xB3`bbbe zLAUJ?Ka3dt|G*m={J+XjZa;`E|H{Jw7#~L+-}x2Zs~E4p9qbmzO}1jmRyPQc$2|;u z$dkAq!aH_nI0Leg71ce^tL1pQaR93nq8i?!9#I;i9Yn(8kZ3=AjFE~Wcg__zTCik^ zw02fi_xb~p-PQxNy|3|9BV*TUKw^^@u9{+2Fxq5tHv(->MV?aZrubSl=$AvQy4NP^ z4$6LZqUk6IYe4!zN^_KSEH10Y>T)t`P3q_q7}k-r%h2yF2dDLL2q~to#H7aUS8E3QOObA4vNLOl2v>X#A4qA?$Wp~wANvxIObcj;<1R3Up8g+odiGQL1E6jOb%H8zanBA?H}P+8v!I1&N( z{@b#oI3F60VyaoQ!@qV~iJ6Re;wxN+nG5n|SHavml@|qU3Zv%*u%1$=B<)DS4xYho zoRrB0oZ-wfLt5BEB_cJ6%VxZWT_Y>o2u~*wL{Qil6J29H_SHNE(aULiNdhvo5vUS% zAdNH?Hjr7Bmd2r@x+eIEPZ^NbRxuhvDpn@;d)2`&AnfZfr_|xrqfL0Qg|o>OK@@z+ zGvYcFCRBE~#&zOk2Hq&0fb7;SUV#1eOSOifJuyJpENNS(XACD%bZJceJ%OmYch_WT zm1Owtz2NL?5Xo2Y^-dkquN2iAipj)iq$fP1SSftXNiQ{Vn_PkK#08jzj6gy$2EmgN zKn;t+gxeFY6d`BSVmOR`s{>ad#9))cgoCj5C_M&~muz9_Fh_$iWFCBD)ag*u;h9HV z;Pc|y%O79e&fd+B-6>Ry?ka(!daaG!jc&aTV%)Mca={(610qVRR|F-3GTmD^aLr%* z>KtakU4|yj`O5Uv(Q<*UqO;Nw1S+QX%iM@l(Q)F^fI@toe#8=5IP|1HR8$UJE7q+m z9T&8ELe;vep+K~9ne@mEa!+XN&lCX-c(daqyq(6JWKE2)Hf1SHSWxnvA7ynjfA4~O zhfQp%^WkJ!x!+Z#a%@fZA|EkvIxB)%D`}tWY)h_5BNpM{ydm z(Bew&H7&s^3iQPCjerW$-%ry>WauzzTQmJxg$xBm!vbI<+Ib-X`Xp@8ZLu?-lbQKw zR^~G>GXJJ3|B6)Qe;8T$??e$=Z0Mf2rD8L)^n!Z{wk&w1>otM*n6$a@ues=lY&ofN z3aeazQB%0|GanqzwSM}|KL0I6v9c?2=fAcg+F7F&dfqHBWi1<4o;|g zG^uLJuY)I%TipviKVbiofnN6EAMY{h7mL$jG4Cmis0yASm)Z2ja)*7ykL~@I!UkcZ zhBJWfp+HUXf;h23j(UOw<7GZ&)%#J(gtxtP;p;n=+Z@dQ5?4|6Z>~{%Zgdy@YPZzC z(uR@~rnaNUV!%9kHL8XjYwgcZY29ht=4cJRRgR|XjU2$*wxKr`#kIAMTqV5wVpfv% zqQNFxNqPJK2y4hdh!C}jbY+@Nvs}4$23j?&;QuHyEdXXfnZGv&>lkafT3^y+s{;Ef z3#yxd{fR+XY0C8(?3fxU6IwH!)!okiZNl^~Yr@omF~fXV)&M)MYYnaQS|pi+qXL90 znnF3(obnSYO}#EjHcr~QiYjznyRJH^+843z3eU-2?Ge}W58jWp_mUi13#+fHYck&R zT>MbOg5pvE6AIB`Wn@zu{2uCj`w(I$n(?t4tu0!ni%2PEtDqEw>I~yEpe-2&!3M!- z0-nv_$>|&zV6DKn5bmU#nzy3t0NG3i)~!$bUdfDLl0s!J;FWI}7vZj4J+Rn=5r*uq_HlX_F}|zbC>$U@f?;N3Dzxv|9Czqw zK>0OLEn$Ee74f?)N`h2yeNuX1I@BFS?t%dwa6)=-4{JhTZ>Wtnq26wTfGekbO|{u> z`GtZMI3@_Lm0B;4OhiWv+oEjD6$;d11L>k*ycf3kkXmHZz%nHhm{Uz&FvJPXf&K%g zm!}X-HLt9#<}fVH(VT49!?%79`7}MvFPIRcw~4s{1J#6ZE7YgaB48jFG|M^4&r_S( z3L?T#7*o4sBPo?{cINPjkjLveyl~grapjh2D#oc*#R?X02|kSij^b|kjF2^;*P09e zEiWAA3WXHS;_-4errfNkUUFe-+?_&k5BjD^J>|zXjpIGN04F(2=6rZHO=qZ7B1b|f zQfP9&aOZOfay`Uh4dbt^X9z;;#j0hq+-c_;Jl*}1`VG!P-7zh7Xe-WU@Q)zx2uGYm zkbP5Z#qk|Ix~bXfxor>Y%a~DiCG&+lU5pXzlz9FD#9#Jlu*iWRikKGZP-#zbNrs(?oC?U)K8=HV8Gxmo@(QtN7|yM79}N zg}|z6WV?{F_gH}O<$I7kX}lD1*n*jpd!}2%na1cjVMcgZsZHIw1R};eD8W30`=RS2 zQsBSP5*r!8@iB^&>%574nbr7)lU2uSAE%&*wPG0(I||9nUXW%5#ksKqZyUsn6i$cU z@_D(q>{5$p|05KjRoToWsKPWzTvChZ3gG#dw~x6QBO6fjpkKcWEGLF2|+66eQ~_P>Qoqr z@73Ii+3!09;BQ7KZchA4Dw!VU42rHt&!6#LrSo2R(sQR2dcEZPlx85W@?`;SDNas_ z>}-`+=~;F(WKhg$rQ5m>_7s$Q2P`HgP#@yhBX?*|%bH8&UVubfup|6Klx|7vSkB?` z^nD9$HnH?8&yVc-@*7H5lbYW1lWT?laSFVh8zt>zv~kT zy=s)2M{)5or5VQ+Gq>aFsySztNmkM^V5S%OR7rnyZ3hfq@R5GnId9sK5UlJ@lQM4C zzm7-P?~KLi_4~=k1p)3B9<>3JUk5QO&_q5O@5ZsUg<&1ctdEKm49gkS?YRXrlj<|p zWnZ^mZK&H3D`8B{y=-KrJ=C*c7NS$u>v$VP!fm5c-Urj#*4`s`;&~dpRLmqannAoV zCBE<42Lvt0tp?W&-Ay35jKK|R2T|~l{ zlg|N25r&qQJ$s6=m~-Pi0M=v9Q9#^u9wwb1F<*m)Pqbk!KB9Zam9US;6Sygl(}I(o zNR5-m8GVHwj$yyXn)d{>j4&b@g3W4G?`w7^9^|gCk7?*lOm!j#u{s!EHBfc~uk7C& zT_kJm1CqqMq88AKm9=+WeWqYXeP5>NECqgJB4Aiea@TWal4K< zCZFh;pF|)iFz`odtxxZhbIi#S0`Lw`Acfb0a_SI;EJp8m#wn z2&u|)7Tfrq(ZBQIV$1%nf4dVw=CR3FKfHORnV|#q_~la`#?zX9b^v+x6oV8?lO;Uv zkmo^d>Tr@lq8RCS*Ia82Ygm=Lnk{Cn4bjml6Y z5xjMLb0k*^q$cjO2Y1G5A-!;D<=&!sl|v*rWgyTdltj#+>E8H zmI*DNvc6~GBpQSG@Jfybp2T4EZOEd5BwcYAZjWbD7DFRGu78Kr(t(i91X;|B0sEmO zQ<*5&oYSQ|e<}R$Ai#+a{JU38f_&}qlL9XT>z(zq0us!X%KZ?ndUb19(NB3N+tf~o ziBFk$^FrTk8~)+<3m;RKn?f=|EA+iGc8eYQf`0Jecxb;Mmb3q1<*X#8;-2?dY{&Rp z7RP#|gG5az2^tdeY2F14f-h3bxq_p2h6{~8HDslL9IQQT4BDB+HtQIs>J^9SN8gTJ zp2BCxHd*7sWqdT?pP`L2MwJHX%-G3WOe#ld>}j)KlBsBJ`q0-+Kt-GzaFkfih1 zjSl$N0WLp=M3mP1%r3Oe3F?G`)ST5#fu~qWM((DqBHb=2QL^S6N2v$_8%h1?kO@<^ zqQcgta6trv6x|LsPUD;wcrApC&lor16oV75O7|6n_(%IbIvXH$8Q4T^NgtbaNe&fR1U2z-#1qG?s0S~`Oai}_GkEPoadVU+KihU2E--(qcWxv#0KxT4SJhTk%I=TR<#~qCbwM%Sd zQ!xPN$uT7Z^5dYZn7@SDWPQQLqzy^G-|zY&ZU|5W_g0e=j5F6SoyQiM(<#6(C!(yA z&1&w}DFEd?mNHTst(bT*9nL%5@Jyvis!TVkX6&fH4ivZA*&E_F6sq92cmNk7r-LIS zYWv&ica7m}X!6yQ1~i_9?Pf&-jGM|C*fhlYKE;4b^{BbOvt!ts9*PL|l?J@Hil}C9 zi2ID<;AjQrZ#I?_+W%`78e-q}(14L1QT%hmmmlFFTBPPa0I)GP>SZBE1 zx%G*ik};zP{FQZKJmO?}sDnMy*OeLO&ogcEL7k*05G~*v2vI;G+4+Tuy?B~0oTVp- zycgZR`ca>?ub&*s9mHm$6y_a+$m@%+X6|X*vK$I|HojJ&W#Lm}o?yErO$>toxqD-y!0j3GR^up3zbRW#mU@|!lx?WSPO-%h~ z)W{N2jdS#Z6)BR8pp*9yd~T*{iO+fELV4Y=Kvf&ph=akZ%7&BPi}@YYRn}l~GDhC0 zKsf1<8Nb0;pIjm4V#SgqVL&i|a%c=i=KxM-!>1bB z&LD3H(>btexQh&OCSO53$ z4{AR!Mp1d%?Kz?~1!v2O7;)lrP~qyd4S zlx{kuCUnu!X=+N-Q8Qo+kiY!)hz-Xt&?rJRGl^YriVm(KgO^73_9*eh7LSZcTc7MF|f###8< z4ug}G-Mgn*pYbI1ASG52#M%^}0LxLb>3d)`+<66l1ikh&vxNw$Y69(=~KdO)M5 zSXY{m6boaoJUT7(CW16?aETaUq z+KScLU9lA7WijL|d8<}Kv-3Vx1isid3WSb1j1#>w?dI1K4zI#~wA}*+ru=_Dw6#0TA z=J$W7hxhN_?^2KxL;QRR;t!UoQRUTa1j~K`c2cZ;Mizse0=dyhu<$?!ZlX96O15f= zU*HoXT0btbRL|X&o%E!cJZ;sk7Ae#d8I5$!#;JvS%`G%_-|0@-I{2DJDB?Ql*6YE; z?_vw}Fj1X1`Y4^7Nd@ti*bh{HCo-eV^1%5_%OKcvDq*hgu1r10ZZC&Z!3~S6CN44- zn$nIMGY7x5Wo+o9#vg&%hvl}dmB-PSy8^%*Xl|xh42btX%|O|3Mb~QZ-tdI8`J%L06o2keiYVI;o?qJX%Bz!RH4>i_P7Z|1>tYqVMf^Jyzk|DgP;?CSxg)@T#4pv$V`y!gy!vc3qtp zs6H?rfiy9A#Rl$9pD?4za*S?n)?nb`G@GPJtokF>4#0QOK?0Ni&+SON?_CgxcmXfu zR0y}T;)5U(8pa3r{bPA+E7Bw%TUIt}jXNW~Z|%j9?Czd=C=WmDZ9_}xf!V#$guOR- z5y!Q4u;fe00#J_3CpOr3#RULIXs*8r-+EEW&ZHM8tN0#|h`fpXo}?7`K%>ST*%rY9 z5z)?vlMG`d5nx7ka+DAN(3_#kUuxdKsGP2%&dU>7p+Ts0+vF?jz<(_S8Zm+!q0T)| z{IN<5vv2yXuU5=QJ3BvV1|NM1-VAi9=S6k4kd1rs^x%iLKSB8R$poYbG$T9SN)dk`pAZWOeawsH zOd2A?Gq3KRSJ4!i3ouk6FWZLj zYK6UkX@E4o%~BEpEb;+KdBEH|UC^ZTk1~!iTHhilxj5gA9tFbo=J4_1TMgISH-EFe zQR9Q7WqI`xJ>2noc=fYwAGjCZQ+$(zC%W)}s)|;+E&5~V+1q-jH%6*$Cxcour!<`e}4St z&j)LrBz}#Xo+K?Oe=`AhVB9(ZXTjGeB;Ysdp_zbFldTB&TC|ATt&ka`e9OQl;!A7Z zq!h7s<5e|YD_K6XM`M*`pZmThu8c6N9F6=I6=lJ#*X5gxoh8&i2@;x>Y~kVU4Xmvo z8n6E5*j|O=cf3nn2IjegBe*-2i8nacZH|$!5ao%oP;u&>5ZsvFo`Ovo*xzre1)AaS zRbSx-dZrh62!=2SZKTY_5X08fAMAryV73HflGG2a8CR-$Kdm{5C+AUcqxWVTMelTU z+OG5s{eeCpy~TN#y_ebo9ER+#ulFsyF^x#s4Vd&@dZ9zw0`*~qexnJ*3Vy+Rqaz6P zG0Zxwj8Xi;F^i2|0~6S??~WDM*a+o9XBls#rvhFk3*&m?jMuGW91vrP>)Qui-$cEM zo1tGWkEYoo*(9Xysdt+_m65;P<@==jfnt$?z#9;#W~f5l8C8z1sMW2iW1x49VJ6lR zs3i5Hm0GLD-4N4XHBN#qy~a$`=(J*s$;DO-IvDmfOWGR3IU_=02oc)fE2%WqfD{PA zXu$&u$pfK3%}yWwB%3&KL}*VhG=GC3NHn*3p}*-BQSk^RyIg##S$K%Moi2c&7Ukjt zR=lS7`dc5uM?$H@wNBM`b`D=Ze);C{6CCv~k6*tzpes&A%EkboUx?bofA^wcPn`e3 zNje*&F#X-;`@}LC8JdyPbZln%Sh^J4A#g$PSb%&DjO26^**PhDbm{&a2VQlLMt}`rY)x@cw-avC>m(hL^qoCpbyvU}*LWB(>$Y3(NjTJR#m{Az;hPu~eb7FOSb=1J@BVCjCQ49YH z!$%Dur9#>BcoH*-a?Q_Z&Cm3ja~)Xbvslmy8f-2y>YUjLC!e4eM!K;5ja_ZtUqGs( zMI`u4>>BRxl6~bSTRgV$1XRs?;M~&UJWXO}hFCx&hjQ&z&6Q!@S2W{!>Bj+b8kk_S zx~^rfK}hwQDFqwj0CZmQ39a}f+~o##Mt-sB#UMV2;WQne_3gf!xWqAnbu|bp+J{g3 zbXzu_->@i#SK_cWpSD10tOhv=om3DTcr=1sL2#W-rd~oEJc{DXhiq69>o^MNhI&Mz zJcw)XMOe_&ClXDE?rk4=5&3F)^wmuIq5iqEQ^T!!h506SW z>0!Kqydv|{)C@;`uV-_oZZ^%O0BLnFqmA6UJvSa5OJIc0av9ppE{ z%imhuKYsi2OW8PwzqOe?yy*NEyXNp4xpZ}Ndh^T8N6g4LqlAG42LJSP9yZ)@THHxq zzIu7kl_H^-Md3#fwV3xR*~&%aCoVKwz2nFvRuR8hgF#>RM$Q)%lc9QZ(264j85wY2 zRqW{*PX({r;GpV7C$`Z}6EIDRK;@q1NQ7jOMkZ&%Ld65*edu~u&LN2%hk{E(V)bnm zw*^nnkNVG^(mf3)ro%DCrp`)Xfauze^xw1yU7KeLT#ZMkqG%EzP9siyjhGrlGd8H^ z+s*z!lj@>}>IHQJFu6GD7Bx80yaDiaH%7Qx3Y))}w?DHX2`UWrDFX@TS)oU&5~~qn zN^qQ#FoP+wsqa_CNUDB?FQ@5h7@~|oP@`!Mhk=tq>Qxi+5e>+tQDohu_uKa~q-)d{wsWgMT&-|~(t+0ThRV(MYE3df1g`|o6p@=q zFGuN|^3*O0{mT68dNzs{!=M@Y+m_c4A=4z?2a7Aq291k+PR5Hg>DJ0Rwglgrv!X#Y z^zKj}jyh!`4K z8xkH-uFrCN|J0>Pbn)6?|KfC6XGRKd^YNya&UA|UwEI3Zh(bO=n59MuUqB>ch-g+? zSbSE@m74Lif};6)c64z8J{y^#F`Q}7BJD@2&)X=_aejS7CH=IIg%PdaYCU<)MK=)C z;kEa@`d}Q?tH#?Efl-|xDS8WwJZ1|8eH{*95trkdTj<)8V74GeWQ+q+kzpGm?PnT2 z(u-)bR~yNl-ld1Hp1x9LI-IHPeSfUNKrt&ZfZy?gUotL;E z5Fg1{WVQQ+8H_vTh$1~7ct3_dl(grG9Q%8vk4la%?Q@Dbu(uaED*zM>!%u1%(2&zz|SJAm_AgiG5lKGQrKlLUTjrMrU$9S^oHbAzc+DhV|f&*gOxN?_z=%=!j>63LGpgZ zmI9jqw$XkKD-A9fK|TqjoMIZ4i`)&ePaAU@!$~_mea*~cOuu>NB3U(47L@FVO@^4W<74)W{(gkTo|rs=$e{;ba`C;_px%1N5eRYF1E^gaT<7lM^( zexbO^D3PyncEnwn9$oDO1I5wa-|!TnL;|(U*vA#;EKbGofdCV!U0P1DA98RbQaN7lK0Yr$L zOmuh0824CTHi|O^2>>B`0?fftIvJ;zW7i|pgg|th6)85c$Wej_26Jr8hErX4I7&m) za%4M!fA`FLUcGu=XP^#2VV%Xf?b)EB?_va*j{qV}P+l&l9Kz1Q8x}~|KiQQSO)?0U z4oU!?uOxc^XoO#M$aB7>R|flPF?MHazq=uWmKJFlAQJ;i%+sS4P46cw`^IHUy6^74 z2MNzVNZMzjmWpG_-rzS?D$S%MY~!qiyg`<38iMv*AT+J=2<{W$iDKwfMKEPAdLwAVrj*(!4lkJ#d2F-FhpDDbd89v`q{D}Ft@0;KmaN@4`1l|f}!|?@G z_$MGG@J@HJYoj@c==An#Q0Q;`zN09Vq<5CvCjalAGR=H&SA>vpoi>n)0p2H=p=~M% z;PY3zdc35z$erjAD=Z`J4A&Q*Ua@T^C}D&`P(umIX*U*_%3_hKN1!=?J+Gb_$!Chm zz6ROxfSuv=&VhUmgY$qz#Fxy{Qkv(5Y)-SXpl(Hrf&Kj+#6sVP%g_D>nJ0SeuJpQN zKNO?Sc(zc;M)q`@DxHl*c-&~^^pdEi-n9r-dO0uH`b&=lZ$SC69t?r654#*qML|bo znA*W&c+yjLEL7LNEA-J^K6`*35+aSPZ&RB_ zy{B(cFaG&lzgFie=jeisJ&8Ye!8drbqq8Ry9jZ@&z4@X2OIVFVC1d**d zl1kJP@2Gr68MXBQ+)b@#LJQ9{zgcQp!0cr~NzC+3tqZA1@or=KFb$auCn-k-UY{|k zc>dk2kMK-G*D=>Vxge9H+N`pmMWuyvL-e$4f>&1A>68;_K%QPp8s& z`RedsN4^G&Rd- z+guNF#$J+e9U7TrqajCE)9fZE<%WHL0S`5xedgT$(dXF#nl!-Ri&8*9EWFt?|K2ZO z-+sZ>v$=ca8PK?0n9uv+;Q7;ozYd-r{CM!%yBBftN_0gT439Nt8KX*H>?6x1cN^qy$)1DIpFFQ;&GKXf&$wA*Z27F&An{!*{!(yl=I*k7B%E3@Ks!5U&A4 zx&lI=)eO89h+2ZEr#wS`z{+=jTY}&T7Tz^CevYkG3 zln|nX*!ueGIW))-a-9j5BAS8l8xaR8;5^YZ724Ztw=H&dcnel#&3?A#U+hlRXc7p9 zdC$Ts_cUu-U=-kCDW6NYJL2AO`!hz8rCXaU zYR(!qihO^NvBaotC`$$_Yeo`QNiKY=t+wytPx3=|AB#XzCc85LI9UE)Q6@U0<1qE>|V~W z(Ikr%@N`<{bG=dbtD1*i=F+a^zs7ja#RFlI=X17E`50}du4@TCsci5Z^T@tCi}VhI zJX|Na!o;R_dy4#hU>Im}hN}eBC;0{5-7$wMwoWxcEQj|V7WIM-fDT(sxYxCUWsiQ< zs)z-PL9@~{g?#+^b9geu#6m#z&?*QHdM8hy8SR5K180bm)w>H(N-_XSK=lMW^ETNaTyw80$A9qd|8y~h!X|!fQ!Vi{&-#8^+ zP`3%L*aa|>m^t0$G7XMk^)*$z(gGNqd#X3hijJQry(@%Rv`lMj@~5aD-GS0ee|>n;z! za#x6f8i-h`%xiPb;%yZGgmZO6kPt5^Ds>fDN*2aexkA9bmr_Kt&*d6=oDuX@|E{9wfD$8ixf~m}U*_yweByPzd@vrhIz@S`TG7T%#L5 zl6ZkLDT5ni$Bf5T0LMEhmM>hXcihE$xt?|WrP_q$HEWW}W0OWELNmDmX5^G?foP_g zVx=V^a`Iyn4vXM&OG$hZp9sLwbl%I?$oY2OhXDFlF+tJd(7iZ#{rc7Gr1Mk0@B|{n z&Z11@;xvQAR;hKSW|?EBwrBUv>tFU=x0lcm*ULv71l0}uSc7&S##_bQuvX{;mqavQ zS8#ERllR&?mtLuk)WCbY{7VPSbb9uxm2pI&zKQH5%q5FJAa%(C?fUodM*{Ej6QII;e`NGhM%j@Cz}V@OV9+(d;} zWj_BjPL+H;Dn@D58pN+}SfLH+-UXumd7imA*!IJ2z*VC&E)KtK(a?23qp{}vb)jNB z%Ex-KT+L*bE-fp%>EsoC1H)HvNW+mA08V8@f2%y_BI0;i(S?4#hy$$K8x)$F> zU3WTG4B{vU^K^K11lhTdGQ4wQmct>_>@ip*s0$p|I9I336EI2vdsO{XtUAO zt&aefXo$j^)0-yob*&^fQqlMs%+^PIB)x53n_j5;%;}tK;se(}K{;@y1I?@TC;4#9 z8G-egSX>QIt*f{CWO$j5$$`de^^x55g3^m<==axgnH1`C&Rr%WEU8C(>RW>}E)x)y zjC6w1elci2pNt{ahWYt{(3_GCvSw4L_o(tta{zpkA}iMEZUk`RC&HDAv|cwOTa621 z$GElwwK&IOgjT5Q3zO`*qVM)Rpubftf`<>X3VP#Mz09L;^*>u%UBR_syW882*IVIG ze76vL&==Z)Z8ynnFdD{=a*|?sgIXi-O(<)-6V&|=_2B-)KX`Y7RSDa?ENy=HYc~fg zGpm_mmHcWRxV4TvA7IITeD>ED2Rlai2c)!M*XJb!1A|xsY$=7Rfk}SCs+S8gnK1@i zV(`^aMC0XjDq{XMWO)mstzg9!SAg|m`}l&T=J-DT_I15RW2(?(SFkD|nbl?ubGH%r zFF~GimhcVq8}L=Wh=kcR0dQG?!_cUT81|4hHbvyyJeFLS6y!<9i9e+Ns*oe`i_{ z=DvCv zWb}S1Mvy!6dv#y$ME!)58iQOEbW|sk{Af54QC&1=gu!ojSsZ74s-TC@i7=lqU>A|h z%^f@qKJs7D2b}{ePfc%gbW}a`bu4ZXEF3F-)0b{3o&PStRi4lS)SLltb*~$})3xmugWSH)ohSAm^>X8_B zLAUrTY!+-+!Ndv07V~8>&&!m;*?=DTD76-@dP1gGQv#4YDlyhbRwy`y^{s@uqrq8Q z`^Qh8zCL*KW&n@QvHCHEd>+N{kr-674&k;3g$*C6fzrF<#+`=1mSC9n|16)niOJ<#`Y>h3L9Hhe!C5#q2Ppti%02c zHWSu39BXI?qZu%vpkvic2yQA#7J-_NY>3lS$ z-)G3URgL2ripJK9e@Kml_51*cUMwJ6^kbN`7#j!;$MWdZrP0~&W8{&h8vG$_ShtO} zgvLGK7_b!^(j6^EfB!uUr^?aWe|+}(&Eeqn!H@cg{PWcHq`w`k3@tg^8c3XJk1U7M4P@##yh;hBL_0EC)#7G+r@_sY&s8 ziP8#K9$VSE9|6w^tsA>gVv6syQkisv0|KC*Tnw+k4GWgT8>8;yKKz49Ja296;u1r> zEPfF`1DH-z-)X=6I`M1Q7;$B-6f17d@U`_O}^=9u#J8G}NxjvJJdU?^-Ri6;Y$NFM1)HkK8 z5|MO1Jjy270@id}|1oV9X2*O_n!HnEtRFhfxy7BtY%e zy}Q#thaw+*5Sj77`;KbYtc$CGAo3{>{i}$E85+? zc8LnyltdgsY-m&8ATFNQI69mf$mIgXvkm}D>7@#DhI*b2Ec4}UGVgB2Ejc;X)l)LC z8%>7Uv?QE16yJIcbJ7~Nz=4Y?wcn$2a$(pjdg&V-#w?IRdk3S1m-Xt+h!)vAi7}$v zD^&aN31z8qFj&4?waBRYB5GjXWp!P@4|)h35UYCEBl+53Ds_jf`K zzMVGVSCjeGXTiCE^%mNXbcyDs zI1H^Tu5aXeLYENV1DOqX3n6_Rdt38WS`2q&nSxzb{WVdG&Qqh#D zOGk7pIA374!wO0Gfcu_SN`hTO@Tig%sYaGrm0N-|#|85e?ul0B=LA0FbI{Tl{7xts z3a|VTx1+F1*XYfd7I;|{0$+OTAXY!5jMp--ZZLM1iB> z1e94S0bw+&ZOSg!m>1w5fGCx4!GSKvQp+wkv7AA}wi{W=yc$GmcR!;$nQuGR&d6mQ zYnDxwpOG1hMDf-M`XUMFpniVlrQnDo85R=3wXG#le)h#C7^p`vT0k`CMUfpX(NWG# zY{Z)?^p{hcvMx<6+PHwM8G}B;&jJ53Wb96qj){Va_|DrresfHG&Z05kB)7-GY0&u5 z^`Z}JAMOti^zrmx*CM96jRw!6CJYjaHo84F9>PGSKr8g5DmgLrG1i^MgliR$Y_{Nx zZM{|PZqx+Ref9S6m$!$5r_bITzJB(@TfAq-qw%g=J&eN(@khywPP!f6@0xe0>+}yZ zcsyE8mlF)}F8CyJpsHbayQHf~!5kTMxf}eAhpkXo3i)PD#xnY74Lz zd;>xL;rZhyKR?$rt&SSEB8pxs`^krELlAF95iR^TB8RqiWx3tFe7RNi4X>+-!7ab! zF-q-iw$$Bd)^0=uKXk=Y?Ar=ZS&D~aY>3(5^PEcj=t)C2WUeXIVU{;2+>{!@LY z{)=9cuI7IDnqK;!CmT(&`({Vj8VB>xqRJFEaaL9c{e68OCar}GQjpFH1dh&cnt3E^ zm^1Ib6FGJLzbeDSSy$YCCbh7Iq{nA zIt~*X-%of6i%NH`3~R$g1+tXLgaz_sJSI4he8|zc-))*;K-I`^i*fgZAz?5C-@MR_ za+d;#7(67|Ee7*O?Q8`MgSrxPxt7M?J25NWU2uZ7Yr+>YiK7xP24}PUVn)`HwPofY zx*tx=3i+`Oc}fRoq^NdQRTPn~zFEe$db-1n2Cec^te_td{g(pe+QfxBio9n#YKt>? zU>l24U07M*N`=#JPEUWF=;DTR;PC%rFu^R; z{0tXrzC1$vx#%*XWtI&cXjNH_YX$9k4Yyk<>IUb;E|r@Q*O-}(`I(jWMX9}?8Yx>u zHZt28i>KUpysX==EH4th#VtG>yX%`j6!|<6P7p2C%!U#>;x(=2Lsce|fuH$wS4mP} zk8(;|U?b0Zz8<{G!;Z`x@zMyPy*@4%5<`7R;~5w%xRJCA9}RQRW;?)R%$RWyY2d|J z`s$)NTjXHHLSmc~2LYk#>gTakfCu z$oTt)UZUxHy%H;8iN(V`hu}F+ZNI7vIa~8)Fd1s@KX{FKrpxSPP>>lRD4v~5)RbLb z2B_(C%nTY*SUKgFzjES`24#M{AWaCwycQ_53R$B#h)iFlp$d!fX|VGPBHBi+wPKBx z;#$^+YfuKnIclLuFCZ0YHTIjnt(2r_8nBo8Q6IwG7kXG-@;$$MXG7gpe?Ka+^jN(C zm#ES>FoMfsF&!E)3_k@T+{Zc?nN}%Fng^J7@+JHOq10V*S=#y|(B~KPv%ba!=)x!^ z8D8EBN&!?3&r|YiI?vJzfP%v;+LsV9jv}|C;g2a+V&l9ogU_!V|6QN*5NNYpVv5b< z1Hq|CgMmsPxZoFWb+jD<;25p+9_dwc{g4WMlhcf z;dN;xT^At1+*kf@c75<4Cax#Z382%HKOa2#dGPq<)4}66Zw_7`4qhC*N&Lz-h-K&4 z3h}IC7cU*uKt~c5nV1Pti|JgT$X+l%0D)g(Yfp9hv-F~~YbJmLwHz&vJrSkl6I@7V zT?Baav_M`0(N}XDOWi(cg_67ez>Hkc`GI0#WHvmgz$NFHZU$08t3F)U&0x`b(x4M& z=VX^Ojd31`?2s@QGz4~3dK&&$c0AVG_}6DIzkSH!JCZ?6((f8s#5HNB*b$7_d#RffYy2-(+X0m-iY9-8H>>O{o_dS(Cu;)LT^zhCzVV9uMa93J3 zN&l~ryR3ab*1PhYGE#OQH{~Gn^ft*n* zCE=@!aWRC03BYt26x`)TcrKdPR!Zr&?P}>$8*S(?s4&V_NH7_6D;kv)l`thBpLEnM zKvcy<)aK5Rcu{|r6g|&r(j$nbFK?5IEXOxE`;wZXUG>3ckbHvHv}&mS_nTKQRetmv zTUd@RdVF74{o+om;`y<%MfWO|;Njf`OoU#lG zp?EwVzeuM?X_2_0^{D9{mG~)LJcjVI5G#{wl1sWAdNmP52%TTtg6BW>;HyID&-O|% zi8@E;^`SShFPG3hPpp~QhXDMu8R%6Zy}M?=%f%x|`p%+NzTknJwthbI8J9EAKCA<` z(yd@_(cAn!xl#Oj_@n-fh^em!B6|&TzjRI`tE=HEUA7?3G^xJ-J_4!hYJD)>P1u4N z>A{p)ul=`~*TDdM14>BZ*Vu$0*Qgl?O#RLj9kY_oSLXjU7edyH!@|06&g&j`Np2BrEk(F z^Hi4CW* zQz0=yp&PEFXa?V|>I)04KW#_pWSm~=d%&|dzdV2Zx53kcC(m9yem?l+)w7p}jT=ti z@*p+DfQ*f9!keY~!iNd|vKb$rO00y%E9$ZLDAtYfFF3NYl$X zIH2X{X>p@+D zXn2%#GnyQa_k6yMiJSLi9a|zr`vT(PJ&%2#?613s!m>VW>gj zVTqGrk43AL6J3}*54+J@dsxg(Z{^L3+j>}jwR}_6&XfpT>zR?(m0vZupm=1EVTrbb z4eGTN>fXDj-dJG_s3V1--k15(NPbv?loXV-#}tlNl=v06ui{ZnoqdJeS&>j(u_;C% zH%jO&K9-Vp$bp3wU$U87z&APPh7W3xq8c<}dw-7w1Lrvx;8gS5!7K|R5~*s{J_ zV|zb4x8@%^JH>D|eiW$6vh#gXLHN%f-uJsO8nQ^gtR<`V49No#=y&(uM}Z6G%#D?W zXMXuFW+sJ^VWd4rRB5U>_|2aOZ=U_10}MT;Cm}yhs&lI+rt=~zr$Y*D0Od*> zkq>h#U{RPolF`x{PANLUBc_+-s8}8y>BE0m6pwWAqNi;4TMta8l7Z2cp}|!;9nG&2 z|Ak$>O0yM9#$--VEebQm9j^Qvs`E_4`~qm{d(JbLMcy6UjdNx z*p9S#5D^i?<0Wx3P$|MOI_bS1yo92df6jIFO$MLm!*R@D5F<_1m<+CEG1%H*Ff`AD zz%aLHomaJpam|EQX@<;9mIWJWm1NS62&zX^omBul@rKb8=0rg=7O>ZZ>||$Qk17IHW4SoKdWz%= zH1v{pK>>R(YFs538KacvvjzALY&08r)!3O;t~4g(dQ9vTH)9UWA1I(W+dsHjiI_6CwSTCI zhqS_%(mdc3Njbxbaq3Jba9DKe?5IwuwtRb)>(72~YO-YY?z(*M_UE-?DqdWEb%oS3zV(q4pw?r8Vf}t(f*je`!Ykq z+{7jwh3U37{vr7d=nObZbEYs38iNy;#ZS~8~PQ8 zI|QrEogM!1lv)mJR5LD#9-ECTd=PwoC(7>F=?+X3yrl?C3cPn!jeRh+p?=*5u~xwy z8*@c*?YGsS)b$I-6|eEeoaR$P6w^|I5S2|QUc7pF@VCL6x4-=I{BJc?C_sy7QYa6D z8LZLX>0(b0eti7)`QhN%%g0X+pZ(?8;ok<&Up@JGaQN)S!K=52b}Y3zINipZw?CK+ zoX=mq`uXvn4<0`qymd#)f>UCPPV3b0Cq+fM9!Sg_e;}r?)n?WpP zbG(sGVQ1&fs~-=4ef;`>>a1j6;9xhI?IVYayQR_5qbYC2F1=d~R>K#XA2W?7$`o{= z93ZeboC+1AOfgZ{e$Ve*fA2MnMt0w}Yp+{(3!`RMgTV>PPltuNdkigG8K0iZ3V5Dw z0jdY$bivvrerr7yw7%8Q`g5ntv2zt$Bxmd%Ocw+P`|;Vq^QVI!U%z@WfTqlttDkqL0k^dJjWPd$TwST9$PhIVRW*efk%!Y1~`)Yz`=U3X(W_&pDWo?L() zc*gX99t5Av+jA=e>3~ z=sQF>q>Iw$ij}_&o^7f{2xi5lqJd_|$fXP5E+}^ek}3sX52%*ZycxhMO^^7K)DECR zlTX3n!{@)cVS-=lxA zXZf;JCfsv=EIdQwuN8ptY}B-|z~H;M;Wqi@r?TjnEHzxBph zg%B<;#ySUMosc5JHHcJa&B6^_@wx)*p4?UG$STm{@)QKB`pO;CA?5Jhfo{x%d(BAD zDgCLJKdvh2VK!7gm0T#_0ULlPAMY+kk<~YN3}6ER+BHH_4%v?#7AdwqZdlMpIIP;b zP7Kl>+E7T=it%&zhp>9=3K5mW#j8h$y8aTFW{=a9XxV&{L4wp4LyHa0Z$=KW<_ z*jQ>^c%|gO=L!CCJ4sMuS^>AMeGLL{Yw>VxF+rebpw<2M;h;e{P*qJZ%NxA-2w2#P zy9K62lpz&p@WtBDR5U58fN&U#+t zCt&J0EKbO}BrEkRxZY(YsK3c(YZ11Ac4DRtQiWrG^_GMy;DvC>G=Fy7jn*L88-x@s z7dtzoAWu5~VdFbw|cGySZic2uRn z11NB)Hz^nBJWbDMvdO6|ExluRc3wPw_VVSyA+#`cT{KiUJa}`+6&9k)F?ZSRlNG_b zm{qTJU4{IO`dXtNs4KEAm{FbYDhTTo#ZCoNd3)38b9&}pqo_Qpm-Z*iy;>KTm|DBx zRE(*+Eub|jasoB>@*{yog?g(ayhalrg$!S=3z<{puROwitv^EG9<4niaYKQ+qf!XI zR1@KFlma8T) z&TO)>;2m09z^@$f;LbIcnY^NUBR4{)!f0g(Rj{@nP^@n&A`Kqh4K zpibrFE0L-m`{_+{qg%>? z#iAzUWPQZrR<3&tUe&R4y`u!vsI{ta`1;`gXeOnaqW*r;dCc*(^BEr_eN9EP1xcat zcfW7u&$X;JPcQU0J!`#e$QrHp)t+;z+}6vwLtI`)qZFGoBerXtc&exWq{)3gS7bn~s(J zbN1D>K;Sw+YFdEc0tA$Hq7v-fSf83b#u?%ak>{r3Z(9yeB%b4nNn^7urhx=qx?wdm zLpSkVR!(|j-;}`2OqPl6pLF}v@HB5Dsxu#umjR3{r?b{e7*)YMH34l@Ct8_%TP->981I zK0w8&$6!`gO5@-{$i@~u>H zoQ0NRXNh0dOfZy&^BEn!Y2%)2>U(_UysGsksIX6pUj!Ivin@f8F0O)S-b`N0*09g^ z&4@ZL@MNpMmDD zpv2$khi<*{$SK_x@9uh7=*6)Re9T5AJxUNHf=2E_IY0(Sz6Dnjv-DfUe$b~3HCdm^ z2?OP8$ZZo@sc_H`JL)df10b-KY(ya|00H0y`x+ueTrMC-1n5mbqC1;pXQ^_5CAv_2 zG@OBsV+p3$Xgxyd%|YJ;{8Tu;9=uLx<+8v!1qS#}fkFVMjy{hFrG=cp4WnByG6nHe zTE(!qcaKa|7wJ?}9L?Nvf+2ubKi22C#yEHkGh}iDB{?L$gkYexbs%++~P?!q1M8q2A-1YSfXJ#C$yM+x*sqHsGw4(d|1t#lk* zBXrIu37At8`k`GohOapi!{0l=Bm1N7FTXPqD@Ydp_YN`ApEpFqzXLS<3kPV}_(~!) zXdu8aiGwp7rN_Amv+0O89iCJqCT(nRr)~1Z?KOJp+N*0Thg&H4)hYnZ&2L4nJ{+H> z1L^3DZUk!xSF24_q^uh&ufNqsV)x(nci2dK{$=(vTu+lb@y8-mf+$6Uq66I&^4pE@kM%hZRq6ocJP`4fR*SfGMphBip{7=dc08I z3mHE&u_8F>s?emB0edL)Nd_Dr?$#C!n3J#4ok}0?tH&G0T5S{uwOQb=Fbr;FZQHEe zS2&V3YeG^Nhzja*fhQ8!{|33pQJXEzWEg1^%>i_NDY7XDB=}jU8=IvO=JOPHbpK+d zHkpyGTqf71bzU~@qbt;?7m zQTU}87~GFg!j62?o-YwJT44JgGj*HuWO3=NTB7;1O|?+)(ne}e=V8&c0WOJ+BI9VI zFVTdR9GB5JTDsC)1I7eWnsKVHP=JKUn8Hqn)ft{3yNJ&!GMgYet<=K8CwcYuuxrY~ z=ECgrASTdPYS@uzupKIEKdbnsrPPIm89h>gAQ#yreo@rVq}iEe zFww*o)2sxFzu2uvsb5c1l-B4&o8BvkTyHFYP1Xho@k@% zGlo>WYSOVrN8)~}+H@puoJ`;34(FvUMvI*t{m;YljK2RfEpk0g#-`ectQ*;2Zvpv~ zSC7=U4=Wb8ZnvkL&Kyc>{y#rHCY3jm1tULKl3?6T%;US=pi5#tG-pZ8V8h(vmd#*f zYIpq}@`dJXZ@KmQsd58)q}ohRLrVzwojox}o0}@~bW1&G(GlMqXf#8RejbVLPtLak{C?T?Hs#xDu8&Y1KB}nNx;g$Q? zd8w*2W$Pa|6-bhgv)Q;3y0m|ko`9;QS4Bh(4m|gufRn1O;7a(A5ki_uv_mjPcBj*M zi30k~X?eEeZ)G;o#p_i9t@Mws7T`=TzhkrI*+cy|(M@#e`6`j4$Ff0lIM#4c+6;4= zPJtIsHcMPZ-hXEBECO&m<8*kI+E%K&qly;ciPQ_`$k&J-!#xiuk?kU)5GN7O%Y32c zd0A#hB#Sp!qM^Gb@%3tPI>ZnzLoSKl_i+0Gna_(CZ>YWGC>;%#Y))&goMd|d){>yZ zNUk)=0@poYRk57Bhw0fab7~7yfYx7JH4H4jSM}HSK+@q|t4(Obnp?HUja6^gs=6zX z6RNW%uPE~D)sKZV(n#2ZU$|2je&P4uqxU)oL8w=4yvBJ67eB=n|Ax2xb{G939YH80 zFh0fK=JV`WWrVT@+8(1Dl?Swpv{QlHT=;{=WL3RH1ISODPn)aG^4YdgDCyf}HnI_p zRtOzbJGn!h9?X6~{y~HVMW?(0%^h~^z`3^(;YdH7Jfqs#X0Wa-&ESXp%gy`0g&m(x z*b%`5{r4O%`!PXCux@tA+-mVewmcP^6t6b<)1+-Bn_$V29;F>0lfgvG2 z7kEiAtX*c*8)HdAJiRGko1?S*GLdulS;+oJ(|sozr=`V{z$ulTB~`lad6Sc)%j2M7JIExgDlcNW7aBCB}i1 zI!BGbxD{+yGB*=9cafq8D>OA7UIM`z&gVsbiGvuM8X){drk5Y|xpu5PB-VQ_?T9f~!+oU4#y5>i_j>=~KhPsX(VP z28VY92}ZzmM=pDlnvAU}}zN@#D zCgKKQf)$RkQ{BHt1_Ov@4`*ft@r;Eg(WHu>%IP(8XzLmvxD)V=Sr$b)TTHIFdU-V) zp{BeChp8FSB)@Kt)=|cJ^jmBUuqd6?S*T)`Q$PIHUlu=nQ8^1X-?rPSkM3L|0ci)u3C`5I(@~wayjBw*N zxaCi;q>CxRgNfOUKW}D?d6xdo@_r8e9#1bafW`>DX3@;TkO4w_nS^Ho;G`0@<^}bE zLT(^^Am!v&!i6?b#Fb9_php<-e_n(et`dg6YlQffTgZ`1bQ3-2k<3S>0q9R0fn}ov&=4e7a zoIDb(?326SU@|;PCtU7$IU7OAM=+|y%Z*l^boVK)JhW|MkgdH5o3~kX${Ve||D`t| z>D}TX@WL2pL5#bG*YzRkZWxemjc|0eR3!+>CTT`ZTo(9&@4F3hNJ5ziTEaqB9;55m z>r`WOdhQ%>yvU(Uyi})qgFs+mOkXc1@aNP+kr!xFAnc}m<8aGvjzQg&ZrrmVr_g;A zm&vvQYl))bOe&53V|$X;_AxEZAlJCqarV*`&o;lao@kCw zA>*~?99bx3i317p-=WVDw9;{>+FJYN4KLPmZq8`r`Q7xUWk+t&>RH-M*X?1-9(8gG z?YZh}?b~@*S{8JbN!=z|0l=H*j~+y^i&7Gxgbe|9}SJTHrN)?8c~Rcy4@F9iLn?pogNnFPu*27e(R|V1o=p9R|F?LY8lQl{gykn zBZg5W8S5WSr<&~lW2rXy;B5m<7zU`|w1OV037dCx-WaTLyl+=qp z$VzpUFAD>!Y^Mb8Yj(QZQD>lt|8w#JjJs)|FcdvEccK2>7$9!cXD}z#9w>gC9v~14 zh53Yscr7dYx(ck(3&a2fla?RC=^cY6s3_CEZU3>GZAb8jr2i|IQ`E>#CTI_qjk5U= z#bL1_Q;UD-tLH?{pgP;N3&B*SG*`vh@V@ z)e{2p1s4Q}{=kL%W{bc0<<;xM$1e|e>L(3@XXcFZh!AuFx-g4()lYY*ysTYc=?f1v6l^ki7sh-w?16Vp(Dd%5(yO4?xlBQ)iPJty**`l+buXcv zj}7ttsf)}UWwSFuT=TP+KfX#jDBTAB!(k}JXf_}{F@4bTReSK8^@Ee}31mj79BM|HEZG7qExju=%sKz5@=f(S3c zYiV6wH`qulDd#`$xefzLr|P$*-d87JC>x$iF=WMc!Gj1zR^E~&V4tt+yf8=84_UWw zdYakoFLT%fjdc_T>HgC{JJN;mwIP@{2B|+HEjY4avXL-uO^6zzY%3HoYrl%kjW2LB zo0`-x@}}r)Ihl9}xO%JmoF!0Nedax|dws*b_%)>sKb+B>Km(i3!6F&x!Cd1Ug#LgP zrJYl&TkSAEd}-cdJ=(+-e5;!ux!8+ zvJC-m7aYjZX#o*Wq_w{5SDcbjE~Ro*0`}-#U`0kj(L1hb@i}OyfdsU|Sky2{)uxA2 zu=|3V`Eiw2G0>+eM0e0rHoMXXoq^HOezU{b%VNbsGBpM=?0YJBEBXrx>M=hH_-LE( zL=(s0f#xw}yQ*y6AsQR$79d*1Fq=g&vNTH?x$OfR0f`pPLw(m{ZUp{%mAUc5!T8^>53I|O1mtPh~zeF zA1A|Wpz0V~(@;>b(vUy%p$tT56vGqQ2uE#Bm0I^*g0GqV3cze)Tr0j}R`^PI9s+9D z&IGeF05`~4oBh+h!N@fgcK7``PR~KTD+rfu}{P+MQVSQ*(gCRpAjU zs>MJPUQsX#UAAf4+{7AGIV%Shb!&*q`0G?X=70@2(~r>kct#JK=;?^cnR-P(CVn(p z8DbrpgUcPD-o|1m)ED|TqufIunQ{Q9I(G5y{(H^ZzkPoA?9H>EGz5pQ9}j?t3|>6` z>Dd#3BsCH53`@`q+uNDV1V!O?XevZjs8!kv>0=2+moq)rkfcJB!$q{loP-FClYD16 zL$)P?9h^Xv`|VM4?N;IkO|9tu7+VdCkWn0SyJODBD*@2`blBQ*J|!F1e!*7jAVs?s zE#%x0{iY_YTY6BPpYz1CW(y)u3$0euNobC~O{@j&9=f1+d~q^`Rzr!6fFd+F1J+&RLH9TQ^suo1^K8 za|vFs27Xhd|5#>vV<(Xiab|ciCZ-EgvWg{*xSZ<^z`eqK;sTTv=c#{X4^TvT~{ zpLloUX^@qq!kW=5(_)cGrkifa8jxWD4gR3-V@VhtqbpN7dwJR$sF#m1h-dZageRck zbm5`YYP?xa(c6&)QccRBHsYT;l!-)fYQBcO7`&`xj!0HXtL1W7#PUuwM4oW(2Znr+ zoE26T;)mh6Nt}95+aQrQ!fT-Xb=f@h(Uohvn8}b7ymYB z+3ye6(%9vPbONdx%}=Eny1cM#FfXc7{)n|kiAJ10p~pz>J2fazpm}tn08)J>V)Hz0 zuSCRIDaPnDpM!!B?TE?K+_yidNW}3>f=l)%P#E4C?c8W(ulG3_qlcKt2Bq%1=Tz)Aape;@K%@v4Ix!{IV!RvV7ZR+^OU5}>zWgg*0DxScytBV zk5f~oUXg-`a})jEDzpfY(V<58!aG{^_|VjdnF~^gLqrBW!zK8g#7!~kqfk~^hIRB@ z_TY`nQriivecOu)o9C466#=VT^j}!j)2QZ|`FftXxl`4sXBONDNH+4x)N4Z0TxYI< z@hGwq{go zMzrnitkJZlp0b4i?uqs6HPb60jqCh~d^yFg3Pqie^@^^`^gawsv8~E{nl6yj#&DDu zdJj~SyM0<$w5nRN$&+l=sVCO5dUCBeVI|`i){P$Rq@I)Kvh1JStcZW`XUj1kUZP>I zAvbPhH2malRk2&kOp9XrS8LlgjrwmLHJj6L?M9ce$MMK5SLiFadTLV?-K2y8j=UJz zkYTdX)-kl$FbEtb-nrf|8YR%A#oG*xKQgBVGm}1E)xMXl0n<0&C@LLRR?%d*LbLy) zlpJ7y5|gt9fdm<;HTM?Rxa#AqTxe*UKwMwzTg_SIqho^x`_{N$qXge*tt=+=ZO!@` zu}hVwjaz=-fX!u;XO^~3;ZKaLw+6t>Rk^k5@_Kwu^P1fT30YxS($+}Ak-aulTIdXL zHrFtpa2-WRO63YrzlYr9R%cOcBkx>TX+q&Ka0q^Yy3fES+nVkhR{*f5E+`DTF$s+N zFE(+qUyz6WG~BD{2*ijeORUImO_E47Pu}u#@|kqxr^7?oEMhyqoz(&@Rn>BBKF&f0yMe9(Z z29w^eh#-YferDSA=nGOG0w{!Nn{GEZ0I93~qlwYE9eNogIXolv?`;m^-ikD4af)p$ z1aFVB3WL5+MO!glBTnJnl`QJlLxE{xJ}C{Z%>;<|RX?jK4_PUCX@3QF4O6%?l8Ju{CVk~hfWwoyFW*5$)ClR9}ccqt7%)G_cGlDb4 zy{iVBPgBW?_Y@CISGv=5O$eO*qLz?*jkXUhWMmp)9pGpXMQ&b$YznTA$=dT;G3wQ) zpq?CzlXHyXSR-&+jQdm9mFe8blRU7)Zlc3j&LFD5tPcA2fZYio!)DvU<>v@{of@}L zazho7un{2sy_}yEnzhiUM4pzAEW%tqNnJ85%jHxzlA)-D<-TYz)fM@%SSf?S42H9|gvHfSN9>ZSg}^*%8^;Pm?ZeAXGscNMQE5)&Vz?tDLRQCIw=p6uyGaCE`w`nB z28G@n_qIR7Cqy+`9-ht+!4PuzN@4Xkm4(^{s;1{+mH7=JleJY7RK~M1n@&rM8{>Qs zK|TS)sz!wMafQ}G2O;#}b?0!YLS1o%t0VJkMdF8l&eimp4piFd$+`kd^y0$#&vWqb<@;tWLnpiJI!MY0>e1mN)=tFjJx>(GKKCg22 z*B6eg96+#&jLaNDBP3wK#A|!sVj#K}9F4Q!+OlEXA@ogklFdX|ndv|eWZ+6uo;0s9 zz61nKno@e8wuJsJ-HdCl@nD$!>glT;^>{p16Uw7UOpfURC%eDFi8`?eBZ$pgR|j|$ z^eFHZ@a+KnF3qB+<6X>LC;>*3l#8L}v);UU?q8N_8!^-1_0By<DQSs~?;-H{x12ND3nCnH4Zg_zk{};lA z+KDqagVm-%j^k>W*j3>VywC^M_UbE|s7XE?-+mq=zJ44nkE1U~dX7?fyLOTx46IL? z-cz7j06wf3dS;?zqP!{>=@eZ|X)$#-$7x~ij!RARkAYl=Nh_M~hzZw&Kp19dF5SiN zzk|`1Em0}p-!DPvVZAh_;8F#nATa~`$;O6*&uB3EkotS<{$z#I1dBVeaOkl+k;gr* zPLq_vQ16%vWlLd}Qkdb}K1^12b4PP+?gj|8Q;a3C!sEp};BxBkoerk@u}$@t-?1GN zs3ban?*QYG&r>iy!yujH+sjM|CQTVmrLX4#>}mCS%<}@grk68-jdM(Ewgh7S6;~*3 z`Zb%4^NUg?*yKgVxsP#8ZCYQl{`$$K)Rc7ySzSEx z0(k!Fr@=3;pS?UBJiial4FI*7C@UcAk-MXxb`ji;9zD8&|9osNjCW!%C9vBPM{>M5 z`LF`nn&(60;BmXu--?D2Wwb;vXeBi+nJ>v*z#Vj2yp;$%VvtN25i&w2m7h`Ucwkix zGLnB zrelJ{+?c#U2$Xn`bPkIv4zXZ@r(#Vs?i3+-<+TeOoC>_OqtmVtryXT8O@GZ2ejm(* z+Hs6WFI*@Wc7twCN*Wxcm!lN(EN%jRJ0NO;_pNzMISMmQ&9G8?cu5E2@c1H+&a{7V zI$R_!?8r6$t)iVnNhO{b+uUGE`l5tz8^XFCXZ-MfQ&qzOuWTLAjUJM*Xiz>o@2O2! z>D~SJwYcHNt^ZAjQ%%&$UHe-902))B5!{I7VrOSqj83z2&GSNRMdW!2vj!QzQJP=X z%?RJjVWIZ-6%zSy8Qe{rA2>*P6Va(z=o?tREICKXjv8l0I^yKaK)!6P$i3vu#4& z%D?})M7+mc%zUh0r%DQV^{ z`@+u>{#$i};}@twW{*2;jVyItwGE@Ta~H_!mCk!B(_y(j?4=WeGjUf%dNC}VPTRQ} z=F3GnnC3Ihd<~~)qsdM6L3mjV7MHd;#NqMaG)0}*o)3z%1g(T-B4uhYO%76=YQ2A^ z_$Z9@4lvG^(@12%k|L4bCwjDg^gv=b2tFQO1q|S{C*37K}_YQZ>(Gk5L-* zb|#vgRI`8Fpqx4y@{XWL2FC`z^Rs2?$oln{QW=C8g))3Fss7U+29WWHk@&vbu5MGz z(u))=>5DNMhz}5j=ee$(u=o#Vbdy>kbP~q5#^81^P8WK{%2gJ^ObrO6M+}=;atmpt zs8j)0Nd_fRuk26?-YXuHHM=A+94YO`>=Hd{P^M)k(j3;aNq#yV&SpD1phH$g`i}wH zDwc*V_|PFJJH03v+v)+ITf0X1vaY(?%msKX4UVn`wm%1q&|<4VjlEY72K_9!%++E1jzNPrEigr6USPcw|6S@ z1PY(o5ru`*u{~{$Zt)J9Of2Cd$XlN6Ux`14th`KT%1> zr735n@2ljOv^X8kG3zpra!C2f`LnjS7aFf-5O}xHKQsY>?iNV!hl=0bvyTbv@4GA$ zu4l+Sw(w3clCHzDB>3O4A%P8;u_NA0-k&Phgz_7j8fd z9*1~j!=ck+V=0sAn~f$|X~v1!vV~<>?}&CJtEujIlVi)+=t(G6p%AfVBw6YgwYNbH z*NG*iLeFNFjwCa~$)h?2=YA-C=6iSWG!feAM&)BrZ>5cgQK==*b zRQhTFjF@!cgKW2uVZfx7@e;AShT*MIqT_zAZY*QbU$xb;$adpA61U;KW_Fa=H{n`T zvsdE`*qjWN*)dFAF+yDYkTg-T z$`lh}_C(oy0l|K$b+p~nh1n3KvSolB=XdF-&mx+{&;9E|>K})3Fe~7-2CfBAJ~+a8 zum%`ufOi9k9JN{pEPl&ppa);DYYYjpM{)?iS72@MExBug{+i5l>}g(dMl5fC+T960 zdv!Efxwp7E^{);e*-}mdZRN+Tb;$MK52hN&o5}K$v+|N&LH!;+UlhyP$ht@b<|?d= zU|mF^@?r4-x~VH~%|a4gvZwCGjaS;6t~492R49ZQe8mC?bK9E~WgTQUJH`?)wTBCH zqm(@2h;kz2%LPwA-wZ02tp6)J9)s)gvzOmKv!z*)r_|BHlSg`Xlpi4!$=qvT?7=K}N3uq46Z}Lh+V=9P%oMLJdGk+-Eo?TrG zulg3@c^_7c@bvVhvvGDT3PKx+L*q!5<|8C+d4ia>WL7;-v`%3w78>!TY*f$Euct7b zFyc;7r=#+t-@q6qKL+brig$|sLOAI?5lo@Yyc4ZTWaFN@vKvx4P_>b+=t#*;3Wx=0=aZN}-+$ZEbF~wZj5V z3D(rQE^c&pN$LuBt%FE_;oC7t}V~zE}Lrt$&KLm8cRFbq;G0hDVUuL z_t`>IiARAoSa!Z|U|D6<<#k(QgcJd-*-j5Y1al-DuQNqzoH=vg!579_6T1dH`ZHDy4Mu%2) z-ju@*{3F@kd^}(X@YWipP|MVg;`f7jS_t)S;=#!vvL2X#(ri=KFH_wdx?nOC^zt)` z?y6HBj(qjp_uP!%MF}zHN}_h;JJ2J$H?3AoC1Hc?R~bH*Rf2w~g8%SFII#*9!kKG} z6CY~uqK{0DyN6fCtZxyvtgNE;s}p3Mq9ZdGZN&8(OkJc``UBm@<~}z2@fHkq`ck`r z0w)aTj||Q`^xdxzrEq!IqEU@K(YR&%QJjnD?44N#D&x8K03qrlM4(m{0OCzFE73Y? zp(|O$#247eagqNso%w8eh}6bzhsYA|>biJO z<_K&sWIGf}X+_{~?=$XhMJh{S2OdV-!an)3Uk8);>hUjRYShfwnJ?ephdCZ{#lP>q!J z6BE`5C)86hW~Zm(9F5s$x@$Wwv-%p0NUjjuQ-oluXy3xhs1}AsJ0Lqs_SE%F$d)s( z>>Lk{Ct!H6xmh(VL$gZteFQspVpeFjTdy~hl3_Z$v>3XdJU#gF@!RKzgTucL-u!a# z^6BFro*xVz|8y|;%Y*;yspqer{Cw~fUOs;Q{ME1e>(hhhhmQw8z>7Cx+UyamNe*`l zbEO`s7mxo+#Ro6nzWCwbwH;tka^DA6I&DN}&9A;&JJ;BFw2s=}t3vmEwB(LNf(VNE z5bj47>ieL~c62eDP|cq#*y7)N&$jWI*M@(+kZAg^z|b(ZopZzINf!IY>2r<6w>{VEuU>a{xT2Xv1LB*$D zU)uh5T$F9HIyv;*2XNP+M@4cJ!IKCx^S#=Pcc^JMaBCG#$5sfQQ*mLqu#ODGQk4i& z#K99CwUv6fPW)H#skPRxUtnK_VW1X{8+M|8%V6rgp*iPST0qvmQ4R*df_s+X%PVhD zyKH}bz7aNz9WL70NZ}Ne6e|xd*_79uM=u7CQARg4mtS1<8ZIv3JN9^T7N6Kc->^)3 z5q=0P{Jc`g216tfdWsD5UhYu+xvaNp z;2i=!kC&$SO=o{Snl&UfO>A1 zlZB0WNk97lhO^@Wj06&RnqDSzx_LNHJg&ihicnEJ^&;cOT5~ab%>i7gX)^yj_)`~I z{sA(p7?mqOPQJPS4Z0@@<>b!JY@U}!Yfy!wwuk#Y-G>~Lxy;RD_g6Dj`h3vTX*xIM z>;qBAUUIO6W8C?*e+`_|h13)&7SLbC3vfeYF^pau_*J`7dfWq|F$}N%E?#>V z9aF}87%!ZS(o*=AR;x)ETqq-eSh7T90N7mHN?1i$Au~Z9p|W!+gcqW)C10kXeE@|x z25nM;hPk^#mhJi|Q=ajzF0 zmz)dR!5!0T@NzIMPn2T>HK*1w-#eN;>+n~oRXtd#o!%<(9u=r~4kul2)vP-t!kYw} zjE~Lgf@z4+idF;bS9t_%N;aJ?$*w}UCmH4pi+>gT%Zk(CV~&aN7^v4je3}yh-rT}T z0PFrg(vPrauL_zpL9ZSnEM?J@Y}cx(%BabPI=1lluEc z#}D#Rn)tDEl;KBF@4mOUR{uu*{)JDq|Aq(R{dEt(_D9glLqeWNH77;nxUFjD!V@>3 zEdM@js;3RVcDwU?KHr>B1IanQ)z76@({yH0qVgs#RWk@_9syUP>x{DF%x6BR zILZ0{MI>%@% zVnYXg@1Y?8^ky=#YGYxi9yAjAizsR~#1)a8h3nSmCc&k3XP$JKi-U%XTX-b)Pd#^x9A(VW%5@|md;Z1gV_}GlUW(_60RTo z-3WQ*f()#g@cpjsaL;wPCp+BpJKXa++!Gz{`5jt1XYgo}l^hxjZ8_z!+FNzV?eFWE z+PzUr^;aw1DD&&*0uJAqrT^Y?$JH_+FKtR}kOhNl3ozw-IzD78 z9b}tnzm%Y)c!yvIn~z!3%vt1TKtYA+PQ)RwxvBhv1sXz?Eo&E31>z>ko|10?W z;30kazYo7PUmox)^X32k=@0zn!JmRM|M$QCGbp_ONBUbDFjZ`*tKr!<@a$LM*{{Ik zUKw~CgdsfF8_}p3%f+1T2lNei%;Tv>C4xH%CKc#HLSd0RUHSoMgZVC>OO2l`YxaYA zK!PUoi}BBx&YQ;=b(z02kE5PFHBXLHTTqDO;~gL?WPX6GsZ+y7n2iH2QdP&c@*=~K zN9L84F-iBd5!?@}hiEq2X?l%e`kPA(OOOFT2I0nXClGOH2J^UMh*Yq>!Hd_7z;{_W z(zwCK>NTw+chlIvW=!fj-WDCRG_YN-8b{l-X{}IK^fFG5hs%j=(!{F%byz^tJL>I> zB9@Mo1_AGyJ+d^@|4nnqp_-kg5WPpYbe>M~xkea{PBfpSFoYt7=rSCI3fzQ$NGBi( zvgOOZi?J;ryA~m4_&i{io!%qDGJ}nxC5NXK9$(-jduJOllNTo!-_i>VQ`WPA1I1GW zNFPHSIJfH7b!`vLI(S^rY|D+B4))YHop1EA*$&}1wHv2SK85XxPGPZ3Pv0x=v7W17 z81^-@2ADdTaIR9sZgs~ZS!+HCfM@XVz4FN+$OL9N5+dSB;VO5!oDOH02@$OzEUgrS zvTR4sOL8#RATB8FM-ps;hLXV!0Es|$zmfwYLjpNwrw>}yuik21IT4*FE^ zhgvHG*VDA!Y0=tBpyIvI43U7QXRuC;8yf72%T6N{kHqx~Ts*QF3kLBHjZi^wfD!`R z>#w2-6X-JKK&S|o3loP)N!1W6v`$cH`ha5BlPo#CWLDOZgv^R;;cX4nBj)tGyHNhl zUd@V(ko!k3sD)2S+U-aPy4~jgY_Yx1(@>9NSjeC?)eM49Fo#XBnGjiDP-$UBGO;-K za$7mMX{~tJRw#vcj1-iKdqGv7v8gJ`ahW7NUBrr-BSc&lD4Pcx9a=jDeU|)VIh+je zMW`gD0E+tFo(@N+2^KLoQu<-sP~UC35pK$nZB}643_vuOQ`5C|veI{?;bfU2&E8a( zUGF56QrA5t;$3+D^P8LYfK(9MB#1=v`|s**BsxhXc8Lgp&<$D*4@nWHyrLY!r~~!Q z<7rINo-lraT`easFzr-wl3af!J7=XvpuxbLcIL>w^87fE_Rg_$H<*FGs@Xp z*jRGlUwW5`RREX1|C!tIYu$&a?r6Ukn>2@;s;aY~e1BirCKW6d9(G=^Dzi5=|3vv- z{I`y8>I#WyW+O0E@`O!2*VWpB`(hfZ?561_+yHuWRW^kNTJ{pWu5bHJ-R97p-~_YU z={SisZz@^J)>O!do~v}ockb6yY$I|;w04wzIZsViheV3e z>?Jcu9tp-^r0&M~DloGw2q8iFjiKg;Gm!)u*_{nP*Q7Djn-7aJ)iZt!i8YUNm>^0_ zVk}?L@yn<(!fcg6YFx?(3O8A33m8E*74Mm$h_I-(2dCsVIwvZNizO2q$f?1A?^aDZ z$uH6Z9EXaZjXR07&ydi3<-nuuK@*y$6QpKR659x zBAYwX>jV@`;~>L`4Z?TOiGd{+m%t`13$-$%6aqqw{w|GcxWgrUV)vVU{&0=7)5NJQg#^3_z0P0uw}5yEa%H2%aZIXfG$o$7=8_gw%!W5NphjR9xB#rSOAlSiuBOHgEMPxRurArFk% zfagw{v(vl%#MSOadQ;n8cT91OUGft>{ivTUAvMJyn_}t~m@UW@grwRZ4;($)Y_aSQ zLjZ{(lQ=K%5>wD{Uf}42`|Ia1#TF*k9J}9k6>YCwp>A};i}d8KH$7$UhnJNcDd|~8s9ZE=B(JV!EE9Qs;v&&Y zeU|bc-oE*pO>jgRs0T;O@~Wp|{ok<9J7H#Y+hes4lUoT+Q(cL-d%WT9n(zqv(Rpun zmIlru&x$mh_GsyFo^WD6Ad`2IQ9i$F?7#{HnhLQ%09%CZrRPjCjlt;Sxsl zTv365AZJ8{vl`M1JcOmOv2ftZi&FS5d%tUR*+V7#QElWA2s@&NM?mNFqAA+APQHRs ze%)$fkP=`cg6Dc;F0$dIUQk2Pf^pay?Gg%HIM6rT#UtDx&)y7vKKR?<*$W6@4dJX0 z^`?P8``&Eo@pSE`s;#rL^Y$fv=~nG;93a&TowyR*PdYD##TlF}tZT<3Aj4xJ!SDVQ z;ES#rmI~b((TN&jy_R~W3-HR+-#k)+*ocfX@hF*i?tpY|VC~|S-_8)~2Eed}L*N1Q zs&I>ffc-PVAEQS=7E5zun&Lq+w!An2AC3$(5HRL)0pg#d6mrX#>DU|o^;f`KI=@be z+Iu>L+_o%jI@X0w`PW&R&fPS#>*JF@A3XVa@c8A^!Q(e?4qhJ)h?c)Nc$3_LmSC6N z32N%I>4`px^|qZe{~$Ika6G4!{t&?HRJlQC%sfK!8zC?o%6CV1Rp2LhjpH>pImLjW zmf3SIlZX+^`2g|`iV3n-^^CRd8Wao>L@_*jW`mxhUAgctNrYI7EBvi1;!9bKU&_Xs;knjxM;fv@y9O;cU99>0ww7cfVXMcThu!Bg5 z*oC&q`s_|AxAM^y>=9I!`4_39iN0fiES1+k(0tg}@9|&<4+XqM&Wrr=3Q4czr!t8vU2^Jexs+vq(CO+oSNh(Hp4g?ji$IQ!9FP?6o&h|-U*Zed0C{arm70# zA0(Y0$+=IDPglW(0LT?UjuS1QcV}l$GpOZqD!tSxw4y8Q_*QRqh@W)Q7o1t7s+$yq z8`W{81m(%nTBroVWk4?XGvJ-u!)XO9VNaRU|Ia*z-k-SL8 zcf!pZDxNe#U4jz?u!^$i z58oVJBXi-yU8!kjBnQ7M4k*7!I^ucRiQpbgO}J?Kf-b6IGyJL}{9P)R`VqL)rfmqY zcc;=W=kbuGyiTN+lXN*V;4;rZ%pxmcV{Qv{>$aj)dbZm94Fte|65rk-^dGy;O>f>1 zf(`C%auZE0DAUD3+77#Gd$e6X%vz1&Olp z2|wrc2K4sjd^k!e<$F5Syyqf)1c0C&$l<KSw7pw5{58F8`52=N6@)VUQUg{bJpe@K{~Fo zV-y^RiqsLh&O2S|y`%Y12V8#c?1=__LTV~eeJ>6r;XFJoC<(l41654iy^Hc?8STc| zh8*zulng*(lYYw+O)?$3jLJ_&KhTfFxVoO*lf?~{ATj_Jv>PK%<7XP1SblH$;J1av zA6SdELWvQ88ktR9Oab1kiwd(eh`Ots1pF zfl|T+RU_8L@cc@FGu+gkXs7vwUdGX>T3qBt8IRieB83GQLrc$4Ykvi|XRzJFn@0(Y zvbgNon>4*%P9|5TYG3^brAL!opQlKQp)rmY)w6ZZ^fV4#okQLDaI{zsCuEXXvXR|4~v>j&i_F=&tB*98dD$ z!neBNV}&7_fxTs1UI&7*`!=9xKp(7%6ddapLO4FSr^GCR`ir$IPc zQY@Ro0;P8lc%=`Iat*V_tZ`O?S1TT?vNnLj z8vgRI@Av=94tqHMO`p9pW;e>{puMIZ1WkQsn<6|*FZHsINC;&DIe_ycNzkVAA&JMP zMqosSGDCHwF;!qdo||>arWuIY z4N)I(;`5n06A=~$YWgJ5=+9P~KwQGDW*xG;*e{-O2${U7&E;%a>T8Sjdag#5;i8+W zOed({9*8z6N#qn@E2{~wgU&{Jh0*pG&nzM$I1)iMz&Z%$w`zcLpR7)Nh{b6n!rj$T z7Yl)Mp3dbvzr}a6_sn<^gg$Wh88`!8b(rY&qz9F0QaOYJACAxJ+yw@EkpjY4&%ZGf zSS*o<5neM&q0o7EH*?YyR2^wTBPvx7Yk%B@Z^rArq$QJ=us~Qt|8B%8KdNAWKiCL< zh+n1)hgp4316b3khB)4<*OD_=nt7%Z`bZ>UBU^YY11_bZCZev&HMMZmIzjYW1LB;UZ~rR-xVqY4|9hKZnZ)s zeZW5FZMgY3h!LbuN94ssg(uQfHW2n7>%_MI8Rt@OzBdSp*>EVD(tCATK;RkJF+z9O zhw^+k*n#i*GtLUEj!$gTOOOwe96{6`b9HpfmG}`yWH$9E?qr>ZjYdJkG+H(?uUSf!py2x zUe@~bCe}{CE7Ekt##8ai2~n_G)WuZ`o_NEm%Z@>wrwhoqRTJ7-pL>hT{<#b{PHIZn z1dtfI91JIDTeZ*^_h5HsGtfevrC0k&HnVJ9#Tnm)WV}Q)J|7l{`i-sa3T(w-mx6Ex z!B(JZpS4LnMZeN3_HD4H)0&8WCSvK4q*x>ljdUgo3r9rOn7%pJi&S$7*MtxSF9Xek zOvd}l|BbjnPNL>Tq8sb$|L{jIEZ@aFoS(r`v(5G7idq0si7ky@ph|qN7fyK(18t<&AMC$qE`PjMH;XH(UpH4H#$&@%Uvk5C_LP?V?CW`8hHMUGZ z_{whNZU@p+@;`d}!;5E!gV&FL9UT4@4A{vQE7xB>uzyxlX?E@bWrQgXRfBX%jI#+E zPZ*ptraPXqb62#12zrK>l!$*?wOw7b)L$91OT8uNp==Y`kNi^86Lbcm%#|3eV3Hp( zh3(1;Cf~VDb8ezWKq9}{d9DvEa8-L$H%a>mG}FTwTIrf~r?B2uvmCA|vy?a@4IrUI zPjxiY04PulFZ9|QI-;@b#nlUagD7%Ds#h94_@}-u>1**Z-f@jY66E8qd3`&{GB zp6a~%xdS)3@G;vzb(Do(&uNkVhSwxRS1RsnqLnVqPakW?9RluW z8_46mLvKV4*06*{2mj4gNMIvReCUjGOcMtp#j6fyh!(FAH{dz^)z69M2van6B1HIL z&op>AQW;oVH;zuPZfGkpD(v}2UXM^)gCrpm=5N;g`^J#e`$Fnvq2vK8v8ld!PNlZc zhsuCVODlzY$f;qoG#VbBaHP8Zfqj`Fq26?sEw0F3tE`Bkv+?B)LKG3Fcn5F&jDXx` zKjuW#)|XVN&JRN)x~Jet=s3?1IRW(!`gUr9T{WFw+?c%}=@{Bo25Sx*+?ZnzcUicBe0>eldbfH7eNd+os!+)(BVBc3w%Y0DW} zr9^8)Py(>6z`1mM>2w2zo<~GX21SMVh$TT%>I0=%+%VRRx)J)|W4K!tOIjx2Kl>F4 zP;)Vvaw#&caNIe*80}Z(3YWlM*qKK@7Pyc;Is8U0wPT;n-sO5=*iX?)S~pRxA{>hS z5HI~y93vi_q%#nGf}ZS*%r}5Zut%2k+27&BXwbRv%9Gj8+G7vPd7X>sSYyF` zPpzj}H47cmJ5^|?^J8FW#_Eg>Yc|%TUiuz$k zp3HlZ%@RegAeI&K;h~4rxKG*cU}gJ?a^3*~2x^O2@;ZN?J%QUrS?YG#G+2iDhFOIu z@`N}Ef@`q_e!zZ;)T1l5E&x~#3z9>wSqiAucV=IqWUeF&IW9_eD&$%i($IbQZ0KPDPJF z=sR4TZmQ#Kf>xSRVCOr{>rN;GB~d|c$z~eB;AO(Ku;ZdZvk%KCo1a1;h)YaBznsno zbnn0f!fu|;S>%fQ!l}@p-q_63B^ld=!6arxJ<;)JRgK5;W(C7;TrlKe&8*z+vMgts zD$kG$89=z4#bvqcfuJ+FZ)>34A#@2?+O;QJsb=W~ z7p6QD+)i0B$Gb>Tr3zG_L}yo!5v8F(^4U|{B^J6=6KU)?V z9`Q9`Sd31ybI4Jl+rykKRCkc`8#OGFoISV}*d{IPu%M%{Gz{x#i20=Y{l1Ll>7wg# zC>hJ(`drr=LQ6B#uoN(Re8gw~C=o_8kzj0~DG%gYiQh&9vbQhaJp1Y8!Bg;?{pIz+ zkI()Jnrzp>7xS>0pP;pUnG(C(~-DD`4r|yFmJ$RtJEX;!^otZ?ud-e|va6}vYOFtf zX)e^crmw(G?kUjzAhm(-z_G&QJh1GBEx4z?`TPC*-+m+GNnLrepK8X!%o%@X6v@^8 zUPYHPL6@`Ah2hn(@9%rqb zDnXocsp&y#=59K@^%Lw=j^QNN2b5ZKqT^nn*VXkE{9$@IN-?IZ-mLA`oF`X;U}$sy z;&ix3x-D0}W4lWF1-Y;`w>w3G&Rv0%ioHLQ0I&TuXs0XW=y$VqhfAwq}7tpSwgOEL4K#qh#d zhWQ6ZxNX3dyTaDQ*bI5jG{h+p5TW&?_?=YRs34~=B?1>ZQZ_&o5eQlW+n4<;E<1cEQp+KakN|}T%t2c zvCx8APd72Z5Ro`24MrXs(2lthqLCDL7j1uE6A2@;B`h(Kk zfMKjqG7do>>?fxnG$%iBfZufU>SylQf{L+BohB?JGL0$w)O<1o^D}_0NQ<0;ikGMC zppCLV4ES}dua!qv;Jjp$%g)eVWpYK3>-8ZSUy-6v3;VHx^d1d^3xa#CunG%I!ukRM zMnhQPaZX|6a3N?^+>(j-hOJn?ckt@f-!o985j}+dGtF;Xq`4T*7N#GNIGVV2l)+t{7^x(dPG7R^QN z&7@5fe0Y@~8;f7PH(lUq@ysTkG6^8DrE|>=4AL(6g+n*q1Y>NBgQtU^G_88`%fXXp zKR$ah_{-zh&mO-#OmrA6NyuIG?XbF0eL6_u ziqbuKDfLYu=pc5#U74!-o61(7yE?p0_lV2T|2!CqKS48JErio3zF(TN5`*qlTas#uCw7Wi4g+PNjCv|yIB!s&t%h{dI?M?9Pxo8-d8 z`|73!-<&8Hc5UM@j76Bo>2N-WqXB&sF>nE}kya9ClKf!Ho7{e#2`lt+da<)ZS`)Q- zdZE9e&0tKYV%1hRzC$#6vN3MP(!$-Bhkdx=S zWrcu5?hBi6K7uO`0uVWMJjx#L$HbSadCRx3E%m?>_>~huEl8jF zDH3SFM}0Mak(E;rn`(gJZOPI8TJ7rvu^?_;783Z-ET)?%!qa>v5w70>~ZVaIEYnC9H-HabN1qh z)T}#js+u%6$0%#(nW&qw&V<~oUcmlhSm+57=2gD0V)S$qZK+N2v#oJpZ0^)_p-G&{ zSYd3IOTiZDANH0-F-Jzj34}9R!1bvVTm%PCmUBZPA^`|}_=5@%h-nfEN??uyl2(Vp znyv2<nWsfUQkU9sNb()u zy*LipFC^uuj!ZHxbaL{g;2rk`0UF)lgvoy_uBux&yJ#Ij|DwDhoOKckaWCQHxh8+u zO|$MEryhoPW+EX1ET}K`S@sX%~ zNT^t?4PHG#!nVxk_bLb=8|k?@h;VA{kwCWD+H~RiixH=&y=`nef3$iO&AwNuM;j=U zRTsq-%zpENz}zWrg`h;NH#feFwP`?yZV&Nw$s+_?oJ1tXoTY%c$`I6V6_v!5nOZ z{pwvG2r2iZ!LZo(PX!x;3jb zk=g2u`35Iu4UGG6X}_Rdbo`bE{;SroPqU@syYWx#uJCB(L!+XET&w9yZgygYo(6v#ft+^)}U4!tJsJSdf}DT||6KGv?17AByGc>^E-a zJUVai9{r+M_1ks~wHxzGdWgPw`~2{k=C}u6t*dB-GSb-)&_|Y#*ZTCk@S9PQci0Dc zGXD4Q!nZRS9x3hxSU3i6+ecE6&XSQ+3pbM?G8Ax#EACo)B#--PdA1X=jB+FmO{A*P zM1_&hoJ_sEYOIoymMprb?runk1EMHj=_ZIu%SOvCa29>jytE#j8!I8}?>t8Rt@~~vl ztI&eAJ*>k9o6f41k}1aw(^1|8l z4`(5M`H#(s9Qw_~E^(jZI;N^w$kKi_H==CFAzi=9;m$Cr$QE z97Ku^o=H@me$oi(OGjRj{kKpmQl(`2>5xls$SGH*<_^BCv z#O$nd%lta#%Xg7%k3Uo|oR+V~SOyht-hnOTX<(Cp*T!=e8Oe1Z`#O*s z?Wmgc2Ms1(k{V&?i)_rghI((!$dS`Fe#FZhrWv!Quo}WAib9Yx$swLMk?NR0CyUuA$tQT!9kv9lp-G!$c#^j!7lOa#z%)=iK@5QvCLSm8UwKJk6rWn7n7Oc%$<)^VeLY!?dW!Z&Dj z?{0azJU*VJu+rt-dtXI-mlgenHw7|u!wZ8#zrZ`y+k_}%E)_1_#u|i|+Ho8u3LmWS z4O`T-_7MI2=it6Nrag>rB-3H(+&!;Hs zcP=d%A%kbR^MIj`7rNW0ZR{8DEZ5gZr;rL$jkZxq2F91~{$?zh8M)LVz;^dcb7tC5 zm6c0mWMu52%g2f$g4m!3>0>x6m!4F;8OIURIAWovrl_Q?7>j@bxTXQJ1)(U$iW@*b zL6i5i#Yocxb0ihE{x!MS4IUl#1nNVKwJMMLH@mT$f z)1l|i#wX>*OlvLW-CqlTtD==Sxa#;d1F0Q79Nol}bH1Q0Wpa(k*L9{^L$+AR#Z8n?FvdOarai#7FCOYp|tabTA1jhTLSx%b6t+OT-~z<*IrNOm>+AR;O|Hn0Dc5u z%Q-yt7lUo9VQrHs$wR0AtvGs3mLSG!{eT;;eUijl;KIkwcG$nulN-&c8fGT?5$cz4 zcqEkvrPN_u4Hbp5TvS8-&(x1S_GZjOquzcv5O5@Ouk6X#c| z>cc!?pna?qO zG#L20-O^Ol@p=E2b5Y0eK8`XL5x*Mdn1YQ?f%vmb-3XbIOO>brE&lLWwV2KG={#Rx zq7zispy=;pWr`_@xR;f&&|!Q zXB5S1UErt#JOvEBx5tw}_p8Y;oe#~<>v=+>tqxp4TZ)#e&4gO#YF>&y=5{rdNHzbI zP4J$FEVRHuavG3fc9yE`Gbm#FRs^44_RYh6HPZL77dwdoV+XZ0{Gu;PH2YHznjsn+ zNa$Ni%t(H*oKUz-I$2=)!xP|&u@{~kaCytgvOuUh;V%W`$b}TFoD4{GC9d_CXF9{K z-5kZbFF0<7#^Mrj*{5uN4M}G|+UZUq1uyZRVu0S7DA0?2LNQDI&4bc4cKqFm06Bsl zK`SpIO#tmU%i3l>HJ>O}CLfwUwS;C4*{LE5{n>!?XPR*o()AUbF8cH3C@b3MXNvTm zZU2d;1T`l0@Zx(?%I@s^@apODPyJ`_jt;>Y?}vkf{@a72=Ld?2y?OP5FsrsCaK-!u zfpbTE{qpeO$Qu&s+VTt?6R0>K`si!zN`>CA8mc;gn)`dN-)?Sy_?j2*^&-PG;iS3) z!dbYaba<^OyW+@BQ`lrhSD||DSh2!wNjjtbz<)GPGVV{N!wgF;&*sys>t1!=uqg+} zCof+=Jvr$Ac=Yu7LI2sSH_v}Q?qaXr#hJ0HzZ@6;yZ`phF@J?~+e<$4#`mB75 z=)c~)J9+!=r2p6L|GwEx;(eZFC_L`$i1zC9%I>Elb(Oed+#q55wEc&qP?Fo=JmdUj zXbAtb{TOg#699`{VoEN2L?B)h=RyzZ3p9&P(bzR%OHwso9@{+FWzXjY+K6C6Dl7XFPJ~b$M&@JwDD{Pm^cMNf_n{^!k6|LEZ9i+|v#kVbnk&;AAczUe^qNRa>kz(MH@ zhO+6l*QIyt^kO;FA=d&!*adVwp;xL!F_`BwhY6j{xL)Y-#VGf5x(KydNSt);zQCe=wWAu6IN2j03Kta^US_DjqAt4*V$ZV?CO6l@-}LNE zF&qWv_dYGAqotDJuC&B@j5tS`3QkbX8_ z)?_q2AE%Sa&JKj|`F<4z%Z5Z2KDQR+G@2EysGxyE0=jQi({ z#jHOVrMXT#r;VI{Bk8Iol}gilr&LP}NtIGXns==7`Lhrp=|m7ZwvrxDqeY(9|W z2>%946-$rjIe7G3<99;`rS%2T1nFPDIC%CBYO~}Ro`Gd>ICmP(rPu4(oM^BgoRO*k z`x`hkt;I_{kyT$|5}%S?-EO3&9bsdjRadG7^jF5wDxlUt2O)7gUmQdH%MRyFR+q7#-6SWn&)HuJ@yq`?P~SY>+A_f4_r(Z`v7sXs{rV&crmkOY?1}Cy6QMpbuE&AiM-s3eVt3UIX-;osB^{E^%|=h+}rjN-7-KsBn06)s@RB`EVW z7@tel783R1O9M)Lz_5r$I`gJ?K1)z9cBcs~n6LMAuh7$xBCV%b8W!a#c^~5?WBr0$ z<{#o{w;dhDNiPv~ZM@*a!A#_`+m; zPrwIN+jbp=DI4!M^Z{AehA<`dk5&=ii`?)naYl@O?^r`s(LkvnaA&2(`3cDGb}ZE3 zcVD7C6ALi^sOtoL%PhADd8_*LPCc7mniYp(Ns4`*^h*@m9EI7ZJUDP`tix%U^97<{yTVZ1B9S2LigFXrvmTh5F7@C97xDxweqm>+s*xdVBii2=m{Fu>`M`FyJ&GRv& zi_=fJEHeFN4Y7W+2YVK0d$)s;2GAHvZl6NI2ppmuO5pcd%(6j#mMggEnZV}&tm&o9 z?4V2EDVE`sykNO|^WF4JILB54^AXh0h0_l{o}?=nr(i5gz}*GBPpwgH&EZbp7t6sw zjRs1%Vy2#FA9S!Hc7XmeMf-D1w?h=9!?oL~P$%`aBA~@keUOX$=;hTvL;foc?t`PF z{_Eo(`>&rKpBxfMBwJjHE?3y(!-JE*y*c{1|JQ?~V_@!^?y=NTjB%AHh>;{l zJI~s^T^B%1&D0A12@o=$_R>YWC0;IbsBbV)rxT!Nbt*xJi|IlM7A)u5?l2fjeAY7U z;0hbINCNZ5r#4WKb)KIt7a$Dzqnq8L8=9Q+N#5oM&JrJ*s~ay&7JO+OObG=I&48#+ zL77-lo#)uy>2z_PxDBw5G_OMu#p$B(HpDca&WjMp@~xkfj(S-aO_Cj_JDGi@VhtO6 z@+%w`Tw;`r;j~lW)zUteDz~o;>u+^QvO|nhZJEtf->W7M+%_guHD92yRqkT4AIy)o zFS6lARIgg`#>Oty6PxGj~#o6vuP1~*ZD4=1mDT~gluup3U^&j zvi(px<%z+%cC2pYDv9gUk=C<$7B-vCWo&Yw(QJZQA(NcIe}p*bm+019j{edll_a*> zB>3otyG~#5w)=^d_eHt!fhp5$<{=p%AOumaD5|+Kw4x~%nizS52pZM;^odS_qA6GZ zI^*yR`tBail06B{#*Ur4%@tNmV}%C=s7r$xn3Y#yD(M*nj3Vc2FM0R!@I+1P_0zvY z>EuOiHZ<<2E@#bOMr6cfl| zHij_$XXtOMEe3{FZ^B)X!D=}gx!5bSh-zC7$H`=gb)J;ef~CO+`78zV81Mm7;K?a3 z5TrpM0cYGmHtI0R{tx`r?s8Uw;6^T}Wia=;LY^BAH(Q^;%w4$PhS zh%(S61YP`_*8fgg_SlNJozHIb=9nf|=$zJY0-o|FnBQK4Q2fVqNm8|?Jy1g)#H7W& z0s{tP90E9>W&^0}lsXK83Fj1tq$elgVYNpOd58DIDE$HCZ)k;&wd10B_b_&B zbu>vmS5~iTUQg_91sJhwvRn(#$-ltq!0;(XIjJv&-oedpF@Kv5JEIUe+FiBN6_0qtHsxbmNP zM~ifhx=_>KV^PH5f%;nqdx5+Ke?JbRzoO`^^_f2%y!`Q}xXM~$dRq?lJwhnifq&fV zteN(eRufgemYyK=f-+mdX-QL#ATz4KmyAqO==wGAk5G!(> zlc~({5xeJK3qc0EFxD){MM8Nr^fYRCIN}xpb-LD`_e1r0RDr(g5^I&{cTuBPEwxFY zaIzQVyAC3oABJ(w%ms84FBb+v*Va(eyaTgGjsw`^Hl@v#lGC{b#$QBTGb@q2eESv~D)>b44P*w#wc2sGeEr?zGPOH>$k*-y&+Jh> z4xPMt(|`T+@E`q?zaN7feSC8C?)izvA03GCTjv`%;=CCn#muTq7!hOjP5h#~>PtBP zXxnHBLverZC+q6MD*A%A*Qm@~k0@Ul`EStKd+Zrv|+*#cC^yj-bHrtwq(KGamUCmh{?d zh3TPn2^5l5gueyPtL=0gkex_kO8>Mp0i@R}OHOcK?E3v$m7g_GQ~49Y=W#!P9!s!% z?kF)l*>Hg*dk$_BbDt#Qqh272z_t zMCzo>=nb)=v>Be1I*IeHxK%2I)?XS370^9gPX^hry?syjB*y@NL6drpf+o=}Tz_+n zS@cCOc}tF3@TcNbC%O6^HQ#Ld;(X~A30j@nRg1O9PeEC zp~dTESG2NaFU)URp5$D6vz#e_0iG*c`su$)zTjf%qwEUI`u%~LlJGMU%klQ;gHzie z+WC_`N{9?yb>MLuf_!I-IZ&8CZ*3)y9wysc;Ki_9sAKT((f!=04tcb*#wpfauW*2! z18P?TZYSJFukrnfH_-|?&A!}vgG-@esb_8#(ood)aiN*Grxg?D8Ky|bJ2rY=LTlCC zq2!;~P#+OMC@97%$R!!tnhLr#EBA=ehps*J_q=jD(jC`tI%+0}N&}29M4`eBQ`dRC zfUB|h-wGGVM!ng!xBXY$C~*w1P;QH3sMZw{oQdd!)~@UKdW90Y$!3->&u*np*qSJw zS2Hs=HWc60LaoDFJ&-V*BAt};laY6jRvzsTiW%x_Hmags8J@VO_4b<^U8Le8DW|1e zZpCi9C)a#k%IrZtf9$g^(u?@J=NRq9ok6u*ED110U6u^>!uIp*Om%bKMiYI<@+P;4 z1B-K9H`2a&Z-Q2Xu&_G?hSgmcb1yt>zt+OH>Z(X}t6Jy}%T`64g6nxFiyYfMUri(V z9PLdkC#Zf!9Twe+*6R6{K?p6NT>tdioLK9~ufY@Z$@PDl`{;&~7-eURu)m0!KzC4* z*Q1oun82TWVz3Bw14MW!;pB*-7M^zZ5!@tiz2o4v6Ik?VB#V%|}uN(T2;M(+G zJDN4w(s6ng-Bb>WpE@V$b{axs}+qS{YSqc}%3P&&FyuM0FS1d+dBnE*LwGS#`15=?-pE8`_p!w|c*3w&hT zCTF>tLUOJMfH3er;<|xL*@RF{wCtLV-sr3zQRAxmcbG;ZCgeXJ?$7#yoXQQ=oQxo@bw2 zaRZgV(QI7}`C!NHSUQWtsn4at9XJ8zi&a?8LhI*npQ%EE{CAFCK;M65HH>Ze`FdjW z3D|T3hO*WKtD68%K(N1W-@AeCAG`>&c(?;O5s8QgA4rwa#0{ zs)@JLQ>ay5JwCGq=+(0vBfFFiQKCd0w`|ssyYqKX9oA$-llUxgbxsu?I=BtUq5~IN@{O_6FxZp>CLMbYLZ&l z^Zd=r!vyo5sE#dh+e{o-2!mP~9Kctxb_VYb4N#8iyrzI;Tu+xEW`DQ9TD2%Az%(`8bJ5uT$K$($us zpYnjAxJP!v-Hes^gfcF{vH>v{ic)s4jN8)eUC*>_ax43O5NZml(G2+O$%1UCid0-k zCRVyo)=f@ibE>;X9z6ohJ|;h1P!;<$`NLx>e~emV%|V*GeE${}|+cqn2`!Zy&MjJ%JN*)*S^Bub@_Q}t{z z$aLIL4qR8 zV*Ox>V1*}$pDhulYQHlmwE+vVx4Aa*_cR5ib3l;TOR5C)ot+<^{(R6sd~^8xKx3q> zm}9O2Q-RX@7=c?wRCVj0LUS&CzbE%(R-R2gzCbVVe*m=MIrMPz?{ZF6b4&sQZo7mg z5wBgJL#P(q>nsV)2$NYnqoRSqjt~v_wg{@BHi-df;Kt)Bz_nA4)lX9=5d^F6h1zwD zGfKdXdvywgv4PA#P=UG6p<6?=byE#$P`B#G@V&7s!y8>1+~!0#O%XUwjcXz@_>)bL zFINMd4iu#6)o!B?I2vSyFBA$+R{J7boTFOQe&f{n5IK3 zt!e*cH;Z)Xpy>*!oICA9;e`cRQWZSF4YE~F!)eN;55Z-@}=w{`eqW0F>U(VyLj z#bq6N7MyDA^}|VaS@Tw|wi}{dvQD^3#;F?PxSb3=9U3L+YI#0`e!4oqz-NE4QAv@p zHP*9Ekv63x$Em^%GtRE|6Y{Iq370UBk>4nsm`M$47Ksfz;Z!JHa!y5@6E3lCt}(my zE`u38nmt8-fI)1VV7j2SJeLic%R||qxqBkR4-D2N-)sEo(ce1>yVT335(UK<&g0-i zO?UPAGCQDQY_#9;y;x5e{oE}#iL7=_yYtIo_6dAA=v7ZtPR3SHO?k{lWrdzeWf!bs~?Fc)reTSypy$E@OVf$pzrSF|u z_PSJ#CQ$(;(2>LjY1xSdscvzt?%FXSL{UP&1ebB!pD-EQJ#;E5BP`~=`($d1u%UDO zMMtBScvLerQpGzi^yU#4&8O1+06kXry5%F>N{=@tkC9CP*xVgIpvJ*N7K zCpvb|;C@IUp!d+A3kE!lHmUvvZ*vEKnr^gMEc1Q_f96U&1^auT_Xz%n{|J*v0X z^K_C>&SjJYlaw=7octZN%B=f71O@7GHix^&t&v9Z3zK%58ww&o-mGH|i(D8J=8UN%#P~fz&g1C_x}y06hqW1Usd6 z$QnA$>B1wj>k?tZX+*g!Z*l(ecxIJ+8b4)Nt@{x1{DVqR0fc5$l2B&}uZz?{do3j_ z%SG*mlZ8K>Ou8${mpM2*6!d@|H07_|dNmc0G7=$P*Or9qw96blHFi%%yN} zONcx2#n**54c#2t#H-vtK_M2%A!_7Pp!4lIewHIh4J5?9* zpZ}=MS|C~XKSCYPaHnPV$GGz>FIL~$7-54e0yo;0>8jPWb29$4$abElLy&7{t_OkV z8oC(I%3N?0*@Jai1UGuHk{OTLTuo51S|F~ws0)0U)vZ7D88eNU!Y@Cu+}paDnau^+ zuz$*EH2sv>IdTcCPR*EpdpUt5vFBU^#$Pcan&4@@V@nEjjPOJ4Vm@6`$_&b||LpKM z!LXkl^ubw9@&TFr&=$oRyC^cSR1ja3$b&<0nB=63-N^ zn+aMs$xN9qOyaaO`STwW#i7m7980|kf1Q$Tuv;OVbK4}Sfq>!5a63E88Qft&3tfp3 zvtm_#Iag%zGJ1W;t>qW4F)eb#92-Ts^;Fodtk&n$7-cPMX9B&z?FZ$7t9?;g9)kENs`7Ms z-8VABRojsvt}}(giSyTesIk7Rs`INTJHL!N{%a`Yzluu!t0?8al3M-4jo#;#I zwVNd9CMC2ddue_Zoe)e7iipWTN^qsdy`j!Mxq`9pA>0o#_?t8nj;7UBNjdoH>jiEljS}uw*M`?SbnS(EjX=xOLO>K0Vrqwq2=zZpPVAKrp*QNdr zY?|oQedaqpH2u!^fN1(%=n7H&rR%Q%4rY;q70DBYTi&srBzMku9T(eH&QL9EOVFd3 z){{LoNhP|UZU1-w?VIDKBP*e>|I-h8;OJP5(`T%hHzV8$N!7NxE`A#i5o-jDr_=a+ zlRSC|X+nyJkD6W;mB}|6_5mq6ygDw;C$Q?hNod-z@vUx8KXpjM&)ftfC9J)15eu1g zkGD65P<+|;HCTWmO#Wib4c*{)CQZYz6c)T^#TR#-D=`g{FHWFTqZy#Jeci(Usy|!4 zYC2pAEdX0PO4x>jJ;uQv_0Pq!X!dy8PXwc3E(e%ESCOVnZS zCdG0F>8g~Z33@MA5d7a(%XNDGZ02?Y+Q$EF?I!kn`X&Wu>1{=r9;^TVN&O#|pa~=_ zzS6GKH|Y@I7n9@8z4(?!yr&O4kVNvCkfe9bxd6w}vx$!BjCH)a|P zJzN;@3hJEQUYbm}I>}i6(2f+Q5xTn|jmKmp=NSrCWQZ(vfR6Ll z;&;ch=+~qtj{|dcMLFGyO?dDq3m$R8d*FGRIOr2|;Sm(8jI-`#!^eO6)As*s=Fa#N zxYU?EoXw}7@*yBd;N62hI%0J5EBQP+A)NsN3FGk>lR9X3+BNdVU4Cjrll$wsVeI0U z7_d70@rx|6GJ^6;j93?hd6%CO>raW<=Po~YF*n`i=YA#dU#S58t>ka^1^>VQbG8=> zWXANUET)lb7yA+IRJEiw>f%~t^GP}q8j#lX*l-u)<>Bu}0sEmlEv)Yp!1k5=L+rxWFezq4~r9Qq~)qfRkV67NgNG`33nBjTH0*h%ta87X{dRACu`o;C=+t!EQ1N-99 zBZW|q)lIFJ85J10hyoM7mD2|lw}Xn#=Gi9n2k$`n7VQjq?Of3egE#=~#d4iMKto}J}`914`txG+=Qnb{?EebC+@Sh-xglLZ9QN5U4R%OVroi}N=973wm92vW6S zxU2JQl&g#8TAh6*=MBzvD(%9q=Xi>vO{SOK1Ve%rNuef^!6&$%=9A>*Fw?kjsh9_V z_*s61sM9qwfKf1o>m?-a{)7oa2czW>w{MJNwb(6b0+m*D$dOL+Z6@{umWa(R9mQg6^74!lnWpmvE}C?M_X|aQ%)4iBMg_Auu~rqJoAV+VzE%q z$Dkjie&sU==~OX=EMD#uPBorlO*-8@-{k4pS%ycF3#!Q8C?PA2MRoI3I{_)NXa%#g zY+m#X|6*e1(BeUmWLE3-)0c;b2PdxL{q^6T9v!|s{E>=Zv_Luxs1=Ef>4H<~VI$|x z(IO>#n`vl*LIH_kP-jVmoZqm7SwTwiW)U*2ney}Lc$_Z~yf`UncyE&pSu3?YRD-f$ z4y1oNQWwi9@EOAxk#}zz(O44z>@plm-dQ%20A6V^Ztc@l;i*-yK%Q&OjcL~B&b*RH z3#)s+mK*(>~8lBBa-Ne+{t z5(83hi??w`IkHL601BZ#HN{(pS35}vc^xh96`ob&_HB(;+x)%i<^u$Q1D67N+^u9M ze9xs@`CO1=HBV}x_{p)ZAF7Klnm^&J*`etr>=XAWYl@n0dJr1iu_<=3y@r01O+vvL zSZ#>*QB-`Yw&{Gam^~@Si~Tl?(nX}ek+F7RpAb`&G<^AkB2iuDRd*&+(7#WJWdJJz zWeiYUEgT|3;=V=V@;+f$21#GC$%z??SFE|^K0a0h9Zn4^LHYi5f^b}`j_n2JrU6_c_gyQIS$ds5U!^zl0JzRIe-^s!b{s zxgTX$z*+VOic=GHS5#B(knyepeyS1tb{c08jjLR!q`YLuk&Id5LQpQKg<@2kt~w+#V|rSccZjc zqXX8HeFwAAJ7_Qy`TrAIlxe|bh8X#-b79(Qoai5qe7ZF>; zVoHeMjz{WzMn>!iz#c&KK~8);VjLqJ?#rAxmqOg|GvqE5)tTiaWRLmf=(=aG8;|9f z&WAc%0R9Ra5182G%=5}+A@m6qLZ2wALEY)1=NlU)?*hJfKY`p!6LAj~=SrwkTSVVk zg#`Y|stBz~;v0cd41b1fOa!)9Z+`5*J$iX~(tq_>u|(4;R((V92sR;t%^TYur69SU zc?bMTR$;0{L?0F4K1_~Z{?EaVkp>Kkn&a+6a7qxIHl@4k>ydud$MJ&;8!>go7KLg@ zghPNg{7}@jr|p`4FqKlb(} znx_O6@%NM9q7=Ah+r@H`v}>+Quiy8!w-4r4*wdxn-K0w1N@W33godposs%RrZw0U9 zbX)BtD!{E3qYg+|&2c3XM4)u}AoOBcz7rJ8<{$!Ks=SfB8QD z&+ZFcEC@ z!o2rTlV}b_E9g(sw=CzZR83;lvB3~!{xbRjR-~gSujDh*7P4y;$@iPMk63%iXaU8C z>X$xUej?|S$aFTNbJ=uv!YP*HJ9MpS*@HdE3kG?DI02xdz;J&;n@;Ufc5H{*DW&s9|IUpvlj4&rmwcIYyLp3$_a+zNTHP_TJn%FbK>pcJJB3Akr$? zcaXaZu;pJ$_I@6)Np4x^h}DVoA+9;Zn~LjF?fZ;hIm5o<2W_&Q&r`2yWHG zbdK65{rL2n1e2YXRD2YwRbwO6`wv`B$rWCb^;9d={1>MkTjU4FgM4yZ`|T9qD<7OH zmf=P~<}L7h8Z^lLXeN!Su;U{8B&*wr>f^zXaz@l2@QJuF@x8M&*NISNrBto-+s6UG zw?JK!?TFrRW$h#RQ-mWXG=@;(Ev5^FAXu7vjX6|H`;&Eb_G$T65)IlwsWf$qFI;}J znl4JX0mSrSqWSgmi4uPn?Z|nf(?+x2*i)lwc5uxkGqb3G5+MM;ZH@Xg0?2mIM<=2V z{?%!K*${r7qDUh4R=C~M_5vB5o}H;ZN5!HPIMT%elw{xn^M_sg*FNyuo16M?yUhj) zw3gU}V>1;q=$aYpy7CrjXcv+_2lElnc8y_1T@gh(0k0c0-0;rzkbHGDrcen>XI!HU zB*{&_;WO-FDq+m7B;yjgnh`U#&-N4ZE30qq-$tQQrTQ@=K$SgE62@8^Dj9jFi6!|b zt=r*07=04?GUz3W30kFzqnm;DA9`b3;Z2`6{&%TPq+;a3f5&!aPhI@pYgb=mPzdkD zS{4)9Z8p-w?&Xd00-1j3nggrzBz%&8Ut6;BXflSm)&;xMMVGlUA_W*6=5c;gPHa|mC8!bk;{%)Kok^>#0GGqVNeI{|qU(rqe(u<+LOYktc1N9+L(R5}?%`cU86PlGl7 zlcVS?iTm8*E?Q>%o341PmH#c$B|7iG_&-_n2Woj|XV4%YXYIpxuU>VM&E)9d`GMln zIp*Oc1xTrb7Qr-Vi9JU6|^fW-nXDA>9S~j zK(}@9%sD=Jdh+hL|LooIKg>BMF0+5SEUwwPCK!9=@gBJ2z4_Up8m40{+#&y?*l7CR z?W^M=uVE3jv#91MGo#(aI3C~#XVU^x@?8+u7+m9wlBmzipz!(3kW&`YP#I^R8DhKd zFtVe3oC|VW&Iv14PhIS0#t?l(T(Bk{ifbUTdY&zdtgpJw>HEjtNtE%m#^*{$*}gnJ zIePi*9d6CDmxtDi4=`vEFV@kd6L#){Ut~AUTvIgSs~JQb)b7siSlZ-{Vd#VVxDlPY zMNjU#DCH{Y)uCS^#04phxiAz-0@C5qm={m~ajfpLXRqEo|M|H8_TUJfxHH1KAVyNw ze6}!gmq=#a=nR0&#h$xkqEm|=u~fL+c0U>{$IFpw_bKy?<@EYApQM-|Bz!fPj%Vm6 z?!JtAE7qa=KJ8MMk-Y%{Y~;FVJfHL#X-Hbt6naDuZ%4`{>05osYHA+kbVG`6`x8b$ zA!eXsB#2~S;$E4u*lWGaU+j*4xdxNaZ038cL@XV{9mbuWgiH~a|KQrtMpEHNB3j7S zRXaEAVHJ0&I7s%(PG9{#wq8i3h12U|I#mMo$8#x5RnM)A25ctLS?}q`a!Sk$uRL@0 z70$RAD;&FzDMtzOZ*V0~_M%IvH7oxB42?hqCGUSRus(@_wE^Isr9%!_qEGw@8Y;2K zMHaqyrtGzsSa!H>x_<1$9a zfdHZT{$Vy4p?efWkno_L#BN$4^*6*%_h*awlaJ|moc6)5G@I`y_?H!(s+5!1R3wR! z9Z6N4VK21*@p9Y`83(KFZpvin8+c!D6(v#a}Mbbgi0I^ z0GX3nreQK_2WaJa$_q)iotk^5A_$2eki0l z$N2)ZB1Y@rG&>tjQ%Eq&N(t`1KZlt>yWZBB*VJ?sZ!63Wl}eFHoyhun1acwYFMv^c z*^xiUuJerxvhq*KdBnX^oMOg~@q~gj)UkjA^UJab;+z!O9p_gVTF?aylhN%x*%QQk zES4A#j)BRX9pF?=M_o~nVWueZ(@|zW3Ph3f3}un`EvwP{P%ej@XYW}v$^}=gj-b;- zHgPYoAMjcZf+n=%IC*vBYZw>(tQ55(O$lfc(U%Y(tBc=Pf$Pt-sy6&mzvnA+R$udir70{;XLY&XtdPUZ2hGJaA)BJX2O@^^1__q;i)Lb7z(fBx^@ zU+!<&_P6NOW=`*B(@Vjzl{+_oc;yI6x7}_bfiVL*)W=YLMc`3>FQbQz%+i)5;k{oe zr#m3D^xs&ZC)03}6dTtdG)P4MbgAAr#3xRd^M>|ypET|p@ZE7$$kZ|uJ7t^mgnnCf zah;|&K3HCnc_uMLr&~ef5;lFE6oZ}{*dqrBb)%?8^h8_7H6QUEY}F*|IOxtuj)KtU zK5(%J(E&T=i9KkJsVh54d?{=c6DdWm8=*%&pI%a)B5MXzARNxqOGrVJ0&U;Kdobe7 z!}5U7dKwX>Oged3a$|UMhC`N`b{r?5yUi91ICGgBMAecoR3|eoQ+0FLEQ7~p*@6ti zZ)859t+IurzRRBMLZ#%Q~2Qb6Xwn2TcQv1?0j;u^b z3nW&EWtJQn{6teAmPN{_0<~2yMPo5SyZpRHXS@0_<^Zm<~OTzzF zxp38$`*h;4ArRyzVk$1v!Z=NjuAyWMP*in!PVhoW5E&>N0>&ou8aKRAa9eCiXR!=s z74tijXDIQn>2A~CClbJq+>yYC1lFbqquuPYP!owzr@HJ4`|yV ztSObf7;%nZOu(3_j5=NV<`ovZ6U(7h%go&SJ4CxY&|f5ucGEiCd?x-DeJ5{%Z%D_D zQ1D+#eKL3nj?zKa4wqpUvbR?nrR)1N`fGC!9fzcfJSyR06KDHE6O}K>t|->>bY6Z` z41|qlHCxH)0Kgm?*Cv(8?iEHcvz`@?`p_4xByDE3~^YhyzqObuS=600H4fzCvWs#o9+mNSa`fxwX+qK21X6EqN2p86mfT(ueGcXmsGke=P~)NLyAR9qIMc&8nM}ks#QWFs3qdM%89kl)A>VP zMO|u?_mP<{cAdZ&g`1;{(z*iXL!=NXPY@3+hB<8CvVffGzKYFrlTv48dMPt__NVi{ z^otKJSDi!Q7!U@ah@EK&iqe`> z{_YcazJb2ye2xYpNacHNgUNi;gv@35u|ER!g$PnmPBM}iD3>cr4s0x&DCW~2 zOIpy7mPQs}NUOHhiujB+nXTcpfG}Rz+i?Jjh4d8e@o<6uC}zFx;n9<##JBk*A1}w2 z+jRLI*s!`|;&VuW9mwz`K`upH(>R@jG7a*a&QM-ecr@bEX+YvN!t|*69UeQ9aG(TK zK1nI>Ip$^N_z926X>jyI#;gfpaVVgPeN4T4V?RKfm<~z(6{F&RM8~kLPZfG(viJn}My~E|A zEn!a{g2xS1&1mgFP0O077OHE)oM6~9rAxQ4HakcZQn}u&e^|vS~+at_Bu67UtV5z*03BJ~hE?y4m zTHx9cmDlb(PbVLjBX!7~IPobY_I#gqPdl83au4!xL1l`;-s#?QMrq9SoN9LQ7KVXb_N_TysXDjs?`!xjUdn zCzk8ut{C@p$gBCY|3E}pjXHlb+%OvcFN+?{?ttGBhbfHf6aqx1v47xwW0O0TjXJb$ zig)J9t;DY{$OT_!Qtn#47we;*U{2;scGqvk^@y;opv62m1ZVjd z?HAeUvdjP)*uJ3W9a?!g;>Ze~Qym#18+hHKw|7F_veUJHgtTsA1Mb|23baSEJIXyY zohE15rR{-?4aH)qzC~KhPGT2XHcJL%ZumHz4!h8)qvJ)54@&9?+EKbN!5MegW7eSc zRs*ySj7Vr&nUf`@Se_OXNR44y>_3AEz$ljvsP8~;c!4HlR4r;{OM&iUYZ4D3KY*Mv zlN!V!OlKrt615T2HyLLMQB9s%`cr0?O?#}~PfqE#U6UP=mkB5D;MLo=oBN-R)Fc$@ zUbj1&`$(Y`Rn0~_JFg&v%gzp-$!_xO;MI$RzxQ>J3!K{LPfrehd~@`VSelkdHVe-l zAPHlb%ymbC!tOnM^gi!tMn#jlo@-&LCM%xR&oQUl46-4v)sT`BLNbj~kyUAk$sS#p z`Vo$b)TGq+tTKu8%9ErB@Jq{q*ns z=WpH}o-kEP=b3H+^B$Y5MUaMRS12)6+xc>Wo#-)U^Qj_YW6=I*lY|^wQeD*&t7EqL zz)c``A}RFk1ZG&01c&Z~)kflhhH60<+4VjcI>g1T|1ud{ zgT3&e?Lrv56au%<>_D2oT>Triw*0{*rMVVY#zI5t@}y0Vk|O0=P~lS00b zCJ8UZ6mFep<1T~Mrr0P-awUa*byl3g)E*+7hCm%JLqUz>No%OH+rbqGI?wqOjp2+d zfLan%U2dSACG=U7j$a;CE}1v`m1S2mDzF9gJa9t`)+O?g7_y-+4Syhec^F#cSCoQ; z;Zu{)9e&`r?Iosco}0g~Ebo+v+#RHo#kC-VBIMkd(l(mfL zZGvQdZzi7YZ9+PQY(2$3TlkYATtb4+x5GJ!o6vsHqjZ{zkS4OU8abM<=0(}*pf{`S_q9PpLNgK&z*EuDE zC5F;DoaMQhKQMmJcP)I3g{AhAPbu+4PZn3OICZ~+yg)vGi>uxz_3%CZVYn1Iz=xia z^3~U!o~Q#CACIW_9%5IuwlRQo%156)^I7NAh-lhWQJ?=JDv2t_I;mM>JBOpn5Jk>$Vu5yx= zDW*eVMmo6`)bTeKxZ=78PwN8~Rt-VW?H}C5)4wXYp0kp$C}suKng(f;BSXh1*)$xz znx+BkANeFUTEnu44J#o=Cv>+;hmsDOH(sejpElEoc!Zqu(}@LXz)yMcx5$FJ{&?;K zDo|HWM`8;qnN}4Mr=JKZr=OePS@@)J`m@r!d#SqV(b4n_Dy}-P7QtOCi^H)5ZXw)+ z@;I0yB%s1gFpvPaxb*}=!&b_VsMrsys8~29np4zV*s1h zwa&rIS;5Mx5{tFefR~pw_6i%3f_mw$6d^5;_;gA}tS7E@sBbk0YT zK!Lg_dS)v;+JpNmt4!%GoX$gOxSiHM!hb6+Ah8>|{)14pFZyKNrgJtgS zsI08G9xW?aURiO?U8b(p|*=P%Oq!67Mn4={|inzyHa!QI>j|al7)43 zi+7V*hCYS;#Z|ICg9m~P9X&sJKAoK9AE_eb0DsplPO-L6AOC??Or6-SwMxrj5xE?I>dEaTR@DM>HdAOKhV5UXZGaT? zY^JYnM0dTlcwkTkv8YpEty;=yiOjfyfvTnGA3!HF1Wfh5G*l?mbRW zKo>hXtpJZ`DivF%V&8n+=Z|3?TI}tcmy4^+SQ-Or>t>;j46>i(kUS&n>uQ=*HPXx* z)Tr{t7j?)acMq@R9L7b5x#Ysg`$fi;#Oo-R21?joD% znwpqyEj!C6S;7hbP~khsXDPTdm>v-JcV3i-gLEpD)y|M z2>)gdC3|g0hO_F5a34iFDbVR)awdM&swYyh!Q>$7g2{^W>B#CL?S_saQDdLzoZvxY zeeOs5bWu=&`lZg4jjo9^#V<|3XkrLhn~q4`(dgJ7*;vX0@*Tnz9r&P`B-%-Fkt@by zXjPGhe{4THBtC#J*Ix92G}^gRtxLdj(<>RHoHp4O33WW^lXnG{gE#c zS`Yq;nvk93X*YS+O`hY67ro?_YV%a}Du5h20OX^Nv=L)yw(UxFc~7L1&~;b!YrCWL zRV`YK`n9F#(Z_s1Uh<|M?^LfgdAvD2+x&x`b1!)=q4IGG&^nuGh0Ia*CjIU#0HKKe|!7;!fh?@ z&=y@=pvZ?FJdE8UWMAYwq%x7N0Ha8B36(}fb(#hX_E$trI-NYkc{I#$emzZs3}w*h zs}3@RZy z{b*nqU}8bNnqr>!k=(R!^;`Rh8+lekzfV_T98j1)VPA zmIjIvC45(HFGdrA8HIHJ>aF}+=;1@tez{$G;tDWcr^(bv%F~QvOZOc6od>t~lMfy7 z1x2d$KoJI++2Mf9E*=}bI`pv}?IY3&o3hM@k1GcU^qdB9oYTx9@gD}is#m33m*ZB%zSxO=%P7^r9!j4- z^f%mD22w=r*s2zc9=-{MJi=tq1ci00(WIiiK6#kgg_G6O=?wJ0fQ{-;>bKyI-4{GY z5g8=XD{R!Bhr*jl1Z$(8s>#BT`W`KJTD4zWTQmXHs#~m(771Cjl8q_K2}Q};CYobx{AKFCLbus zvRP4J-iQ=^7r>T|VeC>_Owm6IWhjn6*)k?o#8OfdLigg8_7O8(+YZ&GZ}f!%H$c~n z#-IggjprC~pwm^*dPqd$EcdoQlS?qi>PP6RQN%q#xx_D~e8)H;h5(+5Ee4qN1eS=* zVtOAP$?#*{3jgTo>cN)!M~7CDe4~*CVyT93xOkKWUdZi^y7KgtxT{L_Tom1%FoK)4 zh#y)w510X~LDZP-&z=TFBs3AEyMk4g(YDMWly(x+ulNLU*RfVv5_!=g;QIKT{$=&k%;t0iXdDsQ9FW9;5{(JSZhYmRoUJkf;xOakGta z#CA~%ONQVz85yrygY14`MJzi3-)B#=WQzHo)4@oHn~m#WWW)U=WP#x;muUhLG*%Mx zBJ^-E4n~L_S9Eldy<}xcA8ux=J24lJ&G$xx#qm^9sMTlw9;6J0j5+G!{CCYed$KYU zD#?R(W1!U!EnM{za?CPh1$yT~M}Fj!qHm%TOQ*^K%N=)QPpDONSoi(CBy6AsPj-tn%7t2e+snzP zbd(RJC}3}AWb4YtdS5xs53ZKM%=!H!=l~v-eQ7;G%+?8t39xl9^$LZJ`QBzYaksib zz?JrtHE75^NK|AIMxq~eDCn*Fzw2Zbae%T&CxrPgD#$w%k~!QIQw{{qnD7fNyIZ-v z>G)l`XN<7zuGw0elL~K_SVVvx>^v>bL7F_vulo4AdkOiJNwjREuE(<5Y(&K>tms$s zNzDLE*WWS4zwa^VkXGD`gKYYJ!HRTvn{?7Fu&XXCsWr;EWkxUz))`QBhvuKMFJ#H;=wF5LFx2+zIhkISR4`cux+ulf_p0IU9xm~h*#A3fxlVsssN z_=uDqcYvx9P4u(I@(KOiT}p9}N0qozH{!PuTI%Q3lsUmehL@>$T6>eeHsl@555po-3;GlO0*e|p(lxr+PH|JG8Hp_2?Ba5w zQ%3S@MOR>sk&rLi0Y&rN4N&c7iz{Q-QNQ=ZD)z*ZdTMt_kd0YSTV9*XnF&fTI#O27d~pldsf-*Ab37=1>XE*^N0>4C8fQNVry(?v`}fv2$Y# zv-(PS95u_w6_p5r%r(5hGlyD%CzdZwBP@*aQXO5$e$vyDgU_0lwXcC@WB||d;QU?j zS!`S4z%B0t**GGQ_-lDfag(a{;;-^gj3z$R&X<35e%5EP+4;^sF8|cBQ*fy0#A8$} z?7`dlbb5Az=76_&Hl8hk4A0;(aXgT>5Hm;4m!qs`|MWxuuLnoRKntF|1LE-Z=*^oS z`fp#oJMJc1ph5X#vXlG)=t%jLd^|);$UvP0w%-ZckFM_rS1rYI!i9)={j>vS2_ghu z!QPV!9I{@!R1(DIk8Yf2p;^zIT-x`33_XW)b@4}NXfz@hwIe`E)wLH>-kOMQ$OnYX zks{voX`g`Fj)nzyOMTd#@NJzF9~$l_>)w(()D+{XW0T{Ox(d>xX}U-T%lX{aaq7>r zQM$lV&7jxmZJkBb{iUngaXpBu9i)w>9334wHB<^GhY$|~g{U?KOx;Hf>!<)_K$^cF zCR>OmcWB}l`@#AvJpce8(|=`{Ku@OKpg*;58J7B!@KCQ+Q!@|k zime9FpGN)JQ77%4HPt*gLpW-z6GUyYEIRr~&I@SgtAN4$vGyZH4ItV%MY|_qyVGnm zy$n@)I@}{#d_L#&PG_Zs2xK}EYE0A%t7I&Se2Bt9F_`D4kO>lPyYGWuN+v5y&{Q@j zEhsks;Aot@IMaw%)Z;L2l2;9hVzZsfj_%qcEGCG-c^cndMcm2J4E+Z2TBCF#oB|9lA9JxFvkE$0P*Opl`K4FNTw62I z`?ROMFpCtlvN}5_j7IU*(-FD=6w7=eE1|hv>h#h2Vj?~mg6VsrA@SBcKjx-w#>2jN zfma4VPhx>f8WM8z6jb4ta;o6tch{K(=aMuJoKXS4WaB60!a-%#R?10;{;u>rZzN~K zS6fN&)@m`xZZOOtKmO?2?+Z6SyZ%mX`EtUO(;Ios84&SdR~L;_9WQ6xw{@E}9pY$| zQ2<$@$yD(`ijEN5>YpmHPjoClisjhV|3!||9Zfw#J+2XkDS<^FrN^{St}{9(sL5#~;zmnTtR?GE ztWatMG;`DBBh*qxWM#?;VwGC^nhoM*8BHd7!-jns7Z*hZ%jW0C&iWmb-z&xoDB_{e zj=VA`UI64`zUz!{IK&#vCs@)I(~V^FOn39$Fd3Xrfoi4-#V7J}y6|)jLP`!ma(1i8 zPe-7XZ&5d&($O;8#8PY1iFRNI4LIQC;(S>Em;T)XMTh(<8?Ke5L>DE_c9PgWtV#cT zm(4aemCKaIa$bu>Ioiz%?@VmliOwR}qOUTQ==M8Sf;!C-UM7>#HO^->q`%e4s?#cQ zv%p)ibh|_NPJH+#x>rF+CxMc1QoWaV8i%*UEDm=iUE~9+sDq7Qr+;ATB6|^LpJbj| zfx8*GGaF~Lh9Wl+?1-6-lkg%?r3PT}l%kUB^Rgd-506pmGFbTUfY6W7vdrd2$cZy) zV}o_DwzArp=B>H;S6GmV6Z_y7IxR49$K$@!Cl}Dm92!F{up*thU8&+9cA>i>b62bx z7aGyOP(%KzXJaHV8j}R;c1MPvauW!ZYrDpN-N~n1EuHO4WUGQEU?w}yuG(S8CZYsC zv`+FJz9*iJF4OD6Bwu1_KSy1NrtFwzzb7U^t%bf`6D-^v2~=4G(FfSR&OP5X0MoO2 zef?V9SM#Zv0>vOf#%d}VI95sV9qjssLQpjhvnjd=HgY7rS}T> zqE@jSD5PiGvp_-Qbhx>AyeshX7<<{#Y_rpMM>afCmo?-QavRR^snhTvdTO-3Se zq6GPW5{ZD{?nWQ5hkGRs(L$zmi`#Ecv8t(apzm1DG`gYae-Pny+%(M&Vql`(3FanTr8f55cB;udcdCLA9r?iJ#_0O%L5)TxoXR$)37!vZoSv=zIkCF~n}R7nqhJA9mpv1P@JIAKf3`ygPhBw;;~f z4GmI{TQbGd8taybBR_)-;7*zyx-y-PBGR_*$KIM&%NM;K*CGjIq2jw3r^A;q;vrEX zCytlg*@jj?ab!ik2S!t?NtxB%x(!9%TuKPSf9Yb@6^Pff8AL?T!=L|%1vaNw*+iWd zB^v>Wk!|aJ2z79l{U$lfv(Zp1x0!~ij^X40DRTzsx+~G1l>+cG#l?DYp#=D851=%( zP%`_F_?y5_VcXBM!3AKCafu_%|6FT%E8<2j@;aEQCe1-*W?&5f5h$rHyCFh|xVN@L z1gyGWO$nCO4!-qZtq2Ph-|2LOJ?`Y_>GOkj>y0q3OnU7jwf6-gFFVcSn`{`O|(X};t>qiT>?m@wUONcx~B`{YY(2m8?54F7G z>}QFIcrS@|W<;H-Q_;dnZDyk|%hQEn4?esjxG246+`F7o%_Uf2m)pXxTss%HZE9-o zw!`Cxjb+|Ty*Rl!T&!2;>a1ALhRNB{w92oZ;%;5KOt>;@DVd|=FllW0+9VTwf)3Oe zRI{ATsPyr5W=keBa?8e)N<5!nejVgtKSItwNR`WZ=}>@G96!o&N>m(SS9NlR{cxW{ zDf{^XGVwr`7dEAMvW*z42Ugp*puRPkaj_nDd2dY;ATofp+D>s3Cix*T5@o&Xn*@P3 z&Y_$KhCtw_oBNzSAP3KW0(09#BiH%e{cPNKoZ*d5|G^X-(p}j{lYL-{^o}mBzSB%E zs2X{8p+Atx<);(a7ZKz{{eLhFr~?kPGe`>ol= zA#YA;M@`lp9YO~{H5fnxDcTQ>xMK>)1*H$A&qIZdNN&e!q-2gM`7O5Vd;$;r7&|_O zoQ&CsQ6+hs8XQ^CotJTpP>j?yTAa2Du6F20xT!Bc!AcbGUDo155-W z{hG~+P1LAzg=L1od(fuJEo*6Q^2wVQZ+4Qu!eL7>jdP(mowb#13F3bCObJKX87RR) z#WWHeRot4OA#w6|VNA%&j0s+AMzh@767oBn4f3yUO7a%A-_B93p5LQdJz%CDJrbs4 zFfSKuTi#=(MA~Z{ zQ$DU#VUzK{$kMrDhQMK!{6^wwV~P3RPqg{AOuawb5vE{{v=g5Q7nzw!vEA2-solz< zV^-v{X)JLbu`g&&WqpbHEyV<9?kov_pk7vtPzqrwCbJ|~vN3)(OgFIFV!z0&Zw+{o zUZEawM$zOI5LXRonQIYSg5m;#UGF%-DAwr*XEDzPS^f##G?c*WJfhpwYtOURZHvrE zxkg25)mKBzHCl$#<{?Yql-VrXndAI^i8}QGm(Qpt>pKi2C(WT?tt`k;v_a4VnDatDJha%Q{4rZC9`Y z*eN%(0xWt>EF{#b`8-0~NCe~M61!sRRbOZmfw*rd(YD1oZ@V@Y(#4m60OtjGA(M`c!q?9V@U8uwaPhc=_RM|w?I8s?BsYx zm$CjU))JCeU@N13{a!l_?kEK8Q+8=o*(vCTZ}Q2m23DKWR(E!iH()5xrOl8J!49Yd z>}3k7iy_v+%syok#ShIf|KoC@V}ys<_sOg2^rBE$42*%g=9$lici}3=ir@i4<3jik zE$)}AW3}rDo7wC;af)Ws87T_U;gJJ~VL@u5iv79AC_wx`o&b5r@1l?1T58cRKZR(!Rbt$HH8EJAR2JrTX%4hzY<9~0QaR5=a zsB9*fc13Uyo#g6;V{q>BcQ&DQ-e{4zqfP{j!92uvFX zm>4V>il0xZ%Dj2*P_kN7eb4|^{Sg1zd9+B6q~A6;NxEqb7cHAMICe>ha^7(nILF1^s@R=a zx)o)&Bj+tKSEIoh0nwVfP!n*th1B6x_&-1mHt>%uEv1+M0!@T?f&o?2`E^hBa0!XA z3Zf}V3danx!lz?cV@+qTd4tr$Db& z{RyC<`%U>?Xxrw~;V;W#Ar=FlJu#LE}z@p~#*1~c1kK^aNIyr{0U(c3v^)&>ZcvN={?0Z1X z`_t26svw;8wXD_FzZlcibK@+<2+MY?wLrU~4P1L{W^y>c&43kS+g3c1S7-YXqZy;6 z)33=3lW%*MCC~~;dPbPo4nOyj|4nwnz5 zG7iXs0VYGfPI?8!@)WjgtNZ6acKq+$UQ56I^B?uc<%$J&3U&4gw{>$nh36(l4sTv( z)$>xrZF5FGYkB$w`=G*Q4^8p8 zv^_&QzE51W&HPGK-}uJpDx!V&gSI^L&HZk=VxU^1nDC;hS5E19qOso&qIFb}!i|OE z;?lYM+I|RIKrPz#VYiH`QA-}H7(gnxwoBi7r`LA~++mh!)zDW6PE3)W3&QtoV!|0V&Dwiw1COF+UsYz3s*!N!6fw0l9cp_x)^k zo{beSMw%}@7A317rXN{H6L12-66~D!NZ3EFvqdjCnnEIBu9rET=O6P4XblH2zz%hu zU?jj&E#d|6?GKOq$8Xi+pNjSixX$vPP6@_sj5N>@qi^^2t{(o_ z@jrg4KE8VR)2#g#Ua{{J*L((&zMeq&_L%!75&Ye?h8K)ib`0`;Rb4aYFstDkTq;=e zr2q8rMgQsX@xjqa|MkIfd(PGbb!(gjw}w9~xy;M4Wk$;AEU-a`ooVILu-i2Qx}&!D73_YRwEo5Y*4rfrCCN%2LO%n}F4HV%tl;Ph!`oRr0w9#2-iI zyK^p@_?4jU8SrLiooznWl^XGIK(csYKf=6YIcAVdQSLlf#Q$tL8eQwGtE#KNohyXk zO7I|4POd30>nJ;0kZ~2Jy+}T)9fIU^n6Od}GF04>Nem2WDd%}sK!=#~Ufn5_Wd3R) zOV2+dVG|R{LiTu^3$!j0kg3%pO!EurSk>oin0I8H0?_8ruSuydyT^j5VC5c-dho@` zd4`%ZC1$~mf)iGwz$C^P9+iURJjy4Kh~#WJ8EDX9L@yQ8piG-7bO+i_)tyd1Ou5{K zq{&3M;y?%;%Cns&A9L_TIGd-YYhKJaz0yv$lF88Sql;!eP$oQ5-`>6$0rgJ#jQz|qflND2*WwqMb zw19-D;HIiGuAZl;5ME57J1`Pys|7C=l&rQGK}JEeA2~%8X$~1VAbG}hvB<_y=xlmP z9wk~O-A+g2siLIV4TNq%Z5~ot=|T&f=x>cpvPQfsX3Kd#h19I1r-a%GfTmZFKN54c zg4~s*1+L>fV?&GjU#7l-?v@1x6YzMwwP-F&HpoE`uNA2{!Ju?y?>}ybp7Ex7t=J_h zVY|=e-dWW~xhugD9TO|{omx0XsB(%enpBuG1%`RPxQ=DkghqeqFZS0dl)n!?LRH0g zB4u~HvI^aV9EEIo$i68em8{zml`QsUlVUjstpAh`klvBWD>-~~c%Y8pkoIkIjhTH> z3UD(}-_g2vgaZ=z^Go0^u*LfMrTPs8H(9in-nKhJg3SN9%=#D?j-TMK zpYhil-6u@Bk9ohLcF4R~nD;hA7j1)*uTLihnNQn6eJ;!ue??wc4A6-p4}Cf+EI_%S zFdfoz&7as9jka2v(1p%)Q7q63+2(87bW*Nxfe(e=Y^zrNLQ6o)FbAq z=*Dt7@9&o;t;R%t436Eq_Aaw-`yEG1&^a&EE-uH+`zvOq>pWl{(S1n3=X8T`3jz*( zwi-4svvs^=WGtAjj%AQ)>d=Et$3$4J1?7*QRNORKhiWL3P?!rxwYJ@dYJf;}`gC z02{R!E!{pCXXC+mmbBN07;)VN(D8483{y2I{jKP1BAsjTssNH|rxADP!}r!fdMGo3 z&>C$I$!4LODx2-B6X#_74I#_(=)Z8JC|@$vN@@;A5GM8ZqVL)NR_uX4B(A}TyoeZ7xP z^g}I%)|dC@oFa+_NFkeVTj`@-IgLt;LoXNqvO4+uK8l^YMgoAk$%^^{Ii^Gf-iWmqwNXXp8=IHBf19C%$_Tov~ zO6dWQ-*L9>3<}K14UG)*&t*DUz^`9MTLd^U;t>`>EqO&W1Vgn+x!5pMyFJJ=8%$ zvS|p4q)nB2OxFq%mnmo}myl_N>s@iFtubZC0!10=jG&!U30%*r5Jb`~80$3>43e~|vb9k=k7Hs!LE69YQHBPK> zg44B2>DXr1s3aUuhdC%w$^LJ2ko0&!G@A&W!To6e>$Cc)i8#3%a@XrbSm*rhOEOZsmpS_(;dTLGayJlF z+<2obNJp=lv^vsy4)%ph08YAqGrC|mVIfhVtJ`Gi-$^w}1NN&k3;0(ypGtPs_;*@+ zJw?y(^-J~HwKFd0b-nTB+?_9{>Uf-?o>>#TwZyILXp076p4s(exY$9KpovOk3ppy2 zQxo!$TGbkw+O3AAYV|ORGph~JZJ^!}_1Slrxd{D@&F8uvBGBWMp))-TK1hNx<<(1f zru)Rs)+|#Uj$%3P>mBH_ihBz}kYxg=o3)j(4+pJ8J9mpY)5PueX3g`G5$Xq{k|6(~ z*faebDJNY2T&bDBOt|H*p<-sBo~J6PyYUH{fs^=Xs}90c(=V&6U1nheTgWu;JvrRo zf8Tv__;|nD>-9dg6mLMj)Rg%FLb&(Q#+b4lk^9oyJK2O?hj80!iK0%Ld#WNBx4MP!D@p=0qlty ziJ~=fk{rk^95RRFe4_PLrRro?iBM*O!1BdSKew7`bqjB+&3U}bHBHsw>yn{Y>s$lX zrP*d@6~q9yjm)NbZ`#A;p|LNc*E*0MkZ`xVp%G2Mq)WHF5%p1=3B>^=fQRFf>zNu@ z^oOlF>Y3#IA0^vcTdV`KOgCdIB%MNnY-DK9x+kbX*cK0hvEGqy16DIvAoq~oO-L1V zxe=uY??soU=5_hJHmXUO(;MyEu92b})A9|24jY2Q+7Ff_vehm6ROgd==$62s>s-K= zjRX5R;uxK6xXyrX=@1KuFu>l?+CH&VOZ9>W^)lm&ZJ5KYGJpbggKVZbmpd=KbU$Jc zFpV8hRBUP-DmNyjo5j8|ob$@PE1sY7SlT;>-B{5PfHl>eoHjR%;V=XFF`s0%KqG#j zy*v!ZXQc$vZ!1#3Dh#!N{+whmLoCeOD5-di+)5c$*exGwaXH1IiuhQMZ#7m$mZ)%z z`?1;73J?dzNeBE@C;}5Ns!;aoIweI#WY$`xR#OtC)pWE20;_a_wfntUWL7{SUE!t-~{SXCy9@l`#b z8OmP3NVY|b*lVYFDJ8JU6?+{f0^UtFHgYSY5TNR7NW9NKXm{+S{QxYG6rDQMoE|dD zvIK$oJ1m$0=|T$|c&w?2>J7W0h<8gAva8-W{Y5QM@eLlI3C(>zjr@pXoxl}>=?1pl zdks@33`IRxwetLQ?**;W<;qHAlASeb7tEWThW&Wpji9HUA(L;Wpi#tIM~CFkm$H zqMHn_bSJ1eMXS~fqK5~R|KR<)h7WpWN)aSm75xwwp>* zzwPFf@63&mnhVJ7Y)dY{8qB?f zTLlfg<6k8yd`MO&2#&jBqff5gyMw7or3AyIhNYJn^DSFnJFQvP5e^jql`lwPT%dPK z2EMnG5*ZHh!{z^v7r;8`m}$JKV!RSwjR6Y=e3|(qY%klktj4N#{xGV zjY-ap2_s$|G^^e{HwH!pAPHfJf&c~Zqookc>f#e{DI#}Q*I`TZ(h;|<*ef@)g*Dd8 zM32_iZus~+`$TancC5Zb&h5tJS!9{W?NUHk0D16p2nK2*QGM#>(uZ8T)9HnYFX(FV zO^x%|39fjoPf9O&Gs&!YH$!Yo(fvO)rWJbe36jY{=*9($K{^WH^cs>Qsn@6yNtJkZ zUE~GnI+cuv?Ck}qRJli5#w-Vo3h&HBSJ^Vb5ts?6m;8;_j`Pb4$ z6tqfiVL}jma)de^SPU&&QLoU1sh##rlRm-GG1G(A5qFu>vhFDWKU5roE9RSuX@2vK zJ=7A?WeUk7mqQ5gO<@8)ZlF&3T>XCxjp!yib5rSQLRp95CVJ74wZOs5+O)bKQK zLd5rnjaNvMc|r`Qhq?%4;PRbd`kkaewQ*h1^ql~kIXGs+8$eKIanh5|trWlF1PNKl}qWN&M_pgDpHdeqwS zxR5Lt>jU!r)`t((BDAj)+9sV)qYKtk7P&$}(~BK8C?;gbMH=fP)2Q#Q56$>mH3`QP z(J9Cp<~gheS}F6>6kcT=+!0iz-3lmG4BZ?+Izg9v9}eNdj9-dtoIx%h>AVD+a3^6z z2i#c{wz{C|M7n|61Cddb^{nWJqUf002s^?!!l~s)IK~swDjG!@9P&0tqoM~9^Fogx zbdX6vRwR!fJqDQ9JAy|UI=b^8mLjJ5XuTnJ_iUDmsv+dA(ni@KDzL)zzLrp!S`Vt$ zL0nqf2E9?B+Ro0&(bL1@r&P-A`O(4ClY{;bPk%l@tihd4+U*C@V+VvHbdTNoV9Skc zbwGbA_+-eURCdk4%imT7j2fPX>%I2Uiy2bpL zsgl(zV5DYP*|nT=Nw2a=MTZz`h%woeIzlX$-H?zt;!DRlEcrfZZMKpf$D_b;Xmaqd z6CYk+CFP?qe~Us2aoHwP4Fd5|{({sEhOLV3bpuU;X=I~J+yxv? z-B5H$aeJnL`~u<@fR=as;B+TWo~Zuysm(y3mb@64QWhsvc>ug} zz-b1+IT(Ro4N0W*DQj8=Q{o2IEkak`^fW!qfuo}mHKchd5{Mk<#Qd767+y7Dv#oJz z2y|#RopBjCpP$w{3oY`qKD$v@oodx%-B<3ZwI6WubeOt(3KhBbPFe%^xei$k#bJ}B zmnow0Y4Yswm~yHrwDyKjD6qxq0@%A#wN|?9Ij4Z_OpC0KgaYGl8Tl09ncX%RR?vt- zLjuyW?TQ>SJ}&d(ob(;!tzq2FwM%<4N+EE8=0t4B67oC|h;4uR$g+CQq=855hHMV- z6sCmYv;7Vpw~&jG%u5Sn;EBmXJxQ7rV^H~gN=Hjm|(H0Ju8GE{cCWwHqv(N*g=AgDn z$^Sr75u+Hv?H_BJEd(;p7mntKQknm7SA2zHoM_cdt(RuU{Va-@H3{`|hOw*Qc-E9qh(F(zA?w^!&}c!xP`WhI5vp`0(n3 zFre=R6Y`6*00MEgCon6^&MMiqu-_-aI^4wY9>Ycsc#H^_DJc*^>o8cT!%Npvmoel! zbp_0qlL=ewG2M(dDxgC^XfzL}hwKX%w^JRpnh9GLzqK9@CZmKGtwV)uDoNq4M$0;} zB{>IXYO?m*GqRk_Gpa;RUSCFMft;6GotBVC(-eZ=vF*eb;N@kD7XgNJ9gRlxfcH>E z((YK96qD_W(e{($gXeD!UugV1I(Vfdk{5?qSNEr*gX5pxym}FykXxgH92@srKB(GV zXK!!5{xw}iWosEt#aG8hQH*xQl%qSUiFMr7NXc1&tC7N#7?b`sC;fusJ2TXI5u7ZnngRnwyYI69+4%$WcQP3lzkX zdY{?}!(Bc{xkHzsh16Q#fqS_^!r?XRP|yinlM}@gpe&POL}|e_Y2ecyc4s7BIDmpj za}>bt4*iYABdx0G>M#>x*G=FMt}zHbjjqt19fQ((Y*szgX&#=0-9oE~gJpa-wJssB zMYtmP#&%=B@F&uj5%`ityE=k4;(Ww01d4;BbZo#ZqS8xPU%U#5Ttzi7_@dqI0uXfVk0u`Ruc_UwI6>g zRQ0!tu8~J~Kxx0n+ak4xsGDU3GS&eQG={9dCYXh2|iu zOI@Vt+tIe&aNU7Et8Cu;d#lq$J6=UR2F(N`^?T6(Xb7O2PWz|1T2jgEy3J04cjnR8 z8HcwNyfix$ND&KjtBE90_y#jYtwqoPyzi_#70__;q|HlqB*bzZkVz1!XW=+nIbI|y zB9xfZS8y=#wN1vwW2>83ScQkru~HAOc1k2K-QPwiV0jWp5FVSXPj@sPb`l|kOnZ*- z!D*gooF}?ro%sDXNBPFYOeca>;LHakJ`|VvVsLI%9!w~ul>f;n{tc!dm z(A6+u?u1T^VkNkotWnRi!%@_R=VU7H{6^QAU5gQo&3B+&H3y^QECd)lMbve&yvme6 zh|@9y6`$v>Ilf(y3@3Kh5Iq)G&qmWz0J)m(Trp@0v*FH|p8k>zFw4cMdf(Q4_7+$1 zvuaW|&|$UByF7$Bes^=@iV2*1X30G=N|`^&@vlEro2^qhDM8b`MvVw21cHUax8Enu z%Vb9g1}>((PKSV9hLL6B%&ED`0l-LuJ`DyV<7_IgkH4@EOBng6CfjxpRrVpQa_mfv z4|K+TecZ(k?nmGn6JsLo03|YQIbpy}CNAc?Pprh0;z2%ijDMQtm;IBi2IPtbC`xoV zy>TGLPBc?4RV6qd<3h_0tjGv+P(S^AiJX3#>=@U2!ycduy*SGEnd480Nj8m|l%2e6 zf8~5lPU&amoa-V=X&cys4n2I)gD;;*M|}e@xPVNSzkO+1Hf@~P8Jh|1vJWF!iO6>I z=;4M#9Ug+qQFsxD=Q}7JNq9M3MRZlu@T69l7z?Z*rUJUQT;bI{$Yvp$Y)nIf;Vs{` zgb9wvQ;fH+93>zZ-rXEXs2dW*oBjsK4`p7*ie?T0k8w)y6QlgGmVx!9p4K&BbjYIs zkeFx(l5mHy!OZts<;zvZ>iL^W0W6rsn64cdJ2e=pQ|=d(r#~l;6%-I7*y8Oq8&Q!k zhFW*AFH-tpovi~@r>%2RzY#}d5Nj2#2tT?#Qv^TXh%pNBg?N;S&ZtE7Bp|3U9en}D z#od38+#pR!Yz$ZQ!+>4mG3mLaNH=RGM}*n3Z?MLDTA>s?$aT?`)A7a3zL|Nij8X>H z9cmBS_CPobclwx;ygC(OpZMHCYzYb>#`zv%O&W3tmTVgUszg#0%0Ku)XDrFH##x7e ztdjExX2|2BJB1Eav`k{lBNFjwH2qWQZLG0a{;P1Bh1?yX%w#L=Lu0u$MQw=&`|q7R zUr@VS0eO+nRLhKt6>+!^=}Fc7p_yXGiyJ_oCo!AOY`{b1aL_OI6-*k->;%yhRh=1q z)-7C;L*;C}oxExMCrkeOI{Ab|tY%S?b4BM@3j7slb-xbj5(igsYpsc%sDw>5 zj10kwW;)v>(m$yOldhJLJS5(*=JsrHQcC1-D!-yT!N@o8k#ld}CICYH_Z2SMM!P}8 zzz#0YZae0zoqWpXoJ>_`7sC1*De-7BPp-+3tV1!4BPmu4(aHVW9D`{hFFj|qYp{^cB!7_?3rr~sw=}qWysE2?Lbbew(V?{Dty4u40 z$8PT84M-;_`GHOG7INS@d2Am^j#j%7H8D#>jS@J+s6>$@My81TJbIWMzIpxT#VzV= zbcKN}_ERFk=wU@}3h+IuC3&j?)sj;IXph`3y94`^ZIp=kQE0X0PhZ=@9Wfe z%^V#0E}R1`FUTu5>+u2JygNxs&(b;yGED}U z#Yec80fe|+vpKFPemC<(RC+3robDzy-Ix@?Rn-F3V10UTE*r0Egw!INh&5t&Aa?Zu zKSIT;m;KFzxjoE1FGV$~ljpjz$Ct5v)T9Jar+BaJ4O}t=ULfZpm)K46 zj&#UzxeK06%S!ZekCc>i-ln|;1i)T)N`-&D+1iU#SDy@pc3!(N^EL8sF;f*GT%}e= zaow~FfWc%iTAJB#SMHp8Li<2EhX~ii&6?>`ENTjImU^%VUe2pNDAI}IGWzId)V7^< z-3o;D#)9Wu+*NF?0!qR)lW7!kX0idvB*mgqH0S7{vvl5NH~Tj~lNy};ZD6M<`MsMq zg{>#@h^jTClaV>g<sy9zFBx5*Vv99UZzpuy&?SJSeBZC@!|o;l{5r&4pK?U150V z<|Q&K(LqedcY7(@_`_*XVsA}UPK3}sG7#zlxKKkBO8(BXL6(2QV7^h7ayn^9EuDkY zm&pjr1wE(pA-GOM+2<$mN`3Jh?tlo2d(r(G`YoL-hJMii-TFOQ;&fI3NbK(@w1RdV z3IgpM+mShm?AK0bcumlHH<3{=#Tlf=u)AZY8r%r8$}7Pt>uN3pZn(LZ2<6{RY-;lz z1BZ_H>6`&#v#9B#@KjVjC<_{PB*yG&hD&mVMoz<|s|Aw~Md80BVNq-CMycxyLxkxV z#zzrO*L3Eqgb>|bsSst#w*oy5qkaoAT+B_>Sy5<9ppUvu!44ht4oa0VW`P}wQt6mo z)%)DHQrA&)0+dOgLiXS3a&BzC2K6Iz66d8t8(iIkIZ58z__3-(O4=Z95Tzc;y8Z>w zR%HE?rhJ8;2Fl6$ryNY87s#LS$7(m|scGhCk4Mc%pK0IFb zGrV8Trew``zWK~CLOX$IQR)k;$A5^@GK({hFNFx~p9 zGaguRsCwdC0KrP3Xr{Lp``MspmN2xZoPc)~{PI z{;sv+!(t(cE_#PdZY)&2%&4(j-CpYbYZ%tm73z2yH{oji8a6sNfMW9VxsKIuH7GI^ zS!u%p%N4bd*P3U)SHwP+^FC=q;KD?)3n^CKQwT5G*%bY{*&2s#oGlf%p%XY83+{h# z?)$R3N|-+BHhJ;gyWWAf^gTk|KW-c5DG2i7m!!exlgh8X<53I#R3++?d4vQ*sMZ<|V^zLzS3**NmL4RD3h z5Nz(ts&Ix_2t|pkJU2^r8iUi5zDky@tJ9UfXTR&2!}nSxy7!za5A3>w${Bj&Ij{Vo z_@8hr{>L3`b>Pn&(ZG0OFpD1Jx1qDP^{sX}X2T?x=edZc-w4=fhi|s*GPA_CZ1bJP zheEX)Wo=hoU|5f2{UZN26DPizK)`j~MCKTv_;4G2RD$Is0gVg-{cE$O9=A#PwlzO# zQ9)fJ7df)04cXW-UNu@kDP+n@$x)2waTu%^VTpYtlI&=1=|AV-gR@M;s1%TZeMtkp z?cv4R*uS6P<|o*Mg0(Mldxxsu`I_~T7=gWCzx+2k=%Q{=HYt{1$Rnd8lore|<@4d2 z!viP)k3tQkLIU1?Z8Zy}vH9iyTxNZM0yzibub=T(EX4zT!ccuk$hCmtDvALS@7Gq0 z1Vkese2Cn(WB=yN1Sr_pBvP(VWH**EsvB{YrxnhZg9Z40e6}3AcXUE4-fqyg`JyVE z_CkL><6XlCkDM+%_Y^;RtIV~myow4qzo-+k!~H99M+OCf$n_a<&L=J>f8R%Y-|kv; zI^Afn*_P9~IzaiFr8 zI=_+l&QE_3cJb2>{l6X@9fOMC**nk~yghpJ=7;{8FjZ!zZjnVmtHjUo9y`J>v^Kit?s@J#6-DF(0pvuKOiNDn^agG%ki zleWF>>{TA^A_%z>odxe`BKCVztC!G6(}wf2zSf*kXXojBNYLl!?HZlU1KWjB$3#0a zN|_(XlSYlMsN($E1X#lta7kVy`~~Y@xDGf%1ZYj)xajFu_Q77_yMNlxs7d>woG^pj z@>MR_9KU1C4J_P=5t76K9ABn0xb+K6mS?=KP58M^;yX?_=OR^Shk7^|DG7S3aW>2$ zL>6M?rt@^p)uzDz#8zi7k~SB_(Onz2A~m4m94NfJ$auK)V+z;Tg7y8N9Y2HcBK0{{ zS44))zZ(Hf}dlTYa==O_grnJ%a)_zlWBqles;4O9v1gha~tl`@Xf@2tF}UyOTn zJk}Z)_iIwn?~X zn0%p_?=zR(h2x5_GnS5BM=bMr51Sgy<|%{OVTp7iDh!pCQ?NlPy5?!vkkU2I#TKs8 z4}(Yjjh?0rdvCm6q2swtsJwQ*F)okEUY%N=@z+SZ-seeyD4V^{OPyZX|BmG8cG8GYjd38{ z4~024OILmSB%5A3rU))`07L}MJ3=4(+ewG%i&|eJD`p&V?wSabiQi$eX$taCF`vOy ziEmi7Ge-Jfr? zy4^1Co^npgFr3>Yo(yuReZ@RWWdAXftxdF4irfE*StxfUqH_SzVn?K&%nF-(wWP$t zaN%|KZ<3ypT9<1;s`I$jJEjSe&*~3XViP%-hSR%eVv}(TTy$)npDsb&h_3zm9NK_2 zbyOCYS!PlsS__-u{Yb?`3a4nM?|6zssf%{{uuTMX6^@w?cEd>w7m)Y zI3ot4gn#%}DeLYxVO>eOy3%BIcTZIpPgK_=OMs8A(@8 zT33~_&dY!GTTB1+MYBJBiCjf1UqmK1k8>A!DQ%>0*6psK-%i?<-?OeHzh_4F41CWm!f7~0PB~ei|IHYC~B5N zQvA7Qq*d4X2&8{SvK2R3B>&3h({Mt%@91>?E1b?M{-|=*b{yf{N@%TL?c0p}eou+p z#rCfAPQTdn-rTIo?<2mkijdiUV~(=l=)U>g-SyYx?s`u}mT&5deE;sy`nyBx?+&eB z#-a6=n*HDFnMUfFR>zMxWli1F_KoV8enH>he}A{&wcq`Mli&S<|Bd~E*QAseDfdSB z=TrPuFil_4X#+%~Yc>U8#)2c}pkMDBSzJ#PonPbw^j!uOK;1X(7FckqR0X$lqIPHU zTPt1p=+Wj^_*wfj&4w{w~2F7Q4Z_oYKh<`10Le$@i-Guz}N_kSMy@X;jjE&f`3HM97F0vc&a(|)cip+GFZlF-%k3@l zj}tZ5>Wy7boRg$JA>%(d^sQ%CfEoCpD6fgC%a|z+L<#Eo-!NUIpZR7ctexFSKL3|3`9ZroJQl*A&+-ZIew_$2xl z9eTh7zc|mwiTE8I9u?`G;a}}_@v4iYoV>%hGpL3*&j$G{&n6}jG$OIn>U1Xjn49~L zC*XXjUA%YKR&niQ3tiK0J?pFo2@Iy9^)wkn;`Bv`hls*%%DwJhj5JMxiL z0BJy$zkek%TZMdv_+I2pd|?`$$WeD_>qQRC+rS~Ozh(jyTw+-7FR1)r3ob!N=<&U; z80L%o9tf3l2oduowjSe4MqhisP;*E@bEM&G=`YwYrfnb%FJ`0k+N{ZnXEf{2-rAg( zH{C?c_nYmFdw;^k-RAo&F*l^8hgmuw<)EQDXUzs~iv{+$Kf!>;*#HU{UtJ^MypP&8 z9LejAB=opY+vdO_mR2WI-~cBX@qg-~p@Y&*`u(%zWYF(_O^BkKnt`at-khIB00}F< z294t2qL4sTWas3~i#I#TPf$T{iq6`|aX?=Q><(S&BUB>NkO_eNd)5!P$NdpGBI7$J zSU^|DG_xSE2mA1O>t?rdLwO&+%&x73N>MgUvtVBw!PXKTTRByGo=v99kLQYXoEv{a zZ~SD_+4x->m$59+tLr?&jb`(}7(834DF=p~r|5wT@R1!Q`@u>v>`in{-Qq{^x$`yI zEiek|3@TT8eSxwN0^>fR6`E87?t%%icw+Kh$Jf_BZnxgyd_Iw)R*Qul6Z8eFC{C7m zP*aD;?c{K(7uK{RU2#jy?wc~AO@e+s>N>9ZzEq)?&5sdH~(sU=d)Et|(8v zslPm12dl+~JC4rD*>AUUnjXh1ou)ynM$jcuMSZm7`(z7D=prN}X5p|CwYEhz+Q%vZZ=|-vYjGS+qWYAp}MA^>z^)*>uwSs zccrl&xMRKf*-faRJJpsm=VPG}anJoYzXD_`J2BXao$m*GeE36sr1STT7OfA!DccdG z^MH;TF1sP$bEjR4uLd_iR9%yjCYdj|xT{}@ktc!pDdUkm_g;z+D<@O-0^zxKoDz^4 zJXn@e+oa(x<>yq1?;nu_Ipl!Fv8@giXv+9?pF)l)nS`161>Ng z?Ja#E)vYqs{mkeC|#sQ=i)NUW={G@hJGwRS}Z{tZPYlu zCP$DGwMTG%z%1V2kV!p>Vmi>D-u4iDpgohcelh%JkJMh$s?#Vjjw#oj6thu|=^tCr zbBn?(bj}sPg60&xPfHa+YSl0@1lxo~+jnv~uHk*M69U9l0Z4K?u?~#M z6SKvNSDhCN&6CDQ4fHKe1@!V&MD#R5*IJgx)Og`|c}oq2+wR^1a*7*F9zFE@$CHPT z=(%(s$A{bMp?15+2eB+%pT8<+?paOqVgQD@ihJry9#PM!HmStKvw4=OQ>qhxfGeM8 z75hl8fS^hoKoI26$!5!5hU?@WX#4>(g+gfPhBxqU%&$(i+sD+mIoXGKJjcKmzO`vT zoOEo<$#g!1+#&sOI&&<|vH9(nX>@rE6c=G}#sx1&snIkLa-%OCQWJ0>*xXkmPA5a% zhkmnXW;9ujM*TuPjCUn2fL|zqZ3vEVs=ts9I?`6mI((}J_(jgxk%hmJZPsGpS42NV zJe!T7}D2$2r-MhLG+?eDd?N??A}6P{V03Jjz9q&NJ<^M6NI}ZFZI*ClLJ!33W2=OTXFKdH&|ri{M7L%91q~lNaO^ z*T-DCW9x4m!DQLgZEG;u1@F@(JJJE;f2Kgw0LJb_f1Tc0tg5rVQF+l9h-tPk1{2#( zx~M@+-8EXUX*sPbpm?iBKUEkPH)oNEzg#!U(KH?Q#oM;{y(*(LMn3T! z4(TMIHB@FfE}Px5d35Tc`8YVu^Y_Oof944ZA9M}o_(uY6`dfQc*I`YR%L&yniz`sM zD3;LbbG?Be<_=&)U#L&tw`TL@Bpb4-M*7A}m9T6K91AJV;YW1ZOJ^VvL>l{j_dn&? zC27*Jn~Lll>O@ShvdLg)M;$5JT{B?4ZpFGHV|;*g$|c7i(k$x3#ajVU)ifr_v)-t? zHFJI-F@xd~lmOI6zkg2uh5b?hHtx`IG!R!HNrhx zId;vw#mqiZ4(DW!jy)CA(;HPnU38Fv2^w@r4}tm*kK^&5F$RQr2A6IMwuj$$6NhxV zaS_l!u>fO4`@Zd6-lN3-`IJ%L5eJ-KHbF*k#Vb>c@P6w9^rwUAeGm#KSsoVw`Lk=(G|)i{a^u!8xW~wiu796O%EKdLkr9 zKPnlKG<}$l4a@`>fJ!<5Y!q6YD(}|jH|r`UqQof=NRvw<=JS66ifVF)If;tCzGdqan;kY z|86(+cdZN?r(Ek0G`XBuhE~xnwT70C#o?Uj%Tqi^{4Wi+t0g>=5%w%S(i#-7lW4Cn zT{%%b?7P$MpI&3ZgSNMtt~*?p`rFtLq*r4btJFv;LSK&Z4Qs??eG( zvK*g6Mzb@~p$prh47g>V3c?=&dBPztdPVH#;zeXi-H8s8iGZW(0yz_ zQB+~V}AbGwxpkeeQ!cr^#*7@}`7_yd17md?*hY@Bld} zBiwY2X@X;Jq6Ue^vPlDC?8I-2A^0_O1OYE_6%ka*DojNK^z`qA-azQqJoQv863JB? zE#$ED$~!ff&aV3(DV;Hk2!2~g+Clgoz*98UaMkYzll~cm%wduOuS=waKm`5gnmBs> z5~LJ1M-nM0Zsb!wadIix`3z&5VEWN|Q1y;JGy~q44S-bepE6P(KwT$J6RKtp*OJ5*&&y{W zkR>P%fPmI2&kwr}wyCZ|bW&rzCuA_Vb_d=N-1w@IbJs9;uT8-k)1ZcL)m9n9y@I}rBn-RiZlZsmUC<((QDOws zj<^pW8&>1OVaBxaCCaJA=fWd1%0S=Q>^vJQ3>>*UzBzi(CLe#SRR(H27EvGP*=Ux{ z+c6~(sE%ydD|qa<+o|XR2oessyA`0YFpgqX*I8+o*HoI$K2sx&yf3@jC{o2!>E|8j7O~}lJ`;u-FzYr(z zi{b=KGvES#aqm0P>-%3m*j&gOc5Z@FueDYr_~26S91ru7C6aS4Fui(>#xR{vK24Qm z1bpz5*ysC-`}`+57uQ=9EbT6hqt$NFMej6kpvRv>mOgZ8UfuJ0^&!d~uFEiQ^`WNA zTOqkt?XkXO~%%-@Q!kYT@eOYo(Kip4dnnG!PO5#2;@n`;o#rY)TBh>!Hh#G9B z`D3>;>(92X9`{J&rI&J+e+0<}`jLDa9m&*@foX+A7Sz+eKDL|Nw8;&?_C#TT#lrtmfE!dN8zzx3DdLT z31L|M`We#7yy#QvtJhC|eEIywIN?=TYK%O-uD9c(LU9Y5jzu=lBBYxjK;c;M=O%WE zCx%(HNjwLK(m(xWXD6EsAZIf_MH?Xs?eMjX1mGUNnX?zlxp&l}Mc(kT>hx<57(STb zp}_2PZT_^I1iRgoRXsNPmGdlxvb7}4qoHfhaqmuK>)vkUwxl~4}?C6t-@MxBV@2c9=qL#oMtuA!3c?M@;p#q0Z7 zotRNJNme0rJSIeuZ7C3l1>|RKa|SJUX8`cf4A-ghU_dPqjGoYB=wRy`Pq-}qT5+IC z5y{t!UJXlD;)$*I+6h`y;a&Yv?R8(mXjLF#$a(qqHSNr>UXS>|Ln!*NZO~(R3aJsd zx*8`~+sysf6BTT zk}sK~=`()YX>dw|@_(LSsaxEbHlk=N~<%M(NcbMb46(6zZ>As36fymS()b%i%G^^0tRg_q`3JeB>1rCk#3 z;P^7Jzt%|Pf%?wvsLd1A&g=FV+6X(_x)G&v9A|`iP|O}>dGH$@<*FfSBr>285x1K7 z`uC+5ntfb&KI!6(x$?_=|H2?%%cYfmB+6KSnLP+cR$N@SWR1Y!-NMoNR(m9mqc(Wc z3ABBb%~KTVv4_GO2lo)5XJI)d&_jnOQ|hzUZFQNF^RzB!T}~lZ_zy*fwxOh|dJQS(1h)s)0u+-5i=CaL=O-uX@131PCC`Uu zO`9gr9#D4*x_GomWnw>%uvEn$9i?+ox7xvzR11RCtnI@QF@(G5*gkM6*()SSTgvJme|gA)SYWq6ys&H;)#qCYEc>2Vt%(@osv z%D?G*-l?BNH|Rfrs04Tcowc8L5zr!Gr8rjD_WbcMSe@uooGjwaU;p%bK= zU701Sl97H3oX~1A({IQKds`bUf@6BYp0F{KtIiYP+Ffg;#p%WUhTPw4ocV2{$tzb| z%?1IdRMNsK^IUej(JCN14Baf<058$M>V#kfNC--naN|>jT?+6i;!9}kt}mmPY1fL2Vt#N;hGpWU8O{QDnthLmtQyDpOWo)GrAref}6yW zmNHE(jV!|~1Jw>Ow;*$PhMg+4bRj@Dw|M zoNkj-EB-{}2a|Rp$XBr&S_>jN+E6(rO^o_@ft_H_3%lr1+OBf>6qA&131cbt(=-n2 zfs;U_CRniXiw2nTavzozrV$SyvH-LmL!pTk?USd@JDN4b8MDGr>2f1 z4V_4uIFQ7gM`Dg6l};nED7NA}g`}8-th*&2SkDqL<@w++e!)H|l1kms<#9XNs4!d* zft8cf782csbtQ|u!B>>1kn||yS_v;b%BdTe7AGKAAHFUvTpPlo%C-2cf#$Cw87+WPNxbfIpbd7AvmEH|lMZQBPyDu;JYfDXI}WX2DZp z%Jv80l;^A_FU9s!WR`l`dqwC5ldr|{Z-wWcoz$H_Axi$i%(7DVU2_B~Mv0t?uXiqP z1A!Y&^f8!aFM0Fsj%Y=_af*OUa6Dj)YRx z-QMvK^TzS#3oqim!5wW94&CEDUB+dvCuzW^UXlaFNKo*F1z7wd%4{ns+H4U`Et3&j}K$Vw)s>~lvlZE?%p0Olotl9kn#*u0d z6l0I9UeRI>HMa?&EsmoK2a_Q6S~ikYovMLa@dQ>4MV>@7Bg+3pWUseM_WHAPm{$lz z_O`iWnTpken$7p3G_8njjz%4Sdkaf>g?+pPU={|)WGYeAetb3)a?y7A2^mNZugwZGoXs_Y9f9YDv_FxJ)so1Qox zWE$&5`rhAA_29b6MEKKiE+VETKAA`_k-2=XV4vD|&OA{XxW0->p}k$oSh`jPZs_pv zvx^l2`8~$j3FNO$k=N16w`w*}lP z$H3i|;Kt!M&P5fB#pW8sJvx3z{y|q%a7?NHNKh>;+;~es3sa+$IIW zjb&Z$psD`adg`yErEZ!p-_uY-))ASpWrfgF^n%o>Jh`Z`pYa|>(r%Fdu}%hIrXvq4 z6p;6mkJIT;1S;DN+3J+}4!EC?D&FBflkYXl?ZiZXMN>612FcF-Poz$-Zj|a#eR?!D z)HmORN_G$AIh>zzus2(8nQHjMeVI~mlB__+-|c>U$r)Daw8J@B>qlUnykA8BB8H{g zAo@KSkbX;uzN&d-HM7orAo?w4cn3sh%1%MJ6G(c~b|J3|wspN7>2aor_aEBo`bcsm zL~9KQ))ClEZbZ2xbp=gN7im86??Q7W@@)E$qO$CZSkq z-JwMVr&cR~euKszt0224`_C#CpeQ)=4>peN-@`;i_;v3Fp-4GsU!_{*5Iv`a%Z}a< z8>_>PbD(TvP?!MKl7D#IUsw&^K6p2?+HjmtxU+5GtWRUe$W2_=VKE<_D`JZe)P59foAwu9mWEX!N zdAqrPmd}etH=+5ci!aiIiib4P5*;dOE}Kl3AJ6rm#t_IobModa;dE&klmndP?v&he*1$QcdvqgR3IW1-7N>3Q6!SS{a-eU+}M5=(?vS6&v|+D zw2G{`d_1+^*sEK0@h`D#?PIjDQT$6frA5}mTJYjuj!|SV9nYXJNm=qyPc0MJ6H2@q z^`fsrw@wq zGh_8`%gJ=Em@Q-#7fw1MD-IHs-cJP4&Ig>T`tw$I>n6FmhaGEtQN_1QvxEUO)){9HX>2X_)^Ba3dJBru}%{rEb{#fr@pq!M*m7dB5-#fmv^m-tu)a19ao;z`F9mIDga zjZNb=5a1+TLLC^(*9F{nG&D6f3m_KTRB`i%s zT{_TR=F2$f*R_68v1f&d2~MR+p86ai(0s~SjxocNVCoYnKpdM;#bVrzix8p`9cX}J zZYDL!p;(3xh*-ILvBuH98Gjg49ef{TQnZ$-4uAZ%QcnMYO^~mKXW1 zctb4L1sB5ft6m zj-m5t3>r04ctQCIB}-FMLB3F&44lbH7A+d)9wD=g`0=sZZB5HAw<&f3mM8zj@?IXd z%J?iry%Dh4q?tH3|xHETX<$hvhC$?eCpz>H_$)E6w>#Ezk#Y`eXjz}~3+A1Lbhu(KY1(M^Iz ztN|gX4pvza9jo{pfXhB#Iih2cCOQNj>_g)R{YGnOVtQ z`);n;s9BQ_P!UcR9IQ&g`ClR8X*THdUp281v@%oaXK9}muex>9;P#{H7e{ZOC%P*LrJ7EY=}0@uRcI3`Jjx>&*UbO7 z-o-oa<{25#DKA)2KkK%Bt*bk+R{D18N#+i<$P4u2Hp4U_9l0PKc_DnD>y?fX&Ej2e zcSW@Jb@%XFq*3K21nnCpQEHqx@}mOPL?nj_Qi0*zBYvX-r}oipvVb@-ShM-VR0gK? z`xP(iH1Z_|Y0rn!48oMsdXYMwyH)fT8aq?OB7u(EuT`h}KAgRD20PHUxy%|E69Q|2 ztV2UO-yNP(bGv}Y7Bg>Pcf(pDBk_G!qijPhzpBMR$1`Tdy;VR(1OhtQHbC$~7*%KL zgZm9+w7#`)`6g5ss_O8V9Ycy?|0+E`rS=M^q0qjHKI@SHtPDL5-@QGUXaDrTZbwky zv46PGzQ_?IHSktqh7j%GgH~6JSi>5KG2XZ6SK5Kr2a#ph0TfZgmEtaqi0f=}B=AyO zOsSbI8{2&Mq=jh}6Hz&c4ASdby7>nYq1hy~K=Db-3CB!kHu=r{r(2Co^_%8ftv500 zU|vg4zgXJU@_B?N!2K07OD8S)HD!GS$B$Dd+tF7o{}QTny>c*BSg>T}LCvDy7^Sn5 z7F8;}PTstD(>~2cpoG{-{ss=I)8%NGjMEFnb0!0DhT8`1=B1|hp-*&D4QiLabth8ba7rF zkrCQ<()&+_IQ|&iZiUtl)O$e8$an}YIjYq}_jjHuZrY559rbz!9?zEKZZ?@-cByl4 z8B!wxCkN}n!UFSb6Z!H5$dJJJE0ODZan7vW7}>wkwe3|$r%XOps^*=XqhSA>b2$=I z0u_xH;9`SVb*@CaqMwd%+oAHOhlUETK4z1BUEkCsK;lpr9KqFoskt7$IXSQxaf0wI z7H9$)f=kn7Iv*yKs|Gx;)Gx_!Ia9EQojv)x#0HHIyvex7h!vhO$L^ita;C{;mP|4h zvJr4gwIj@DB1xIjnh&8_ZXCg1I;6mpYrwgJ#E@JsVU%#h_KMDlDsT}#T^5`| z+QQ$c*$3PdCofUhP;TK1a-atr=hup>+alSaA%nN899QOg(!7qGGnrpQHMzzm$%t_< z4&MI0S14yh@EGYzoX>Au#hO_+NjYU^{!qD^tmndlZqAq0vqZ{um)KJC9!K@$J$7<0 zE5j{DBWXuBm_IUqqasPB#L`O?13>*|;@-$EDbO6v3A z8?YSpm-7*L@2kEI$%u6vChZbaSqBm)hcsPLTbtu-+y|ix6p6rbTwUWlxI_9!grKTU zp?p2ffNfFQ7aNzfWpEF2hKYnW{9cc~!s19}QMyKVbxva^Q(APLZr{#dw^()AqMd## z)}GE_W{Q7Nyg<0SayRAqoMa17EJkv0puX{ATdh1>0J#$>ofi3+azsJo-vU@(c+v*0 zaJEp0vzc(Wy`K(V{`ix+ZjVo1zJ7Xg&{zMc-=Dt5vMKgX!>Na}RvnjhM1KPqH$b<` z6WLF;lJAqY?zm%KJTf^Jr+p$HMf>FG(T|E1c=qbe^Ph2I9Yw3?g4|5->2d$3rBWZ{ZCEgCz71tqLAfQ~vNhz3rjpN{yC|vkzKbigV$wBNG^A>fbD! zgAf@teG$Ev%!6aZWJ7x^xJGf&b2o`Bc;DS(Ge6!~hITN65)dmP5C}nSGFN$ht-}{I z+E{l`yoc5RTllNC>{R8Ll7dxu4Y22xF!(`MAoXFNl)F$xiK2vfii)q83~jm~R6PQ# zELvbNqmzhJ^zRVsT!-M51D0I#`swm&HSnqr1azqv177vA7P<`v{sJ(jQ7eJWP3{lM z>4GUqcr26k3$H4fe*P&PEe-yf9*Xzid#4tIuOp6V9~+Gq8L1l7&UtSDX**#8wU&DY zq(zeu)P{S;$w8JE3*(TT4neNy^6|PL5tad#CR87YE1Bk6xl^ z4uZb@s)k(_!v~Pno>&?lADfI_c&I4A&}s=Oz@~nn>!WJ#x}{DD`<@V?9cLElM}0Rd z?jxHNpap}0b9Ki~2G?skx<-D$0+O)5LzkFU#A;XggF@p$ybF%)t^B^L0OEVrI2WPn z5cKu20X#`iy^+;>y-!d8WZjH<&=CraVE)vWBPKtqM=;A9<7$IbMQ$gTzSHts4$}r- zfd?f`@EGkT?jmsMcn0GjHBs~N*Bm&Xa~gyWpWU*SCOu5qGKj^8N!mbN?APFbtSo3R{#t{`Gbz1ujgd0m(K`o%p({>OrP0Xfkuz5( z8AP3o@gUawd-W>-UqGS!CW9T9Itp?;2Uossb+8J>Xo!1{5YlD}B8&sKhD+L=(ez_J zpe227y_;YWQiW+S15V_b;Mbd&ScG!{-H6Ho%r;i~CpwOajz^+#CMx&YM`PMLo77^# zDsotv!|8B~Vx zlt$z;{2@Gg@dVOb`#&+ zA4!^7Ax=R*CZ`-Y0Wb1FK1(NyIOfY+=lM?u&wuVeJ$%uBdVG9vbke7r`1QeYTc)XN z{f)|<=aea1ztCBte|y(UDK&%M3@DL+&b^S~l6$BD@q7L5)yYdZ7wZ3qPfy+*9ej0K zh~&tj`1Q2WxmMSMWWjNA=y5hRM6nDq=BU0tI68WB^zTC*%NATtEJFxJ7=IF)JUUkb37e=UAO&DvQ)L`g? z)<4U$(GbGPGC7G)6X;ZwN?X;r}KZ$Gts-6@-Qz3=^Px=E%m04egIedSw=O2z;RnOoJ@cof?mvI{sc$vVx+|P*?daY z*-LgP2M=>h0fduU(={n+`R*EM0lnnq8E{?{KS0@5;cZ?pqA9uxO|CKR%XxPF9oDPR z{y?1DOpgi;l52o^k}i@_mU3@2^G4^b!p-q6$8f7@6`Z%$d0B>`=#X3)7=#110^YLR zJ-hUz6ZyZK_GzrWg_lcpAxP6XHR@eJsAsUTVR)50 z?r>u_Uz4xND>3c4^h*EtGBuBI=H0E)lHWdQQY2}6NsOhY_`-8NlBG~B42T{&ID#9N z_;Z(=5DTaFA8s(n!vN9jZP`7>DoVf8&o}CZ=sLYQ9`_IAn;tG1j84u|lSZDuD>*Ep zmV-JnmwdPsjYV)FaRh=6hlq2=okINC!rnDZYOEtZ-zXxB9NdFlju}%!45uhPJAp_T zoup?dY5Q7q;(=I~T|s&W>p@?DMh|@%eV0dg2o5V;@zNxcCT+9e8aQBUM_uU9x0>wF z3M4NvnD+cIBy{YQ0A9yf^{MaY^Tct(ly({5|_ zh{FY^XJV&UK@Fu^4664BLiz zv)8mxM3@nxSuGrel$ckOkG!~|Sh|g5J>L=Vv@CyV-@F7S-4)HaN6EY>AOVC~4Rw)a zvxMl(Y?Q)fceQZOK}i3&hd@(fNyq1(5B|}A`TFUP2kLr#`{wAR|HI2yQWqzvjG8!i zNUAgM`bKq;SrplF^H-3v_ZP2#*)VULb{)2)AB(VWTQ-RXUGM|jI#rYJq0=Bn&0`jI#RnWt{1tM<#;}qpx z^rpEyzeZ(73X(U zwr*Kqu)OY=`n8ftb)>bcSX=0NQ#S$uzIt|gBdgt0j^TXL_tFf1kp#nJ`uFNDuAER< zPPQhe@E4PhO;X{S5!^Jq{R?$aCboEzeZiVfcy*C;_FDukK1;5Y&3!Xme zcXa&0xPYO%j_PGdkn8-&xS4mESM25pC(C=!L{}dG^kRbJZ^a*zTAA~ldk2Ct!CmBi ztWU45)oiR-#6d=>yBGrSy2r(-q6Iq|76UwH z^0KJEeC%^aNIh!Adfm@D#fQoV%ZbQyS}R3*vX&to+J-w?&I9I_`QU2RaP9Gw;RB0# zyLDzxE4WyaMBGA=Opn}K{zZoybZn4g#M(>(MFr>Isq<59H=ld!St=!Y;&y5JB+cX# zwT{wdFteyw12ZMDX(4u~>(hulEO4PQw_I%TE%%?{qZ@6Vu7Hv@)OC9BS=+jaVL@@} zWtv?LGW4EI)@$pwtpZwE%YtOZGDxbYEy|#q^)3|yAz|y3G$UUE^}PbyA8Y0PYWs(9 z8Blz>>3J2*N9@5}#B%MJ*^E)Y3iFLWwh?<+=P;V4Lsm6l9>ZxNJipt%o}qkc-0yxw z`l!zvM&DwEJFcQfmnWOcY@NlwrlWj_ywLbG!-P<%*?8n>=!!Mp4^ujS(yycvA)iKi zg6F_6;6$0(MIroV9iZ}_5rGA>55{r9Z*R)OMLN~P#{0DDr=ZVZINg)shQ6@!jR>kg zFW?tS)J{+$(wnPj?dzKyiRR9@A8wU)akr%F(`l zqwt69FzR(ea-`mCPL@fLz56zTnOW*%XBMS$(5pZ(|e=X@m$6@~`DArqtn84ch zp%pffvY(v3|BkA4$ExRD&FZn$RLIQj{@qG?f|xo_F|ZHX0#AI+QKt#liU2Py)p?O) z9y8k|CmI_p)!BRlNd>_DdXLkI#e$PoWEGmO%pq+L9E~_7vGB znkb5a;y35{vld&D9m>6I@R)V>`Q0&#ykqL%h zp6T$;M1*%jdP49rrByh&tGNN^G3?rO)l^DiI;W)DI9xY$^GGfsZ!rRY0l=6maI0aJ zWRzoqZrc_1KoEqbK>lW)2Th?!V0RRJ7SiX15_y9`32R~rFsn_UPI7f*2xbbydi4jb zKAoswwm4wXdh~>6%5tpKBk3W#-{_n;y|%>Ev1x?Xi7*`efW4qgTF->?f~;U-jx=}h>h+A}c+ z&Awvq1n^weK|0Mo)aI-`H@c^O^J}*3j#X2_9>UTnkVHYsteBF@~e zcW;A_qCeP$j|;Lfl zj=P9HO>UA!^``vBbKe~{hPXQtjP^z;DO!vLR6eZ(^mnXapL++yB&BLljYW&fst+6T z;ICMx)$&{sN+fW%*WQJOD$iFJz_Z7so)36#%zNozupDD9RTC+9nw?R|QkD?6K(+>g zeI!IhJ3>b4e>EU2y8M?3OI1SJzp{x29yr|R6U|jclDH2!p?ACE^<#JnvjcvHZ;=5? zYXrk2-VG4*g+|N>aH`ndTY<}`#J@tg`RRI3bl?YO=FQVcSk@_jOFEE#{VIthg32tMc=op1wRREx588H4d_278iN0>;Fl#rTn9==1m5J(RYy# zoh*Bae^m;JHaGnV1WV+dZs?{OOJ~8vMfKx+JV%7?UpTxf|#x3x%% z`z%Hpz#zJT2f|U2W4VdN698=r&L28rL3YvR061Wwrm?`<(#TrVcfP3VGbpK>DY|9h z%f%#{D!Q{(s$&wp#F~j%VP!d+O$$UN!WoaCjLO)^?~pOO#k z)-L0LTUs--SlJD`TdndAF4!wkSv#F}FeuQwhk31KCTVeVM6GdNpa-rUQ*ifkg zLr~F}9d28Lv;t*`1*Qrv zFL%w|9hgj)j%G(r>PGxtxmDp+wZEqeB97ItgZ?NV=ZoTrzTfsiKXQd`LzV09Iv}bw z#=rTL&CehgP(HCYEvtZA&eEcj-q@?2jqSifW@AIb(ozBU+9z(T=BlR+&0%oot~Va% zx?ULH>8g#5e&5`7ZU{H+xc-n+h1b#&p*^#tB%g$o1l>FCq(k#n8t`K|>v15euB1%! zPQB{#f!C)e#eQyt@%chasetFJyXHAQzdu`FYK$FWoM z&a7#Wv;J}5{BmLnP?!~>phZRoCHYM`iYBHW+oRn{N-0zvoToF@r+blxcT(BpV_)%wq!oa% zPxHxi-U)ABdBc(c%<73|>Ge1>7cJ|O`ucUdWPc-=U>-pkzhaS&XYe$7Qo2!=qU*z_ z>o48$&cm7{iptJOos5>jTv3&AxGGzye%wp^7k$jo_Jle` z)PjfuDz?uXl1i!kB6i8q^a7LgOTZazY}me~S5&>C@COaE28zHRqlWe!UtJy3x;MDy zCgyfCj*b1m*fTSH;|!ondLQ_6PZVTS>|0XugXtyl8!WDYK1s4eqDu;&AmyHTVBDGw zwYMFfWo5m|BjC>jbp?^>5qC>2iu`5Jd91RUP|PNxS~--Lcx)#LKSJ&364h7 z8Ap?vjb(1K&+3nC9;Cq2{H9aLGW<+8GhSuLB@;Z?XIOAiHxU`=4KGXiZQ3(Gzl`nn z#;XU%AT`tD-3}(UZij_I1p_ULn@49H&R25Z-i&Le2YjInoE_!GA`&)>r65cnH|r%5)A!XE8Cx0)1t|WQ#UDC@h-l4B#-d-KcSn zhd3R;Jjn~-y*^xEO)B!DPhXS&2GB)nY(m+`VnRK7N>G&F`|Wg`>-QmFz2mmzUc|-- zkL38+oE=fT3fusQKzF~8X*I$YD4Q$ENv89J^#)}Y8E?g22&}#3F07vpG-`^u3%r!h z8hV=yl)L^8A@!%oq&3GSHJVRyA6T}{O`}G4=3wDuhU@5SR5V-6^RXV%;SS|lI#p+h zrYg@*bg)yKT1Y5hNtEM>XhcJUt;QApmtZD^Iu9kW<~oT0Q1-|94DEGZhkaB2XWSn_ zwIKR)g_5}{ZuNKkHA z^{zT0EVm9a!PX880!vYr`<2Z>Fgl`Kvt+N*=nzfzYI*#oX<@^>us}`K%zv#(VR3Hu zP%cEW=IZQrQ`*I^i?@DZJQu0@WKLQQ6Zs>~>R<;^|IN~IGNmj@s>@=rvjg!R`6opH zpa;lp+CR-F>HNAsDH7cPDS(o!mY1H%(763-;r$iTFedeC9+W zP*C#zc2s>`6XufaF_vzxRc4K$W}K4q7Rh6nezsm3#RQ+6=@@ErfND!Wan)I8x>lu> zUTva0Ij`NI**yOUf$BOPSA@>4IdQbQ5P-?=r2BVD5(Mn5ddqr^O~VTe z32q0x9NaH4hGtFc*e~*#0`|0koX+0?jpKUgT6v@{gE?F7&eKnsu<>z}kB)jU>dYrD z$b-DsoP`HQU&U%ebHhYFb{gM2hncWA<8;s}wT${ZpS8K5jYOZG2e zuP;;qEu>A~ck;OrwhJy0cRtnhHMyKFM?VG9d91&O$fc^CRHR720uw!&!rJ3=| z#NvBTi$BH$_-LIr`9AG#ROh_c>$>lR_hpO!HD&>^c9(f2>mh$2UG(x&E`e^5kv13?g@s; z%V=z_NAh@8%r6{%VHSy%)?m3L>;yy|F_tqG^;GeQL+3`eBT`OIsY<6&ehVO#;ClYk z!SkQ{PY+-8pB^6{C{BW~^Yy{8lFD=+itqj#DU-Mmp?=YFY}R5bH0W;i0FCyeMS6b>ACO(Fb`ihiAr2ln;-#TD4yDi-cD|+6WYkIq&owG}X=~)UCXB`1j}{ z{1x}ngo(#@??P-=H=ZL3JXduu`^h?3Zu+mj`o_i#k1co=?p06@`(nq{F{mx*jRg!u z#Rd(nSG)Fan-2aN1Sh@fCKp0oMev}A;#ZiYl%F{9z>Gf^np-O2f|wm;X2{Ht&E}x} z&V~{naen{G=F_jL{J>Ey_+A}dK(1l0wqaul`w0x{9`a7LxeH*b;L$vBzO_WT%5_zjc zOA~8vaB^)$hpq}7X6=J_&?bzB#l?@=M3;tv9BBFALW%IN9vvlUvP97y&0w6h6;;`T ze3nA+9T3z>W~KTGGtmVCM~%E4rLwb~BtT3vKg`x*$Sisfn9U>FIRmzqm{=g9;2UZnZ~=w@m|yB! zAL-Ud-1P&#k`klYb243i zJeN46iz^q{d57~KH{7!b($r+H&GmE??$cC%UxxboGt;kZ zq$l{SVxG6k?$u25tTguB&Q~$fmsgdq33Z)axU6_~!C@Q#mXIDuv_pzp(LJ)5@<;C{3Wo zDcX2^XKcMOZ4uYVn5HAq9n{%RDEnKL`Gyrzd!az>>FOv^E@dA>+8PO)j(OQ{HFU6V zOo_{nUINvpF7sK|c8}2o*J$o0{&P0g8RMS!{{7Ym?Qj7rCr;|sk&G>pZ^CnETiR31 zuo)9<#w(~w0Al07-3!PuaEl6~xmY?-V(k)PaJ!4-7hat$M-VZax#a|aFh^Y`4Y3Fb zL?g0LLJ2Nx3h9bly{WrO%{GWqOnTTGe z5guqKdD=~$b(81a?m~n!4w%AWoL_? z;Yh&sn`&9$Zc?E@yGXjZufW3;UOnENo^9H$+83DAaG?;-LI^SlZ6<4(?$AUu z{TPXggmM;r!nCnZu|(icoM*23Vmp#nMx0VLnaBb3DT>83PyREqj8?m>Bux6lc&Shw z>LfC?dh*TDjf?#DGjk1CgUQXkhwV@#PV`RtcMrn}zG4B}pQs47dHwFy$;;!HKi*fk z4fu2EV4!avdgDd~bD=YX-)akWzRt38(EPj68SN+Q_IPw7VnIE(v`^oVP=ltKl_cWB0xr_5wIA&-y7 zTv{l|1epRHafYQC%MaFoeU`h>p5?fDC)0{MybyzO9jXozq?Kg#2WD~>^yXXmvDWC_^uV|ANhM!U z?t(y<8|nnbt89Y7<-|bE<8f@jMlL)stDHOFdy^b-J9)BKAuL0^G_QTRnd)y?w3SsL zf;ml!>2f~EikfR!u=r?0dv@^Z#lheEFAjb_Mbr(`EFnWD8m@ZytE0jghp zt)Cu&0fqMdimW1W4mMKG61tHIKwMWBZ4y48UXo5pZ|i0D-8@UUI@XlMhc#gYww}Q8 zaQaH_n%&*Afw#@*L9mk{m$$^A_tP52!fib zXpwhFnYwC8>P~fS+@*~TC2t+mt;Z~63aG{SOyyrSQWHhcr_$GkZ<99Mz))Q&_`R zf{B~o0+JqpiuzQmP#LKnP)!(4Zw7gR@PQf_Z+U)U(Szu<>WS!<*>&oM_WAt>r%YR_ zwF30#X(#!t@ivv9RNHeZ|K3e*9B5Gjj#*k1Wc*ZjI;5paE>XKnkD$UG=zncGs&dOn zhzp+uW3+-(7ke1l+&c-|>eWCns`G8?79KVL1no@nBPVxV!E zvQ#bA0aVxy{N^wgn4xe~{SUwM;xde(%Qk6q*UT|+=mkDf$8C7lp93!@lCK5&+}&{K z{8N&D5;@WpdJmrjZT2~dq3KDJRs#{shVk_BPw+?E0Rbd;b!D+amzRPhu3Mc>IK#(V zVi3(h`NJ|8@=x7l-A?kooeHVi)O|UaDL9HITOZUX@SSaIVkzD2vqVv*CB$^{&f~$P ze+CH#F`bHln4Y_MWXwK#OARzAX=i!D4jp*VAdrWU#zg3dFZ5_6PSdX_QTq*)C@nx% zv4C(}J#)E1Yty1lxix2m+9rC1zSb1xn`Y>$A`wIevRMWh`4vm@L}BcFU$G+hMJagY zWhCiy{n@zDV0Fj%l|ZnW%1%<=mEa}5tI*km#UH;Fs0folUPKw)2O8@+L-^Q}ap$W^ zsy8_^6emv8hg{zZw;=GpqyC~l5?l)3}X~S*}mSd0KR2E1q*EII|lN&1bp=EBnI1xBqf<_ko z9`HBKg2fgu4o#e;3NXT9+~VjVPom|wcrw(BwCU^l{>o@)!wW|e)J8XsCe8B0yIncz z-b~fsG?XH0b5c|-=Q(STq1Yn%YVWj)Pl~vG%ljO22&=hCX9;ez&bAQ8B|^W`A1WDgp3kV_Y^2FNQ=5=Or;~J?b={YE9z$7-%|0|yD%Va@td_O&Gb%9e z*>`MmAC~R21!Nxs63Z=w#t6sOnT&J-gG*FRb7M{m;~0-*re^-gQ3h$}OQ@&;K9#zv zLxOoKF}emcQi^)@EwE9)W&aGdTY9dor;zIL<4AUket+OTmt6spdL~+n-zD;cwctx{3Yc#;UqSF;dvLCx*58)vrh;tMMOlAEO2a2LgkV`JjM$$UVi&zoO_{rxV-T*+p2FBI z19VN1|AcPCVgOkTArmi)a{t?ECA;Dawad0Y??vl>TODI z7XS`Bv$HdqO$+c;@f#s5ted-;m{XOlY&V?FHqc9cEnZa3J6QW@MeL{Zk5QwvdcLFm zqKnxmUl6Lc))hZ3fQOCe>1F@olZ^$S_NoWfeh=DPu7#TFxP&?JH^Uq31FuSPH7BrizTJ$oDPIvo*_PgQ2nt)(}p;4RR%+r)8-!gXztkdCo@Yr#w zA5Bkj1||wz_f~b7@WUNkEq=Ufz6n?CCM$=l%wLY+4*eySu=O=_9i$T-0DC%JoF{6d zKvD6La>ts2-IF1_Q5ZPN2l*n#I#;cyR5w-2(j&-N=~S_jkfmP{;)!6joy4-V;Xa4q zY>UU%>U~K(_O`U!*gi|1)^_N=sO*rx2=8iL&W_xF)S33#PhAc3D=|cdXK`Zit-YR& z^!?N8J{Z!mj`jARRr~8KofFIR7YK<`y!5&KbC_Ne12UftfQ^|KP}X3P@Nb&Me?SU7 zMaL!?hO4#LxrLzhXxS!E&ms0~oRJEz-8wTGJnjJhVgujsbThzb+n z(UK?LL_aHL_s=&?=74@aXUhpi+kvZouEt4@-aglhO}2Y~hKe2M3Xa*_DitZUGy2#7 za(+$}M-urTxt>9z-eTMbsH?{KkGi%0-`19Vs}_otuwgUcwewEx2{>k_COLup>WLai z@d(@7>NojZJ##vEl5E4XKRE4hx9dSg(^8Otj>5c9$Y^M6gzwZ{HJK7DS6Acg%1#({$N5CxG{LL%ik*iUP5%Gw9bIqRHt;>aLd%0EwQjn#Lm%42KpSLj zk+lVy6xbdH!L}4fwQPB!-NXp;-^aV-$B~r$)h)(=^`Uhs^6q&2I`Y1Pi4T!46U|GU zv$7z3Fxh01LN_dYMS|_SN}>kqp9y0yE#ID9rf+`xbb5Y~zWF14clQ46J6Z7lqr|0L zL*&1A%C2>73o(=ORxX)AHzFNAy8Tl+I~r+jjku%rEIv z<`iR>t2~S45nhe+^m;y@9k1+EFP3z`Ylh%5Qw{U9hBVpp{(K=o=tC$Mm#;6sT%@1Q z-Q$~5iZ?km!k>!FeVYbTfZ3X2g1O}IOTIFbddazEE9z&aezr+{Jh4;< z{rbqX*d^xp#KQ(zuABn5M*lWU9t2jarV5#EQ%)&LHzj#It;%Xt4Etq-m;WQ%wlBnD zLLqM+2`0#J%PyfJS)BxDxQ>Dp(r{+_^Q077Y@ypO<8dNaJMY?*|`v@SWbs| z7MlewFZ@Hbxe2Zd^+Yoxgig|FmF0M#&g$xRiMnhhu`07_#|gvSxml!KChHCtHMo{( z2%z!g6oEQH6R_u-smMolKEi+>sg$wCmSoE`;E-exy}p7VBY)mNUHJdYmmglg|M>Cj z5;nq#15Zt?p69^QREe?j;_U6`v$wC&SmoDGpU;(Ky0^FqK?d(YP(nWkdUf^n?S5mL z0Pz5n9a{9v`idhFrwOD(uCSu>1JG68^6eKL;jD zBn|BDfUC+>i0_JS^z|M+Mm5v?2RyDCaQByePYfGq!8KDe<(nglp!;er7A4eRFl zc9>Zn+n|xrTk=`G(6T0t%xyUIobl~MfvXgU`lLr{l|Qv(8VE&ADcpRZ`ju+Sw1m*J z?UOosnGjWe#&rgdpH#e4a#rLp4w;!O^=CHTI1eLFuXxaCmFgxl-6fSN?U?_^M)hN( z?vTFp-b`#2HqXv(vVK&vo94mwO_qPd_0p7g@|7n&j2mRW>fn>nCvbAtINq$#(3AAp$eRmheLI)3|^` z&Cg1@Lt4xr1Lm-gzc(hnDPJd9fRotf{uR& z%l3EuI=g{K8d|53ed+>GxbF}lkvi#}5VsGkfSgPOi1vWu(FNu4{r+!fk9gwj2nfTnxs`-jEX7IFJ+T&5(}JeYZu>#4;Of%!P-&^iaZ+R z!#)(k_M-tuA1b+KDq{kH=Cc}&eoQ0Mt$7!t%dwlHdY*;9Ti^SruQ%#x>#hY|co4M( z9BL<_5k?C+YOE&mWRgqzM9v2FldWGh*B@*FtD&a7CLL-i?2)^Cz0WbTPn`H46JmEz zj^S74@if1Qnvuq!jZb`wiV%(@2s85R<$b1_YI+8XxfFWzb;~{mf^=y4GhLMBY+lo4 z(y&NzNdNyyuN5pp+uLhYK-LUJvn>Ap4jM=g?KxY`NK`y-ba3i5b$(XZ0okbMX6`mI zz=7s0eMubvl@$ILm?Dp^*);^$G`)cvK_@a*{sC5nI4ty62TXZkidiJi{qnVKCC%z; z#NI!zZ@`z%`O<;A;&lEUYs}yvK>K!1_pApRe9T0s0g$68Af+tn=$*+T*ytV}qjb_c z+}qHCePwDTw74zGk16W97M&e5&}98TKRZ2reV(3P{%%FzaXuXO;gzD8D|t3y#S|w; zedubVY;Di?C^4w<7F=XU6kAlMNND(&>E@xNOWz(Cr+|sV(L4&ZOB7bgl=c(y+Wn={8;PqdL6C8(=A<>uSfC# zODAHa+z#EUPP!R%tgJ`O3F-yMybLJ;|yy>hzbm2(g{ z{y6>1ZxO=FCb%rHo7QtWEw!PE@p{wVTk|-;&~=-7W>2kLw3<`Rb;Ey@97EGG1g{*) z*|6OO6y0m+t=>2uQ9AUk(+}_ZtKakZRGigCRTuMRC`ff^3Tp0lB3(7Hnmb*Tf@hgB z%tnnJ(y08<4Gwh!uu(Wy*J2?ywReOZP6wr&udADD6}$ebiOdS}e~Q=A==3pp#n;3t z?e~7fN7|-zB)%9mhD@PoGAo)kMlUIqr8Dr`H}vU04JZmRi80 zat*a`+7I&R&8cKgC3g+~=!9IH1_V1q1L46O>><@53>Jwt_y+TZ`ot887uD7L$!B;^ z!U!h92^CW@&%0-dE#cY zb>`}JCBIlVc1IWQ`Lc(4diX+SDC-`Ztxmoz*~uD4o!d^Kjw#aCB2gh5jQXXOwW_Nt zVZ-z~Ny)8cfS2+EzF_p%jT;5|J|55zvBlkMIfvsFf+{ow(wU$SMmj9PX7?zl50*(~ zB?XvSRhDhF_HH*w#AuE9Hr-@x)^yd@gznxS{0&e`0|XQR000O8eKhG>PTtq#ItKs% zO%nhB82}srcVTR7Wppoea$#4;YXjN1R00SOWZfGdseQ0%f3jhHG z000001ONa40F77wZ`w){|DC^LlqW?X`H?2APFIqPWWY%*5zMnq^P!Nw$NRkgb_pZPwX9WE|lz}9b^zAPoQ;|^SXx%}s!S6{BaSP(7=^huhK z9ZWd;FNtOA;spE~!Y{uU@JE&r&MI&vVW%l*2?3l4!Z#%88kSEIDkP`#TvC=nlqHZ0 z0yG1`avl>+nbR!dZ?Ir|B`nxdxdg`b|16iVVhLT)I8q8Mh&WMt*MzUAlq7*QXB(Q} zK`tYS{}9xZrff^I7l>JwP$egn9IS}!8NV*ON3IEAi_)iljnjy(2rqMV)$|2zR+3Wsf}tik`uAJz7+)}| z-*g8|pb3lf70F~&R_p?c!H~m^%2gx@r%@^nxz{z*!j&+}_`Y^wOsGhaAH=fOJVHHWda?C}mur^*5Lk;|lf?2E>453G#?)8}w<#B!MDXD2-|(f)?&D+vCu60t?*nU^E@N<9iEG2aLTCM((2YW>omAN~#XT!wyLw7J8**;9B{=^F$P{9q|U@)@XM`zeY2hb=uKb&z0!Gk>-6}f1J zt0Jfc+jr1ayFYRYW8-Jk>pO!`1$8(LFoo#i$WnTPi8FBVj9GCoD%*dyjM~8YYl=8z z!q9%S?=i^s=ebe(utbBY?>s8!y*mh|{UCJ1Y3RVc=M8nz1IPd24xHc`MxH9e9ZUnq zLL;HAZKDdz8M5)*p9ZeVo>J|OL&x{0lhF0X9ZbO!rU*T?QF^E|?TvNJm@3bIR(e%3 zbRjHwdT@~LtK{mG*_aC*KW1+bR*`5GlNnZmg|Tx#a_^n-z)@UJDSC1Pr-P+)0|jsk z%TG3%orb0vRLRgcK0c@T=9rS%f11B#$z&Abggnah{6SuNICDn#q5Bo8kL1o2M*(oh^wP zMAyhV_|ofX*(g@?Y_{fPL3b!1cL!Lnw~WM66KSHzg8n30EgB#^i_#g6m~}3TFs|#S zUs5(dftWLvA{XaUCM$OwVLwS$A>`SLW+a&{QuLuh-$vplz!o@z)yUoAV*|Z5s^GEI@+i5A#Osm4XvOkc2nes-_KIh(8QW$j zIh_12CqgQ3U_;5))K~Oit7@G|^#B+UJ{Q;cT%^eXL?xa@2_Q+eLhIK<%Bw{`?md;c zg48I{!OwNXCBBNf1!U6eZE!(i{HDy$tx8bYwXeshT0hsIilAf1%}c#S#ZwI0T1?j% z|B)(vUPqMQly$Ibt$M4}ZQdb{!)8S&NEXkc(2y9sOTxIskMd| z%+l;SpQkidxq|Z-B4>yDK~GD)yhhh1;=LXMZ)#uS4!@F_WJ>I|t-iPJ1}Xpk;6wXQ zS4JFlbh?OF>3(m$W5g{5E|NnTUMi6LYZ zoZvLY^-wVa`rL#RgUeuwOA}NZW(HfMHAVJw&cOHg54^0GrAlfF?7wV--rt*0HTVol z9h-pS(=d?Y(gfkRW)OJ##GhhT9(z?#KNB+}upwyZP0?~Z8j13HBDdAySSGhh`^+sf zHydsl`iX4sr)Zh;J>e6unxn^fMLW@lI2+6M!y^LTTk3D$^MY`O>u?avaKj65-D?-m zj9C*Zqrrm9Z=LE=f^$|NuADE$kE>t5J&wvj1m}?j9~p)7on^*po?(3W;oy%t2^+#Y zdgqDYyfKfiEA$Vxk&^x4LQ?`{?Y7@uO2T2Ys1VDgp}Ze9hMJxe-j)kjc{i&?!`flD zC|T8ae{bdZf!{k=dx=jmUQe5?ECh4`ZL9*^!uO7T=$hzq&8eX;ztrKyvt2`6))9vl zv4Xie(sH~tR-j+&(8?lN;BnX#D*U||0sjY3O9KQH000080DUy+ zS}{kqPe*S60Lfnh02}}u0C!<*Y-MyWcVTR7WprP1aARL{WpZ|9axPt`tb-q{?-z+l@u3ID3eHBCed$lkv;2dFXw;N^0zYZ9zILM0$RjCH{4u`B5m`&=Ywog&&GE%9DlUk!wG6lRL4Dl6Be=8$Vl#DB<5xo{4qj z`AdJ{5`dO)6G?DmnXLURlb(o^=+^h}AzQf_en`Lxf@tH1H)0Wmo=^Ky+9%dB>uS1Q zwYNx1MYJqvT0|Ze=V=CZG8eQGRQEc%rKv(VVU+m`*}|NkiU5!RfFR?ai8l1MXadqj z;QDKs6f%0fOABxsLMdngt6mQJo;_mL8Yz~TP~u6WWws%OC{!$s<$`ofLyZ%k^e-U= z3ze#+slm|fY&aFu@#*ZUH?ajiUrxrq3TVgospU(%w z(Yuxa3^5wd#QE@IIK#5pxW#yPw~6s7fxfUO{WFa8-VV=)v-gbkbT}Ikx>MlmiOb$( zHtf&OdlPXvpInZowjkyP!)gD#H@vV1Z4dyY!v4h`&BXMqcYdy9vCA%TP-FJ34N`k= z&uxWl^d4|0c7H}pRiFA0AxJ!L5!~se-5=sJq+)}s-sF8tgPq#{m}4C##h`c5dj}@1 z&&!73;fVV4iG4xDk59#P{&qSW&gL^)yc>@PEcB^8`DNI*r^n)aOvi94=2N=`B(oml z1_;Ou)A;;$J{^+u2=s6?vnP}Jx3h-y4}PjE>Xd=M>h zb!KCHLc(Q{^&ku8AJXg3%$2|jVa^O@#mIhlK7417`Zmpu3DDJWYBzCo!znEoD$G|s zpq$lc+Atkz&0*Q~&$Q?Zi@0=?ELBDg`&#;e zWQQBUw&BKt;Lfmg9fV#}5c+|GN^nbP1UWqMo8>y|ETTlV zS0~2kbrhvp$Aw{b{J7(K9%4`0Ud8dAMZpb7$yVzQd8hDZd(|S%JU`5gp~j-5$+-rO zMQ3}!@z-(CUNuZGs{{*7!1B{V#^ejl!RV6=7aahRKmAB78CW-vmCT$ZUN{^_6GtXV zlsFWT(i*berTeBkdX<~pY;9je>va@%=oehP6ZoOryTE_-w{Y)7>fhjGofUQm+sF-B z!FMnHn@6X2WJ~eR%^U=fV4LFYUG8ELRokCf5spzcSbhU*zln{%*NINn>YLw*bqfL}}TL^Gm9W6jQx!rS73leGSfIg(u z@#J!Ax#u$0KV{OXK7Qsf64|-t6waXt>!f#SCfD`#DF=7|Pmcc`HXumCb8cOXD{(*j zE)PKq{5stIug<_e?aj|;&iVMA)9=mfcW`shzQY12)#ug}TG|G$0Ln2xc9G!VZ}GFJ z2=8_=a{Me$DPyUY@d0+bl_eY-K%U({<4g<;eu@*}B6|=85j}(@XJVfs+@3j?_GCC7 z0N?AEDFEnA!nBNT7Y7CDr;Ee~Ny(cW-s06yPKJCd$>oWlX*w_@ZJrZHNd|EDbe2an zazrDImS{{Ryd;eXUISeh>WCW;$4^6y;|LTwWLm7Ggf@uaW*Ma(JtEXRN@xdaP@K(X zmn4W4vOAqnGYyl=KH(0bwwxuZ6kc54DR+)cY5~{!4K%_d!-N);rn5ZCKZ!n~Y@$a} zO5$#PxjBkL4i-w`v1ExnQ$7_l)U?}GR;vims^R$w6QKS)!k#Vfjm8J+HxP^0>kzD6yT4Cst$W zBIn>z00&qcU&%y6Lwn$#DC+VgtW!wy48bhbbNx1d8$l(;wYK*VY5a~t$J2Nn%I z_Pt_p{hYtNl@2^{*CnzY*Q#UDfp%EU6ILe#k2rwAh&S0?EV;6SGifRG_?uYqmrkZE z(@!aKOD9@7w{Nh{>5nfikRZ2c(@9HE3I<`nJOSj}$oC-L8`P7CfJA_{KZ`--C=^lH zNzYlkv1#|M`q`Zv5RkM|PIbN$`A3eUBfT`}Mwn6K$F-$t49%B&)~X4$xP!A^tmNW% z)rZ*4&eY*s8go-?AxiQR!?7-j5rko~(WZpE@q<8+hNBw4+o1mSb)xO3;UTJ59B8`q zr3ZV=Gld_*iv2*lcP|?+2v<+4jiH@SoA#TC6Cl>*IRC1@kZY$5R z!{&+AEI6&L*K8jgU_nz`V@fRs%pgDFpnc5J_tfO()$hAm*}ARUsO4)-+}P zN@H|PV~y$Vo(3>&9S5XI4p${D9yJoYNUAaIFRFto+)3R_D2U|iO&hH1ysgSQyHI(( z^20~#>;1u3`-J}wzwjjAP)b0Qh6cmBe>g6O$*n8E=3+HY;o}8BDYvP8?+q~d%SQ-S zOHZtW7aC?AsE#5ap&dl7XF-Go_-3=V4!og++dRr4#ef8d$wk?HquZXMl93xaGmwU6fk-)kDk3}-Cr zKJv2~E1e7#nM0&mNvqu1tSe11++Mgy^8M`2YKoI`aXUr%W7Q6|m=fRrEDrxSJgmTJ zv)(3A$x0N~qCW>Rk(=aIs9sYsx`cA@NoEsEmZB0$VBg96XsRO9?Q z@E3dRkY%T4yDBeL^&ewCuH>rKUX!6kv5m+wH@Eg}EHtVsw!FcW%4UU(~#~f+b$?lW^qrylxR{quGpROWd_h^R3&Bpt<*_SK{s3iDk-RC z0kWz^^wjgRpvgX!j5`M2$|q=0dzLf1$8~F=)!JSj6oZI?RZrvDR`a83a&TexVqd0{ znWY;7YnD{!q483a&&d)uOvyLF)x!Zd9t(nvW@*r!&e6a1-!rj~;hiCin^;_E-XJ(T zD*s6-k3<+kdXi)mrzk4i3a~E~6akQ&}&tuqE&_HG;ZQ+9F=72DDSq;OG*L zwx+!GkD~Z5LKDpD6x@@KA+M8yS%irnU;fCh2PGDGfIp!mty}Smaj^R0-@@a?aCADh z8p9ANAI^^Zc$!b9xgp-Xp@E|l^?M+=N#JMhuY z>3BXG7^2fA|CsVTb_}MJ4tAt;z)~ueimm3Bn{scemP;)?FANiCLaZB3P)r0po|U302zefTKuABv)mX9h@qEI#O#IAE%4V1BObyAGa8@a0k_ zKQ|gs5rbHVx|_n&Ei*3)5s}w97k4!!d9J3a`bEK`^wrzWU8|JUtT;`4*O7O3Y(Q7Q zC`qNvxl%V;N45Sj8OQY<_09Okj_iQKrU?@!4h4%qS<;L zr(Cu#wpp&XpuF9;2Pd!_`p(VmR2x0`@5?dHVDM*GLu80XTDiW#;X;#|D5amF=M(Hr2!0~zk%gmgT}jBw-*iP7i9?0$ zkY1GN?TzNP@_y;Oe&X{g$kF}+oUkai^qxx!pVBO|erPZK(6baRP16c!<p~&B?%iJAX&4fM-&jU0#YnV$kp6R8xcoL^Jf zN4HtRli?iz7-wuOL%cO}HPg9XT=gcS;RtfsD~>(1>_})2Nly{lSDbY91fbg;*rOqJ z4>bi;@#)T`d+YiEhm&23+J&#%vKuO-6KXFB^01Ci#J9Mio|utjN$p<)#!ZElqOEMAtHAQo=6SQRZUqq}(^YFS7H zgBsk-&}+jzYmq*1{xH#RKzS3l89GIWf=@u*d+R56{5VLx8dBF3`zhNN@8`&qk8r08 zTGcq|hLKa@u5eR}-`TWxPvE;Q!hNi-;w&UK#N zJsGb?I-ELh=hOFJi}6pHM09VR`5_z5lhOWe+4ET7IV z)gtz2Jb!oQfN%S&IW`dI$hsE4Q`7TXDVUGW$Nj(CgKr}x`{WQcsG3tBj{36%)V%)e zS1!raJG^tdC6?=~+f`4cEI5b8jCwb&x-sE*0~$qJ^vbZnt*`qBda_b2cGvWT3d4l+ z0r3PstfiFAM$^3VQUA;bIWc@-rUYrpb{Tw-M19RZNJLR!QGZglf%)4ZeAPaM>FaeU z%CnTb#oGV$EebTA&!*07>|6|gt+c1;*y?s|LtG+OBCJwqO*c};j+dudyYVQsnoMd} zIr5XAX6ch@yXu{v+cT$kKCyd)_m2JRa5|g9v-eS)K#^2WNV{KXQ$WuvPzf=TcH;{K z>CCo#2=ipBhNEA4=fi<>*_%$U#*;xU)F@bAM6RC)Iw%6wXEHdTdg>w0$y?`RMPX@zu!T z7et@KLbqB5L}WO0@u^26@H}DyuP7J^bQ!S2wPVuHep&euS(w%#LPvv7{GkZX5^t^&oeCtGadPvh;(x z1?8;oqv&q6;>>)vhSJzo%i|Y8MDJ8U>|QE$E=M%dN`9tcFhwQ4%B(*Jly_RYeb4{k zzI~F1P7p=0Rl8@qqOP5rWa8D)YkFS*Ii-T?v~IEDh+BL4)l^TbmqV(5n)2I^^n>aPh=Vj}3? zGilNNT~TI6OGaKGAsYBAOfu8JT)N{5VFapz-C~bq)Z+3pUF8{X@W^r*n|jXPuG-M_ z4AD=e(TC=%kaGZe(W-T-^OP^zW$2`oUS>}A;^gFn(K#B5QYWr#d3c$VJBCLx7jccM zW-Q~bV#=pTeE2LDW~QY?{9A-EKWjg$=+?~U8>A`x@xEwdo47uf4z0N0;aiTvFzWT_ z-isl=4*HU>tg(ku+9}<%>W%AnE5>ls>{94Y+%ms*sYlB9Y^C`^v2LQsGb`FJ9gI*T zIQ;#*?)oqa;Vpi!-C~3A6X}sNTk~sg^Q{Tr6DjzXx$DD0iIhS7T!6u$5dHWC?mk+3 z=oR#7`#DnytsUnGuXK@R@w(&MGV*RTOcAH(aIn0-|GvJE`uDnN_D53-n%=e7o-b9- zR5w*v^8frNt*;ZP-k7Om7C)uCR>9E(ry?>n(5UCe7h1}liRvXUiT7AE%x#3Y;=Z`X zFrbBzP-!B+vM+jW)QG+DZ)I2m#%$LmTLA*~6&3j1ZPaDJn9hS^yS-peN9nOQ%_@Npy;=uhRy=MLOKL-Ylz(E!cwx1h9;Ksiu1D`F!hHz4(8cwk}mWdB{b|PM; zv{WfRf1QuYa;kZq1vQVT7qH!MfY)3bS`o^PqY{_2(skvJ zOV)PprfR3H+_hPw`~!-lzA9dv06mJ!!{Te+TZvKK9e8qyq`JEQE--zp`O8OmD3wp} z{~KWA{$G1v+TFI1Ec!ja0_Ho{A#IvoCO0=vTaGL-wq9h(Bhg9xJbpM32}u}}ATI!F z8At#9byaWG3qXL99D1%b8IuHhsqU_>s;;i(8eO>a`Gmv6i*;dymtd8i=M0_VU^GX& zJpoe|fj2v-AfO7EXb6{acJ?2Sdf!ocXfSYRD=Hj#vLy!6)=v^9C1q?+#3P~qHHh2d zqvn;(o58dosly2u^s^WxWOyTvK-eBKZ((PS9GcR2xzikJj2{i3 zKFag)=A)-Li;Sr#)*%j(=4J$%R!;xrrR@J{96amLwY zMISfPXpaWh5=CTN!nic&Gu@MRe&q+xGVZ31p~AI8!9hW@tKs;#3fZQQ!l`j&xqgYs zJD3)n&%!*|hf}fB7WDUX(29g+lTk=miWWnQmTF7{%0M7mR8^v7pBn?e%fM+7LBj+` zZfJUD)6^Cbc75Gt0`^?0sj>t!S<=V!HrersXk#2vzJw4wf2Iim{{wHjB&FvDLdzJH z_I5!L^~shV$&BL|>TgZ?VEV;)+x%uEDdFAT~od3Op9RYP#8h)y6f2#VCehro@h zh}0!D5v!FXx@fa_DM5|B@}LrVt2Tuo$L^*;w~gC)f!rJ|*n0ag-`!yHje#H~bK3T7P*UH7 zKrZC80eh5{;lZykqI69|)Ix(}nwt2gX3ayRHkr;eial&5b4i zX+W0009V&i&_hce-MMLyuyKe-_8#*9%w5#fZ1)h1WCpCz^Z3b@Wx~mo4AT3EtAyNY zJv@f9BUQWg{KbFup0WQuZ*`Q?LYEi%3s&Xg2Qm+~8hKjOj41L4Jw(&DLP2)ckC zOtaDG7Rb2P*2supyGx0QV+5rmXntJ-E1XCRvdM`V;9wzkX%jTnfN=|>o%NpxdJ54% zS~)vXwvE1iwmp1Akd5E^N5^}I2Opk&>@fL-nyH!v!sEfwjRpd2rQvprOmQ@XO^E*S z8RNs8BImu6_zY;Ifh#3N(W3H(=tdrx^TT8c9zy}c7Q`bmQ95da#og*RL*m}>SPkL< z_vr9hdh-2jcHcv9Kitd8_OH5SS_ADBrT@~HofYs7109f383AqLvHCR5XE$m~7_lr) zS?BYgSj*?_MQBH7lU{s1piSB);d){?@mBYyK|C~NN`ORM5R61XjU>+7wDB&hWXNz( zM~79j3(L3cgB=gq?ug(x$C}U`D~&aK>Xqp9F&xZeGNK9u16*qBJ!RZa?GLIfHa`*q@+2WefEQ_xB?Hz~# zKS8jb=Ra-*8SItkFMc{niqFJ)U&=;oW+BJ~DJ7hWyR>ExoMR1?5TjH6zXVr&ky)sz z62=2VYR+SV8J%m2OCB25>OnxSW~hZKI2(I;O3&jYiXsje6!Tdn*Bz!m9Es(UB^R*m z;i;qnZRpeT*?wR)(BqfDbN~%@E`q=@5r-#Gv={7P@C`y$0yN+TO+8j>HQB&o9#3cU zBA0$!PqyUZ^@br{vUG#8k*&M!4BOwYeP5>ry;LF*gcKCY07`yoJKdpWL|YZ)$jZ>a ztF5OLQa2jGKhj!5;8jk?^ZdNMxHR5VVh!#dT~?zGzVz{04aScfU~1z_BOHLH-yZE9 z48r|q*gB{tB@#SUYXcEQ;cPns)p}WfvQ(%N?Gj$P>C+x|f}XdO(Y(i~~k%~voPs16r{M&t|f=pqiqb+I`+=Hyt) zs`6+nD76T+I?-^eO;xI0Cxpd<$=qbMAUNjn1$+RM>eR7u)SgwhEp_e;N~&45rT!;d zqnN95)~z?%MWu14xPZD>4DGNYrf#qLgYf0{{`SF6U&pAa2;*dx7qO;H=VEj7@-h4S z$sp9ox`C=-8h8?oLA#0JO8&dYUY)j`-Ej378udfRE5o;jJfudL#_=fT za*%{X7HN0rP5+av^C%Bh^%>m6MC0&Ne9f9-2f4gW$OoFE%r<1?kFM_#eEac-u-MuP zafu;bGN~Xa#*(6io%Lvg zOgU&B()omfA^Ty|9Xx()RP_sTi-+^UXyv1U4F1~f#hVNM8cb;w3=5j61>UaCckjMw|6clPo9Je^oT8q-$(OdRbSrvjhQJwDzp)<+qasER$>$ zuv-D~rAdZyuFI$FAlWC)lH6q>%?vj{y=6mC&|3N zB+)Y22s@=5-w08i#rX;;0)R9Kcf{I~xBhHHRSQ%TMq9MsW@sb7117nDqo!C2qtX_>d^)1n}8=~A>eYV2A+?z5N zU27Q@T6UNQx7Pj!Z0g4pw;j!B+6&51QxKSW+uk zLTd+`!Mv0wkglS*jN_E`0+u2_{?H?#t(=&iDnF0pOVtun;GBe2(*}ElY1TtlwA4OD z`%0FG2uh|>W!cZik2}UCt=v$opBh>#a`oYPsy zsb!R3hVwXm+!&tSJ_e_{{_sqlph*d>S}+IZFC7~#mNX|+JYMzA3Ukc;?U()iMpVbC zQJd(L_2r>Cv7X+o(-BL=vrIdX|bmmH`^raP-5%7RLmd9LBxzN&FTg6FlTa@jfyV645 zMb5M3Y>aAY?SN|08H7Shz!O>~r$C=Lj=TVfI{k1BZ`CQvIt2uupLL%Znn|NTZxa0z zVguB^Un31Ye(Y(=?>6R}eenp+CdNW=E#C)E+aIawz;yt#Rm&Q|-J#e*H3I6>EqlPQ zYGBHx`bQ_DLHlT_caM92?}3C~PsyE^+iiNbX2lJA-Cnu}@AmicpCymhmGc02Q6DW# zMTQ4&sa6RzqGkn?yglADmcm!mnNeBCeqkQ6SD5oiai8y)Mu!VsrSajy>8+B{n^Lb_ z+qNs}qTpq1sR1fvnN^4%52Uk!tjXUYy!!lHbZ7>m6i|y*LX$4b_O?Z=KIbm=<+Z+PN1YMg) zeGhvR02=osrX=O#iOENpA+97puQOz#(7v_g=lOCkdIw8}VVMmlD;>d5npa^`Ff{(L!f<9wn*tJP3K6 zwjmEE6OjN)ymi{K3$!a&Yc-Z@!Ihb3;V>xh(7Zd7?vu2&G%hZu*(aWtTHK!*>CS1A zDrm$X_F}<3Ua%lIn1KyuHf7)KhOwsX!E}6~Q^8ONeRy_bXvgXRt-4`2UgQ2z&RX!u zzHrp3Ak|zDpIiW^N%J`SIt3bj;KZV8CLms|c zWUz~h5Rw!W;WUkDL-wYF%z*j%PZ0hwKFcE5OBy;AQ--^Phgg(jAN6DAVZ+m@(%z`} zZ9M50oeycju@0{uSns%%1$*=OvAL0J>@`R%D)NdjN*LwpBVMQK9^w)*$Zv*z3cCkMh1BLDA=COz!~_h z7|`Bs0IuOYG%Ge1JbG%bgS9be_Y|PEAPwYo=k_abJcTr8=z9s^US?o5f+@cVJ`zkJ z89@XGQ%t>#Tmj=mq_r$9YXIzKL;^2fN^g)(@qlN7kSn{05+rYIWMj@(5v8MO4AEs~ zYKe0UL^C~MqDNfBJqS1)P&)z$lR&0ZKTlHh;9eQe=ZNUfGfzSq$~&f_cSkO%ngtk9|yXz5ici@?42g# zaeMGExP<&2-QYKfP;ENc+w+@uI?mVwUSAo%PBH4@#&26~iNvcJjbWL|(z5hm>yGA) zCR=dkZ>1+t%#L*83;2zxNSjuZVyenYR($5Y90(o)2V4Wn2YRL=@5maDxiH?N@}-zB z{b;pj3{P+o3Qa(LLEQNiUmFMahio>xh9y0Sa6>jQDEct?WE{|pa~LhikaRN5RA9_v zstgs`S9L>T@PZH@!#~s0_9qPT(+M60?dK4Ip0y>abtG<@j0C~qXC+ZrkXJY(nNI-a zL+*XZ&Z>oSUUII5*f=0m0E|PB-|{}=YqwYHnZMuF!VoHnPzjAyt-@8bAm>ipMy__tvO4#FJX*uC(gZWm4{sf}x7Y z2byg;+zF5)K=vdi#SO=(XU5}q2f~G%DhvPI@I6nT20PQqY+l5Y2}0&Tk|6@B2vUCfK@qk3Mfuyv@kpnF-v zL?XaXCzGTwSG~)(yVgcw_P*I1NMN8Z^+2=3-~loaD+E`n-m{Cus zglj3#*HEkrRctBWQLf6vN`j$u=;A=b84q}^=@aNBWpA8BnFzpF9Ny?AE8CPrj5e$( zovH4aSrBr&-n&JW;tCa)u5xW z_2e}R*P)7rNd(HjwOf#xzNLJo}g0hyYh0Zh5c) z_o7Nz50Kj@ctWq6`2CSk2h;fLa+4~Tr&*Rvv!uAj$muC$g9zm#>_`4EtipoxQ?~gt z2=MA$m!GrKIAkXIBuiM{2D)k^4)gk#@bB;XNAFcWVnI#Vb_w4czX}ifgWnI2epRv6 zX<@ZzC|2-rydg>@*?fqbeimiXBoFkrO~)Tzm*+*=kMw}L*5wjVI?T_Lj@YDh17|K> zsquv=Hfkzs%(IB}O{irazG(ABryvY4SM}R>gK)RMbNK$4BMP_0#%Q4$CWS3!k)E`5 zIe2Gv^(2{>OB&*(1PKN)#N&K>cznF~a=*{D`DC0T0P%>pHnI)IQ(HsQirEx1`H_hM zs0!4$fd+oUv!6teZ#M3+6^J1iyxLk}J{u?8HTJUSAC*u8JlG7LHyzpF@G#upK6(Yi z8jvwvPUqthcyKag3&v4)29Uv3dC@oT5d^eJvt zc-gJslL}^ci>Q*th@EtGIp?4Y6ORFAU@`;~#Ayyx5nVVi#$spo=4+_po{<*JHUc&> zatDSr)UXXFR2`5|$2L<4)7G#s`pKFUVofc#-fHSOIOoU_$YhQLQDSu2Yav;ey#`^s?42clmld-_^0J{AQo64YQqth*BNWsh zJ*^t>xh2XQ9T73Uq6w7j7c`-S{fc%h`F|xPf2pGikKgX^)pM3`t2EY#IlX-mz1W@W zyVfD?bm-^?u>yT#%@j}aHiCVL5JFs+4{!&d|U7c1=m0Fk{o@%zp1IBU5uP>+LQk|A6y4(__+Ab`*`YNyE zy;lceqdOp{MtwKUlD>$mpm}Qyc)~WrD867Dr;*rgU(1O+pJ)@1&u3tk%=XjCb@2Lk zD~2LF=9FAp*YExJ?ZM9L@bF;&eO1@k_!G=&szFe*@Ic&zCOO`Oa_U2|z6n4tw|B$W z{XdM;2iFI%wtGgnOF(md5syx4CH)IvrQHm98^W!4e8q+(J=zje zlrq5+smd&yUa)iG$VaCG?e9wtHn8tO2-^y*r@3C$2jxT3PBoyb(mS(0t5coS=Hd;Q z+hR_xy067=Eu5^Go(txYJ@xgAgwd zy?wKyS)^2BLmqU)&%Pb^F3+QaA((9sMr;Ht_R<2hB(<8OrP<{dMoS8LC0bfg-X1OA zyxSk_v9&EhOS3;B`j|nmQXT{=4K_^NoP@xQbquVuW84bas7+YC+sudbW6vRQ70K`J zd=Gf0%H9LoVt9!?AmUqCzMxN^24@jo;bR~H)>V{$LKP*Xf2QK~Je_7Eh%_5QbT`BK zS}0{^^OJEhlo2>+wl*Uc97$S<=HyCdOd-Q?mKWitbb6VBt9JOWIGcvtx?II(e4=hq zOK`+lcZwm7JV-6@hu{itS7(?(Y+{-82r-HXE`5H6w_P~KbUukQHV5Nmk`%cP6%Qft zt|0mXD+ssetrmV2ANY;tY@(rQJ(3iSy&x|%#H2$AWh^??YK6+1JIAAC2Qqc? zo6t7AdxjVZz90bhRkR&tLzn!iBDRL4VH7@2#FRh(aMY7}XcN$qym{WCVjKWBdWQx8 z^RJj4geTYBguv>zJhp&&M{QUu_+l;azMbQiH{=4+?ed@tJrR0=%(?-Y4V%zzk~LZw zy29581m%FuL9xPfprlZ(J55wzm~9{ce>dNi|5AM4p3~dRXa^im(cheOsGJ z*W2Kos|V&(n(u&^$?q;BHfLhQXJRB~0@V5#8!Ip9e2hvcgp+K_8w2Md|JZbT_%Jfk zDpGBfj&fsTpdGME1P1oT?7@!B5IOQ8#$S6PF)Sndt zBGF#zj}1zi)QXOY$5RsFlpv&QI%-!8KY?orUKGPBIY?Hhl%*nE?WZ^dL8VQBv0mBp z&h6(qM(0MRRfGmYAChF0u-9n)^^#r5wo`dgy#tW4je0^r8Wr-kC)lC}OP`&YEX=92 zn($6(Q8}l}pYwLcQ8IzcH||2HTno>*Fv?KEO&mqRvL_h#zcV5C1Z|RYvzv^Y1(o6 ze%z-bkbnnGJN7Vdv9x@(&Hw?~hz(;e{~F%dn^!=mgG%^T*uVk@eV^2*CB_xO#^Z;X)p!;F@G|O%mKb)5S@IJ4HrxWIGwyPDQly(;*y$J%&o$=dwXOOlC=J zMyLL)8$x{SI%L<(hkH{+v6+wn9U0@Nl^;J&yfX&_@*qJeAvQPHGvDTV_R(PvvT?