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.
This commit is contained in:
j-berman 2024-09-04 15:37:50 -07:00
parent 41b1985f63
commit 16ff6a9e68
8 changed files with 98 additions and 23 deletions

View File

@ -1785,7 +1785,7 @@ public:
// TODO: description and make private
virtual void grow_tree(std::vector<fcmp_pp::curve_trees::OutputContext> &&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;

View File

@ -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<transaction, blobdata_ref>& 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<std::string>(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<std::string>(out_index).append(": ")).c_str(), result).c_str()));
@ -1475,7 +1509,7 @@ void BlockchainLMDB::grow_layer(const std::unique_ptr<C> &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<uint64_t> k(last_leaf_tuple_idx);
MDB_val_copy<uint64_t> 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<C> &curve,
CURSOR(layers)
MDEBUG("Trimming layer " << layer_idx);
MDB_val_copy<uint64_t> 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<uint64_t> 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<uint8_t, 32UL> 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));

View File

@ -370,7 +370,7 @@ public:
// make private
virtual void grow_tree(std::vector<fcmp_pp::curve_trees::OutputContext> &&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<uint8_t, 32UL> get_tree_root() const;
fcmp_pp::curve_trees::CurveTreesV1::LastHashes get_tree_last_hashes() const;

View File

@ -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<fcmp_pp::curve_trees::OutputContext> &&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<uint8_t, 32UL> get_tree_root() const override { return {}; };
virtual uint64_t get_num_leaf_tuples() const override { return 0; };

View File

@ -740,7 +740,7 @@ typename CurveTrees<C1, C2>::TreeExtension CurveTrees<C1, C2>::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())

View File

@ -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<OutputPair> tuples;
std::vector<OutputContext> tuples;
};
// A struct useful to extend an existing tree

View File

@ -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

View File

@ -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");