Skip to content

Commit

Permalink
feat: Native world state now supports checkpointing (#11739)
Browse files Browse the repository at this point in the history
This PR introduces checkpointing to the native world state. 

Checkpointing allows for state updates to be reverted to a previous
state. This can be done to an arbitrary depth.

---------

Co-authored-by: Alex Gherghisan <[email protected]>
  • Loading branch information
PhilWindle and alexghr authored Feb 12, 2025
1 parent 7124664 commit 6464059
Show file tree
Hide file tree
Showing 20 changed files with 2,287 additions and 257 deletions.
2 changes: 1 addition & 1 deletion barretenberg/cpp/scripts/merkle_tree_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ set -e
# run commands relative to parent directory
cd $(dirname $0)/..

DEFAULT_TESTS=PersistedIndexedTreeTest.*:PersistedAppendOnlyTreeTest.*:LMDBTreeStoreTest.*:PersistedContentAddressedIndexedTreeTest.*:PersistedContentAddressedAppendOnlyTreeTest.*
DEFAULT_TESTS=PersistedIndexedTreeTest.*:PersistedAppendOnlyTreeTest.*:LMDBTreeStoreTest.*:PersistedContentAddressedIndexedTreeTest.*:PersistedContentAddressedAppendOnlyTreeTest.*:ContentAddressedCacheTest.*
TEST=${1:-$DEFAULT_TESTS}
PRESET=${PRESET:-clang16}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,21 @@ template <typename Store, typename HashingPolicy> class ContentAddressedAppendOn
using StoreType = Store;

// Asynchronous methods accept these callback function types as arguments
using EmptyResponseCallback = std::function<void(Response&)>;
using AppendCompletionCallback = std::function<void(TypedResponse<AddDataResponse>&)>;
using MetaDataCallback = std::function<void(TypedResponse<TreeMetaResponse>&)>;
using HashPathCallback = std::function<void(TypedResponse<GetSiblingPathResponse>&)>;
using FindLeafCallback = std::function<void(TypedResponse<FindLeafIndexResponse>&)>;
using GetLeafCallback = std::function<void(TypedResponse<GetLeafResponse>&)>;
using CommitCallback = std::function<void(TypedResponse<CommitResponse>&)>;
using RollbackCallback = std::function<void(Response&)>;
using RollbackCallback = EmptyResponseCallback;
using RemoveHistoricBlockCallback = std::function<void(TypedResponse<RemoveHistoricResponse>&)>;
using UnwindBlockCallback = std::function<void(TypedResponse<UnwindResponse>&)>;
using FinaliseBlockCallback = std::function<void(Response&)>;
using FinaliseBlockCallback = EmptyResponseCallback;
using GetBlockForIndexCallback = std::function<void(TypedResponse<BlockForIndexResponse>&)>;
using CheckpointCallback = EmptyResponseCallback;
using CheckpointCommitCallback = EmptyResponseCallback;
using CheckpointRevertCallback = EmptyResponseCallback;

// Only construct from provided store and thread pool, no copies or moves
ContentAddressedAppendOnlyTree(std::unique_ptr<Store> store,
Expand Down Expand Up @@ -223,6 +227,10 @@ template <typename Store, typename HashingPolicy> class ContentAddressedAppendOn

void finalise_block(const block_number_t& blockNumber, const FinaliseBlockCallback& on_completion);

void checkpoint(const CheckpointCallback& on_completion);
void commit_checkpoint(const CheckpointCommitCallback& on_completion);
void revert_checkpoint(const CheckpointRevertCallback& on_completion);

protected:
using ReadTransaction = typename Store::ReadTransaction;
using ReadTransactionPtr = typename Store::ReadTransactionPtr;
Expand Down Expand Up @@ -843,6 +851,34 @@ void ContentAddressedAppendOnlyTree<Store, HashingPolicy>::rollback(const Rollba
workers_->enqueue(job);
}

// TODO(PhilWindle): One possible optimisation is for the following 3 functions
// checkpoint, commit_checkpoint and revert_checkpoint to not use the thread pool
// It is not stricly necessary for these operations to use it. The balance is whether
// the cost of using it outweighs the benefit or checkpointing/reverting all tree concurrently

template <typename Store, typename HashingPolicy>
void ContentAddressedAppendOnlyTree<Store, HashingPolicy>::checkpoint(const CheckpointCallback& on_completion)
{
auto job = [=, this]() { execute_and_report([=, this]() { store_->checkpoint(); }, on_completion); };
workers_->enqueue(job);
}

template <typename Store, typename HashingPolicy>
void ContentAddressedAppendOnlyTree<Store, HashingPolicy>::commit_checkpoint(
const CheckpointCommitCallback& on_completion)
{
auto job = [=, this]() { execute_and_report([=, this]() { store_->commit_checkpoint(); }, on_completion); };
workers_->enqueue(job);
}

template <typename Store, typename HashingPolicy>
void ContentAddressedAppendOnlyTree<Store, HashingPolicy>::revert_checkpoint(
const CheckpointRevertCallback& on_completion)
{
auto job = [=, this]() { execute_and_report([=, this]() { store_->revert_checkpoint(); }, on_completion); };
workers_->enqueue(job);
}

template <typename Store, typename HashingPolicy>
void ContentAddressedAppendOnlyTree<Store, HashingPolicy>::remove_historic_block(
const block_number_t& blockNumber, const RemoveHistoricBlockCallback& on_completion)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <gtest/gtest.h>
#include <memory>
#include <optional>
#include <stdexcept>
Expand Down Expand Up @@ -151,17 +151,6 @@ void commit_tree(TreeType& tree, bool expected_success = true)
signal.wait_for_level();
}

void rollback_tree(TreeType& tree)
{
Signal signal;
auto completion = [&](const Response& response) -> void {
EXPECT_EQ(response.success, true);
signal.signal_level();
};
tree.rollback(completion);
signal.wait_for_level();
}

void remove_historic_block(TreeType& tree, const block_number_t& blockNumber, bool expected_success = true)
{
Signal signal;
Expand Down Expand Up @@ -1949,3 +1938,112 @@ TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_not_historically_remove_
}
remove_historic_block(tree, blockToFinalise, false);
}

