From 9dbce17bb5280e7585454942e212f7449b550d55 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Fri, 22 May 2026 12:24:35 -0500 Subject: [PATCH] Blockchain: option for fast manual block popping --- src/blockchain_db/blockchain_db.cpp | 25 ++++++++--------- src/blockchain_db/blockchain_db.h | 18 ++++-------- src/blockchain_db/lmdb/db_lmdb.cpp | 4 +-- src/blockchain_db/lmdb/db_lmdb.h | 4 +-- .../blockchain_import.cpp | 5 ++-- src/cryptonote_core/blockchain.cpp | 25 ++++++++--------- src/cryptonote_core/blockchain.h | 9 ++++-- src/daemon/command_parser_executor.cpp | 28 ++++++++++++++++--- src/daemon/command_server.cpp | 6 ++-- src/daemon/rpc_command_executor.cpp | 5 ++-- src/daemon/rpc_command_executor.h | 4 +-- src/rpc/core_rpc_server.cpp | 4 +-- src/rpc/core_rpc_server_commands_defs.h | 6 ++-- tests/block_weight/block_weight.cpp | 4 +-- tests/core_tests/chaingen.cpp | 4 +-- tests/unit_tests/long_term_block_weight.cpp | 13 ++++----- 16 files changed, 87 insertions(+), 77 deletions(-) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 72492f9ae..202b56942 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -172,13 +172,6 @@ void BlockchainDB::init_options(boost::program_options::options_description& des command_line::add_arg(desc, arg_db_salvage); } -void BlockchainDB::pop_block() -{ - block blk; - std::vector txs; - pop_block(blk, txs); -} - void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transaction& tx, const epee::span blob, const crypto::hash* tx_hash_ptr, const crypto::hash* tx_prunable_hash_ptr) { bool miner_tx = false; @@ -311,7 +304,7 @@ void BlockchainDB::set_hard_fork(HardFork* hf) m_hardfork = hf; } -void BlockchainDB::pop_block(block& blk, std::vector& txs) +void BlockchainDB::pop_block(block& blk, std::vector* txs) { blk = get_top_block(); @@ -319,10 +312,13 @@ void BlockchainDB::pop_block(block& blk, std::vector& txs) for (const auto& h : boost::adaptors::reverse(blk.tx_hashes)) { - cryptonote::transaction tx; - if (!get_tx(h, tx) && !get_pruned_tx(h, tx)) - throw DB_ERROR("Failed to get pruned or unpruned transaction from the db"); - txs.push_back(std::move(tx)); + if (txs) + { + cryptonote::transaction tx; + if (!get_tx(h, tx) && !get_pruned_tx(h, tx)) + throw DB_ERROR("Failed to get pruned or unpruned transaction from the db"); + txs->push_back(std::move(tx)); + } remove_transaction(h); } remove_transaction(get_transaction_hash(blk.miner_tx)); @@ -481,8 +477,9 @@ void BlockchainDB::fixup() if (!has_key_image(ki)) { LOG_PRINT_L1("Fixup: detected missing key images from block 202612! Popping blocks..."); + block blk; while (height() > 202612) - pop_block(); + pop_block(blk, /*txs=*/nullptr); } } } diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 5bd97d582..aa31c8474 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -538,13 +538,6 @@ private: /********************************************************************* * private concrete members *********************************************************************/ - /** - * @brief private version of pop_block, for undoing if an add_block fails - * - * This function simply calls pop_block(block& blk, std::vector& txs) - * with dummy parameters, as the returns-by-reference can be discarded. - */ - void pop_block(); // helper function to remove transaction from blockchain /** @@ -1151,13 +1144,12 @@ public: * implements remove_* functions. * * The subclass should return by reference the popped block and - * its associated transactions + * its associated transactions, if applicable to that overload * - * @param blk return-by-reference the block which was popped - * @param txs return-by-reference the transactions from the popped block + * @param[out] blk the block which was popped + * @param[out] txs the non-coinbase transactions from the popped block (optional) */ - virtual void pop_block(block& blk, std::vector& txs); - + virtual void pop_block(block& blk, std::vector* txs); /** * @brief check if a transaction with a given hash exists diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 9cfd77f58..df1b98db7 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -4157,7 +4157,7 @@ uint64_t BlockchainLMDB::add_block(const std::pair& blk, size_t return ++m_height; } -void BlockchainLMDB::pop_block(block& blk, std::vector& txs) +void BlockchainLMDB::pop_block(block& blk, std::vector* txs) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 9064066d8..98b73536b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -342,7 +342,7 @@ public: bool block_rtxn_start(MDB_txn **mtxn, mdb_txn_cursors **mcur) const; - void pop_block(block& blk, std::vector& txs) override; + void pop_block(block& blk, std::vector* txs) override; bool can_thread_bulk_indices() const override { return true; } diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 7563af60d..fc862ff75 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -100,11 +100,10 @@ int pop_blocks(cryptonote::core& core, int num_blocks) int quit = 0; block popped_block; - std::vector popped_txs; for (int i=0; i < num_blocks; ++i) { // simple_core.m_storage.pop_block_from_blockchain() is private, so call directly through db - core.get_blockchain_storage().get_db().pop_block(popped_block, popped_txs); + core.get_blockchain_storage().get_db().pop_block(popped_block, /*txs=*/nullptr); quit = 1; } diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 09e6a3f5f..666060e3c 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -408,10 +408,9 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline MGINFO("Popping blocks... " << top_height); ++num_popped_blocks; block popped_block; - std::vector popped_txs; try { - m_db->pop_block(popped_block, popped_txs); + m_db->pop_block(popped_block, /*txs=*/nullptr); } // anything that could cause this to throw is likely catastrophic, // so we re-throw @@ -548,7 +547,7 @@ bool Blockchain::deinit() //------------------------------------------------------------------ // This function removes blocks from the top of blockchain. // It starts a batch and calls private method pop_block_from_blockchain(). -void Blockchain::pop_blocks(uint64_t nblocks) +void Blockchain::pop_blocks(uint64_t nblocks, const bool keep_txs) { uint64_t i = 0; CRITICAL_REGION_LOCAL(m_tx_pool); @@ -563,7 +562,7 @@ void Blockchain::pop_blocks(uint64_t nblocks) nblocks = std::min(nblocks, blockchain_height - 1); while (i < nblocks && !m_cancel.load()) { - pop_block_from_blockchain(); + pop_block_from_blockchain(keep_txs); ++i; } } @@ -588,9 +587,9 @@ void Blockchain::pop_blocks(uint64_t nblocks) } //------------------------------------------------------------------ // This function tells BlockchainDB to remove the top block from the -// blockchain and then returns all transactions (except the miner tx, of course) -// from it to the tx_pool -block Blockchain::pop_block_from_blockchain() +// blockchain and then, if keep_txs is true, returns all transactions +// (except the miner tx, of course) from it to the tx_pool +block Blockchain::pop_block_from_blockchain(bool keep_txs) { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -606,7 +605,7 @@ block Blockchain::pop_block_from_blockchain() const uint8_t previous_hf_version = get_current_hard_fork_version(); try { - m_db->pop_block(popped_block, popped_txs); + m_db->pop_block(popped_block, keep_txs ? &popped_txs : nullptr); } // anything that could cause this to throw is likely catastrophic, // so we re-throw @@ -626,7 +625,7 @@ block Blockchain::pop_block_from_blockchain() // return transactions from popped block to the tx_pool size_t pruned = 0; - for (transaction& tx : popped_txs) + if (keep_txs) for (transaction& tx : popped_txs) { if (tx.pruned) { @@ -1117,7 +1116,7 @@ bool Blockchain::rollback_blockchain_switching(std::list& original_chain, // remove blocks from blockchain until we get back to where we should be. while (m_db->height() != rollback_height) { - pop_block_from_blockchain(); + pop_block_from_blockchain(/*keep_txs=*/true); } CHECK_AND_ASSERT_THROW_MES(update_next_cumulative_weight_limit(), "Error updating next cumulative weight limit"); @@ -1167,7 +1166,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list std::list disconnected_chain; while (m_db->top_block_hash() != alt_chain.front().bl.prev_id) { - block b = pop_block_from_blockchain(); + block b = pop_block_from_blockchain(/*keep_txs=*/true); disconnected_chain.push_front(b); } CHECK_AND_ASSERT_THROW_MES(update_next_cumulative_weight_limit(), "Error updating next cumulative weight limit"); @@ -4342,7 +4341,7 @@ leave: if (!update_next_cumulative_weight_limit()) { MERROR("Failed to update next cumulative weight limit"); - pop_block_from_blockchain(); + pop_block_from_blockchain(/*keep_txs=*/true); return false; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 1970c5b43..b887d7862 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -1131,8 +1131,9 @@ namespace cryptonote * @brief removes blocks from the top of the blockchain * * @param nblocks number of blocks to be removed + * @param keep_txs whether to place popped non-coinbase transactions back into the mempool */ - void pop_blocks(uint64_t nblocks); + void pop_blocks(uint64_t nblocks, bool keep_txs); /** * @brief checks whether a given block height is included in the precompiled block hash area @@ -1377,9 +1378,11 @@ namespace cryptonote /** * @brief removes the most recent block from the blockchain * + * @param keep_txs whether to place popped non-coinbase transactions back into the mempool + * * @return the block removed */ - block pop_block_from_blockchain(); + block pop_block_from_blockchain(bool keep_txs); /** * @brief validate and add a new block to the end of the blockchain diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 728df58af..6b718faa9 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -931,9 +931,14 @@ bool t_command_parser_executor::sync_info(const std::vector& args) bool t_command_parser_executor::pop_blocks(const std::vector& args) { - if (args.size() != 1) + if (args.size() < 1) { - std::cout << "Invalid syntax: One parameter expected. For more details, use the help command." << std::endl; + std::cout << "Invalid syntax: At least one parameter expected. For more details, use the help command." << std::endl; + return true; + } + else if (args.size() > 2) + { + std::cout << "Invalid syntax: A maximum of two parameters expected. For more details, use the help command." << std::endl; return true; } @@ -945,7 +950,22 @@ bool t_command_parser_executor::pop_blocks(const std::vector& args) std::cout << "Invalid syntax: Number of blocks must be greater than 0. For more details, use the help command." << std::endl; return true; } - return m_executor.pop_blocks(nblocks); + bool keep_txs = false; + if (args.size() > 1) + { + const std::string keep_arg = args.at(1); + if (keep_arg == "keep-txs") + keep_txs = true; + else if (keep_arg == "no-keep-txs") + keep_txs = false; + else + { + std::cout << "Invalid syntax: Unrecognized argument: '" << keep_arg + << "'. For more details, use the help command." << std::endl; + return true; + } + } + return m_executor.pop_blocks(nblocks, keep_txs); } catch (const boost::bad_lexical_cast&) { diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 1950b22a6..98ed8b494 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -299,8 +299,8 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "pop_blocks" , std::bind(&t_command_parser_executor::pop_blocks, &m_parser, p::_1) - , "pop_blocks " - , "Remove blocks from end of blockchain" + , "pop_blocks [keep-txs|no-keep-txs]" + , "Remove blocks from end of blockchain, optionally moving popped txs into the mempool (not default)" ); m_command_lookup.set_handler( "version" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 1385a85ee..6229eb510 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -2310,13 +2310,14 @@ bool t_rpc_command_executor::sync_info() return true; } -bool t_rpc_command_executor::pop_blocks(uint64_t num_blocks) +bool t_rpc_command_executor::pop_blocks(uint64_t num_blocks, bool keep_txs) { cryptonote::COMMAND_RPC_POP_BLOCKS::request req; cryptonote::COMMAND_RPC_POP_BLOCKS::response res; std::string fail_message = "pop_blocks failed"; req.nblocks = num_blocks; + req.keep_txs = keep_txs; if (m_is_rpc) { if (!m_rpc_client->rpc_request(req, res, "/pop_blocks", fail_message.c_str())) diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 0469c9fff..cebd6b2d5 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -6,7 +6,7 @@ */ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -154,7 +154,7 @@ public: bool sync_info(); - bool pop_blocks(uint64_t num_blocks); + bool pop_blocks(uint64_t num_blocks, bool keep_txs); bool prune_blockchain(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index a6885a659..c2e40c4a1 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -3174,7 +3174,7 @@ namespace cryptonote { RPC_TRACKER(pop_blocks); - m_core.get_blockchain_storage().pop_blocks(req.nblocks); + m_core.get_blockchain_storage().pop_blocks(req.nblocks, req.keep_txs); res.height = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 7815964f6..e70009e05 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -101,7 +101,7 @@ inline const std::string get_rpc_status(const bool trusted_daemon, const std::st // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 3 -#define CORE_RPC_VERSION_MINOR 17 +#define CORE_RPC_VERSION_MINOR 18 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -2498,10 +2498,12 @@ inline const std::string get_rpc_status(const bool trusted_daemon, const std::st struct request_t: public rpc_request_base { uint64_t nblocks; + bool keep_txs; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(nblocks) + KV_SERIALIZE_OPT(keep_txs, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init request; diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 44ccf1e64..b5269010a 100644 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2024, The Monero Project +// Copyright (c) 2019-2026, The Monero Project // // All rights reserved. // @@ -96,7 +96,7 @@ public: *block_height = h - 1; return top; } - virtual void pop_block(cryptonote::block &blk, std::vector &txs) override { blocks.pop_back(); } + virtual void pop_block(cryptonote::block &, std::vector *) override { blocks.pop_back(); } virtual void set_hard_fork_version(uint64_t height, uint8_t version) override { if (height >= hf.size()) hf.resize(height + 1); hf[height] = version; } virtual uint8_t get_hard_fork_version(uint64_t height) const override { if (height >= hf.size()) return 255; return hf[height]; } diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 05a6ce1f9..8bb725b34 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -132,7 +132,7 @@ namespace return blocks[blocks.size()-1].bl; } - virtual void pop_block(cryptonote::block &blk, std::vector &txs) override { if (!blocks.empty()) blocks.pop_back(); } + virtual void pop_block(cryptonote::block &, std::vector *) override { if (!blocks.empty()) blocks.pop_back(); } virtual void set_hard_fork_version(uint64_t height, uint8_t version) override { if (height >= hf.size()) hf.resize(height + 1); hf[height] = version; } virtual uint8_t get_hard_fork_version(uint64_t height) const override { if (height >= hf.size()) return 255; return hf[height]; } diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index f7ef262e6..ad6ef576c 100644 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2024, The Monero Project +// Copyright (c) 2019-2026, The Monero Project // // All rights reserved. // @@ -90,7 +90,7 @@ public: *block_height = h - 1; return top; } - virtual void pop_block(cryptonote::block &blk, std::vector &txs) override { blocks.pop_back(); } + virtual void pop_block(cryptonote::block &blk, std::vector *txs) override { blocks.pop_back(); } private: std::vector blocks; @@ -206,9 +206,8 @@ TEST(long_term_block_weight, multi_pop) } cryptonote::block b; - std::vector txs; for (uint64_t h = 0; h < num_pop; ++h) - bc->get_db().pop_block(b, txs); + bc->get_db().pop_block(b, /*txs=*/nullptr); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); ASSERT_EQ(effective_median, bc->get_current_cumulative_block_weight_median()); @@ -267,8 +266,7 @@ TEST(long_term_block_weight, pop_invariant_max) for (int i = 0; i < remove; ++i) { cryptonote::block b; - std::vector txs; - bc->get_db().pop_block(b, txs); + bc->get_db().pop_block(b, /*txs=*/nullptr); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); } for (int i = 0; i < add; ++i) @@ -317,8 +315,7 @@ TEST(long_term_block_weight, pop_invariant_random) for (int i = 0; i < remove; ++i) { cryptonote::block b; - std::vector txs; - bc->get_db().pop_block(b, txs); + bc->get_db().pop_block(b, /*txs=*/nullptr); ASSERT_TRUE(bc->update_next_cumulative_weight_limit()); const uint64_t effective_median = bc->get_current_cumulative_block_weight_median(); const uint64_t effective_limit = bc->get_current_cumulative_block_weight_limit();