From 16ff6a9e68d49c30b8ed9f50ad4683a061c35147 Mon Sep 17 00:00:00 2001 From: j-berman Date: Wed, 4 Sep 2024 15:37:50 -0700 Subject: [PATCH] fcmp++: trim tree when removing a block - trim_tree now re-adds trimmed outputs back to the locked outputs table. remove_output then deletes from the locked output table. - Since outputs added to the tree in a specific block may have originated from distinct younger blocks (thanks to distinct unlock times), we need to store the 8 byte output_id in the leaves table as well, so that in the event of a reorg, upon removing outputs from the tree we can add them back to the locked outputs table in the correct order. --- src/blockchain_db/blockchain_db.h | 2 +- src/blockchain_db/lmdb/db_lmdb.cpp | 94 ++++++++++++++++++++++++++---- src/blockchain_db/lmdb/db_lmdb.h | 4 +- src/blockchain_db/testdb.h | 2 +- src/fcmp_pp/curve_trees.cpp | 2 +- src/fcmp_pp/curve_trees.h | 4 +- src/fcmp_pp/fcmp_pp_rust/fcmp++.h | 2 +- tests/unit_tests/curve_trees.cpp | 11 ++-- 8 files changed, 98 insertions(+), 23 deletions(-) diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index aa973fb6c..595587d19 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1785,7 +1785,7 @@ public: // TODO: description and make private virtual void grow_tree(std::vector &&new_outputs) = 0; - virtual void trim_tree(const uint64_t trim_n_leaf_tuples) = 0; + virtual void trim_tree(const uint64_t trim_n_leaf_tuples, const uint64_t trim_block_id) = 0; // TODO: description virtual bool audit_tree(const uint64_t expected_n_leaf_tuples) const = 0; diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 0011bebba..95eb7d285 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -200,7 +200,7 @@ namespace * spent_keys input hash - * * locked_outputs block ID [{output ID, output pubkey, commitment}...] - * leaves leaf_idx {output pubkey, commitment} + * leaves leaf_idx {output ID, output pubkey, commitment} * layers layer_idx [{child_chunk_idx, child_chunk_hash}...] * * txpool_meta txn hash txn metadata @@ -828,8 +828,7 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l // Grow the tree with outputs that unlock at this block height auto unlocked_outputs = this->get_outs_at_unlock_block_id(m_height); - if (!unlocked_outputs.empty()) - this->grow_tree(std::move(unlocked_outputs)); + this->grow_tree(std::move(unlocked_outputs)); // Now that we've used the unlocked leaves to grow the tree, we can delete them from the locked outputs table this->del_locked_outs_at_block_id(m_height); @@ -935,6 +934,15 @@ void BlockchainLMDB::remove_block() if ((result = mdb_cursor_del(m_cur_block_info, 0))) throw1(DB_ERROR(lmdb_error("Failed to add removal of block info to db transaction: ", result).c_str())); + + // Get n_leaf_tuples from the new tip so we can trim the curve trees tree to the new tip + const uint64_t new_n_leaf_tuples = get_top_block_n_leaf_tuples(); + const uint64_t old_n_leaf_tuples = bi->bi_n_leaf_tuples; + + if (new_n_leaf_tuples > old_n_leaf_tuples) + throw1(DB_ERROR("Unexpected: more leaf tuples are in prev block, tree is expected to only grow")); + + this->trim_tree(old_n_leaf_tuples - new_n_leaf_tuples, bi->bi_height/*trim_block_id*/); } uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const std::pair& txp, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) @@ -1244,6 +1252,32 @@ void BlockchainLMDB::remove_output(const uint64_t amount, const uint64_t& out_in { throw1(DB_ERROR(lmdb_error("Error adding removal of output tx to db transaction", result).c_str())); } + + // Remove output from locked outputs table. We expect the output to be in the + // locked outputs table because remove_output is called when removing the + // top block from the chain, and all outputs from the top block are expected + // to be locked until they are at least 10 blocks old. + CURSOR(locked_outputs); + + const uint64_t unlock_block = cryptonote::get_unlock_block_index(ok->data.unlock_time, ok->data.height); + + MDB_val_set(k_block_id, unlock_block); + MDB_val_set(v_output, ok->output_id); + + result = mdb_cursor_get(m_cur_locked_outputs, &k_block_id, &v_output, MDB_GET_BOTH); + if (result == MDB_NOTFOUND) + { + throw0(DB_ERROR("Unexpected: output not found in m_cur_locked_outputs")); + } + else if (result) + { + throw1(DB_ERROR(lmdb_error("Error adding removal of locked output to db transaction", result).c_str())); + } + + result = mdb_cursor_del(m_cur_locked_outputs, 0); + if (result) + throw0(DB_ERROR(lmdb_error(std::string("Error deleting locked output index ").append(boost::lexical_cast(out_index).append(": ")).c_str(), result).c_str())); + result = mdb_cursor_del(m_cur_output_txs, 0); if (result) throw0(DB_ERROR(lmdb_error(std::string("Error deleting output index ").append(boost::lexical_cast(out_index).append(": ")).c_str(), result).c_str())); @@ -1475,7 +1509,7 @@ void BlockchainLMDB::grow_layer(const std::unique_ptr &curve, } } -void BlockchainLMDB::trim_tree(const uint64_t trim_n_leaf_tuples) +void BlockchainLMDB::trim_tree(const uint64_t trim_n_leaf_tuples, const uint64_t trim_block_id) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); if (trim_n_leaf_tuples == 0) @@ -1485,6 +1519,7 @@ void BlockchainLMDB::trim_tree(const uint64_t trim_n_leaf_tuples) mdb_txn_cursors *m_cursors = &m_wcursors; CURSOR(leaves) + CURSOR(locked_outputs) CURSOR(layers) const uint64_t old_n_leaf_tuples = this->get_num_leaf_tuples(); @@ -1511,11 +1546,12 @@ void BlockchainLMDB::trim_tree(const uint64_t trim_n_leaf_tuples) // Trim the leaves // TODO: trim_leaves + MDB_val_set(k_block_id, trim_block_id); for (uint64_t i = 0; i < trim_n_leaf_tuples; ++i) { - uint64_t last_leaf_tuple_idx = (old_n_leaf_tuples - 1 - i); + uint64_t leaf_tuple_idx = (old_n_leaf_tuples - trim_n_leaf_tuples + i); - MDB_val_copy k(last_leaf_tuple_idx); + MDB_val_copy k(leaf_tuple_idx); MDB_val v; int result = mdb_cursor_get(m_cur_leaves, &k, &v, MDB_SET); if (result == MDB_NOTFOUND) @@ -1523,11 +1559,20 @@ void BlockchainLMDB::trim_tree(const uint64_t trim_n_leaf_tuples) if (result != MDB_SUCCESS) throw0(DB_ERROR(lmdb_error("Failed to get leaf: ", result).c_str())); + // Re-add the output to the locked output table in order. The output should + // be in the outputs tables. + const auto o = *(fcmp_pp::curve_trees::OutputContext *)v.mv_data; + MDB_val_set(v_output, o); + result = mdb_cursor_put(m_cur_locked_outputs, &k_block_id, &v_output, MDB_APPENDDUP); + if (result != MDB_SUCCESS) + throw0(DB_ERROR(lmdb_error("Failed to re-add locked output: ", result).c_str())); + + // Delete the leaf result = mdb_cursor_del(m_cur_leaves, 0); if (result != MDB_SUCCESS) throw0(DB_ERROR(lmdb_error("Error removing leaf: ", result).c_str())); - MDEBUG("Successfully removed leaf at last_leaf_tuple_idx: " << last_leaf_tuple_idx); + MDEBUG("Successfully removed leaf at leaf_tuple_idx: " << leaf_tuple_idx); } // Trim the layers @@ -1599,6 +1644,7 @@ void BlockchainLMDB::trim_layer(const std::unique_ptr &curve, CURSOR(layers) + MDEBUG("Trimming layer " << layer_idx); MDB_val_copy k(layer_idx); // Get the number of existing elements in the layer @@ -1685,6 +1731,32 @@ uint64_t BlockchainLMDB::get_num_leaf_tuples() const return n_leaf_tuples; } +uint64_t BlockchainLMDB::get_top_block_n_leaf_tuples() const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(block_info); + + // if no blocks, return 0 + uint64_t m_height = height(); + if (m_height == 0) + { + return 0; + } + + MDB_val_copy k(m_height - 1); + MDB_val h = k; + int result = 0; + if ((result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &h, MDB_GET_BOTH))) + throw1(BLOCK_DNE(lmdb_error("Failed to get top block: ", result).c_str())); + + const uint64_t n_leaf_tuples = ((mdb_block_info *)h.mv_data)->bi_n_leaf_tuples; + TXN_POSTFIX_RDONLY(); + return n_leaf_tuples; +} + std::array BlockchainLMDB::get_tree_root() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); @@ -1811,10 +1883,10 @@ fcmp_pp::curve_trees::CurveTreesV1::LastChunkChildrenToTrim BlockchainLMDB::get_ if (result != MDB_SUCCESS) throw0(DB_ERROR(lmdb_error("Failed to get leaf: ", result).c_str())); - const auto output_pair = *(fcmp_pp::curve_trees::OutputPair *)v.mv_data; + const auto o = *(fcmp_pp::curve_trees::OutputContext *)v.mv_data; // TODO: parallelize calls to this function - auto leaf = m_curve_trees->leaf_tuple(output_pair); + auto leaf = m_curve_trees->leaf_tuple(o.output_pair); leaves_to_trim.emplace_back(std::move(leaf.O_x)); leaves_to_trim.emplace_back(std::move(leaf.I_x)); @@ -1987,8 +2059,8 @@ bool BlockchainLMDB::audit_tree(const uint64_t expected_n_leaf_tuples) const if (result != MDB_SUCCESS) throw0(DB_ERROR(lmdb_error("Failed to add leaf: ", result).c_str())); - const auto output_pair = *(fcmp_pp::curve_trees::OutputPair *)v.mv_data; - auto leaf = m_curve_trees->leaf_tuple(output_pair); + const auto o = *(fcmp_pp::curve_trees::OutputContext *)v.mv_data; + auto leaf = m_curve_trees->leaf_tuple(o.output_pair); leaf_tuples_chunk.emplace_back(std::move(leaf)); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 6276aebd2..508c6f145 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -370,7 +370,7 @@ public: // make private virtual void grow_tree(std::vector &&new_outputs); - virtual void trim_tree(const uint64_t trim_n_leaf_tuples); + virtual void trim_tree(const uint64_t trim_n_leaf_tuples, const uint64_t trim_block_id); virtual bool audit_tree(const uint64_t expected_n_leaf_tuples) const; @@ -431,6 +431,8 @@ private: virtual uint64_t get_num_leaf_tuples() const; + uint64_t get_top_block_n_leaf_tuples() const; + virtual std::array get_tree_root() const; fcmp_pp::curve_trees::CurveTreesV1::LastHashes get_tree_last_hashes() const; diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index ee268bb93..5129e2260 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -117,7 +117,7 @@ public: virtual void add_spent_key(const crypto::key_image& k_image) override {} virtual void remove_spent_key(const crypto::key_image& k_image) override {} virtual void grow_tree(std::vector &&new_outputs) override {}; - virtual void trim_tree(const uint64_t trim_n_leaf_tuples) override {}; + virtual void trim_tree(const uint64_t trim_n_leaf_tuples, const uint64_t trim_block_id) override {}; virtual bool audit_tree(const uint64_t expected_n_leaf_tuples) const override { return false; }; virtual std::array get_tree_root() const override { return {}; }; virtual uint64_t get_num_leaf_tuples() const override { return 0; }; diff --git a/src/fcmp_pp/curve_trees.cpp b/src/fcmp_pp/curve_trees.cpp index fd249db51..3ed54a35c 100644 --- a/src/fcmp_pp/curve_trees.cpp +++ b/src/fcmp_pp/curve_trees.cpp @@ -740,7 +740,7 @@ typename CurveTrees::TreeExtension CurveTrees::get_tree_extensio flattened_leaves.emplace_back(std::move(leaf.C_x)); // We can derive {O.x,I.x,C.x} from output pairs, so we store just the output pair in the db to save 32 bytes - tree_extension.leaves.tuples.emplace_back(std::move(o.output_pair)); + tree_extension.leaves.tuples.emplace_back(std::move(o)); } if (flattened_leaves.empty()) diff --git a/src/fcmp_pp/curve_trees.h b/src/fcmp_pp/curve_trees.h index 9214740b4..c5dfe7d18 100644 --- a/src/fcmp_pp/curve_trees.h +++ b/src/fcmp_pp/curve_trees.h @@ -197,9 +197,9 @@ public: struct Leaves final { // Starting leaf tuple index in the leaf layer - uint64_t start_leaf_tuple_idx{0}; + uint64_t start_leaf_tuple_idx{0}; // Contiguous leaves in a tree that start at the start_idx - std::vector tuples; + std::vector tuples; }; // A struct useful to extend an existing tree diff --git a/src/fcmp_pp/fcmp_pp_rust/fcmp++.h b/src/fcmp_pp/fcmp_pp_rust/fcmp++.h index 1e767f9d0..81f7d0282 100644 --- a/src/fcmp_pp/fcmp_pp_rust/fcmp++.h +++ b/src/fcmp_pp/fcmp_pp_rust/fcmp++.h @@ -139,4 +139,4 @@ CResult hash_trim_selene(SelenePoint existing_hash, SeleneScalar child_to_grow_back); } // extern "C" -}//namespace fcmp_pp +}//namespace fcmp_pp_rust diff --git a/tests/unit_tests/curve_trees.cpp b/tests/unit_tests/curve_trees.cpp index b5b1dc206..baedc1a24 100644 --- a/tests/unit_tests/curve_trees.cpp +++ b/tests/unit_tests/curve_trees.cpp @@ -169,9 +169,9 @@ void CurveTreesGlobalTree::extend_tree(const CurveTreesV1::TreeExtension &tree_e "unexpected leaf start idx"); m_tree.leaves.reserve(m_tree.leaves.size() + tree_extension.leaves.tuples.size()); - for (const auto &output_pair : tree_extension.leaves.tuples) + for (const auto &o : tree_extension.leaves.tuples) { - auto leaf = m_curve_trees.leaf_tuple(output_pair); + auto leaf = m_curve_trees.leaf_tuple(o.output_pair); m_tree.leaves.emplace_back(CurveTreesV1::LeafTuple{ .O_x = std::move(leaf.O_x), @@ -641,14 +641,14 @@ void CurveTreesGlobalTree::log_tree_extension(const CurveTreesV1::TreeExtension MDEBUG("Leaf start idx: " << tree_extension.leaves.start_leaf_tuple_idx); for (std::size_t i = 0; i < tree_extension.leaves.tuples.size(); ++i) { - const auto &output_pair = tree_extension.leaves.tuples[i]; + const auto &output_pair = tree_extension.leaves.tuples[i].output_pair; const auto leaf = m_curve_trees.leaf_tuple(output_pair); const auto O_x = m_curve_trees.m_c2->to_string(leaf.O_x); const auto I_x = m_curve_trees.m_c2->to_string(leaf.I_x); const auto C_x = m_curve_trees.m_c2->to_string(leaf.C_x); - MDEBUG("Leaf tuple idx " << (tree_extension.leaves.start_leaf_tuple_idx) + MDEBUG("Leaf tuple idx " << (tree_extension.leaves.start_leaf_tuple_idx + (i * CurveTreesV1::LEAF_TUPLE_SIZE)) << " : { O_x: " << O_x << " , I_x: " << I_x << " , C_x: " << C_x << " }"); } @@ -926,7 +926,8 @@ static bool trim_tree_db(const std::size_t init_leaves, MDEBUG("Successfully added initial " << init_leaves << " leaves to db, trimming by " << trim_leaves << " leaves"); - test_db.m_db->trim_tree(trim_leaves); + // Can use 0 from trim_block_id since it's unused in tests + test_db.m_db->trim_tree(trim_leaves, 0); CHECK_AND_ASSERT_MES(test_db.m_db->audit_tree(init_leaves - trim_leaves), false, "failed to trim tree in db");