TEST_F(PersistedContentAddressedAppendOnlyTreeTest, can_checkpoint_and_revert_forks)
{
constexpr size_t depth = 10;
uint32_t blockSize = 16;
std::string name = random_string();
ThreadPoolPtr pool = make_thread_pool(1);
LMDBTreeStore::SharedPtr db = std::make_shared<LMDBTreeStore>(_directory, name, _mapSize, _maxReaders);
MemoryTree<Poseidon2HashPolicy> memdb(depth);

{
std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
TreeType tree(std::move(store), pool);

std::vector<fr> values = create_values(blockSize);
add_values(tree, values);

commit_tree(tree);
}

std::unique_ptr<Store> store = std::make_unique<Store>(name, depth, db);
TreeType tree(std::move(store), pool);

// We apply a number of updates and checkpoint the tree each time

uint32_t stackDepth = 20;

std::vector<fr_sibling_path> paths(stackDepth);
uint32_t index = 0;
for (; index < stackDepth - 1; index++) {
std::vector<fr> values = create_values(blockSize);
add_values(tree, values);

paths[index] = get_sibling_path(tree, 3);

try {
checkpoint_tree(tree);
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
}

// Now add one more depth, this will be un-checkpointed
{
std::vector<fr> values = create_values(blockSize);
add_values(tree, values);
paths[index] = get_sibling_path(tree, 3);
}

index_t checkpointIndex = index;

// The tree is currently at the state of index 19
EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);

// We now alternate committing and reverting the checkpoints half way up the stack

for (; index > stackDepth / 2; index--) {
if (index % 2 == 0) {
revert_checkpoint_tree(tree, true);
checkpointIndex = index - 1;
} else {
commit_checkpoint_tree(tree, true);
}

EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
}

// Now apply another set of updates and checkpoints back to the original stack depth
for (; index < stackDepth - 1; index++) {
std::vector<fr> values = create_values(blockSize);
add_values(tree, values);

paths[index] = get_sibling_path(tree, 3);

try {
checkpoint_tree(tree);
} catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
}

// Now add one more depth, this will be un-checkpointed
{
std::vector<fr> values = create_values(blockSize);
add_values(tree, values);
paths[index] = get_sibling_path(tree, 3);
}

// We now alternatively commit and revert all the way back to the start
checkpointIndex = index;

// The tree is currently at the state of index 19
EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);

for (; index > 0; index--) {
if (index % 2 == 0) {
revert_checkpoint_tree(tree, true);
checkpointIndex = index - 1;
} else {
commit_checkpoint_tree(tree, true);
}

EXPECT_EQ(get_sibling_path(tree, 3), paths[checkpointIndex]);
}

// Should not be able to commit or revert where there is no active checkpoint
revert_checkpoint_tree(tree, false);
commit_checkpoint_tree(tree, false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -965,8 +965,10 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_insertions(
// std::cout << "Failed to find low leaf" << std::endl;
throw std::runtime_error(format("Unable to insert values into tree ",
meta.name,
" failed to find low leaf at index ",
low_leaf_index));
", failed to find low leaf at index ",
low_leaf_index,
", current size: ",
meta.size));
}
// std::cout << "Low leaf hash " << low_leaf_hash.value() << std::endl;

Expand Down Expand Up @@ -1454,7 +1456,7 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
if (!low_leaf_hash.has_value()) {
throw std::runtime_error(format("Unable to insert values into tree ",
meta.name,
" failed to find low leaf at index ",
", failed to find low leaf at index ",
low_leaf_index));
}

Expand Down
Loading

1 comment on commit 6464059

@AztecBot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'C++ Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.05.

Benchmark suite Current: 6464059 Previous: fdc2042 Ratio
wasmconstruct_proof_ultrahonk_power_of_2/20 14409.883042 ms/iter 13468.419773 ms/iter 1.07

This comment was automatically generated by workflow using github-action-benchmark.

CC: @ludamad @codygunton

Please sign in to comment.