Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
content_addressed_indexed_tree.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5// =====================
6
7#pragma once
8
9#include <algorithm>
10#include <atomic>
11#include <cmath>
12#include <cstddef>
13#include <cstdint>
14#include <exception>
15#include <functional>
16#include <iostream>
17#include <memory>
18#include <mutex>
19#include <optional>
20#include <sstream>
21#include <stdexcept>
22#include <unordered_map>
23#include <unordered_set>
24#include <utility>
25#include <vector>
26
42
44
51template <typename Store, typename HashingPolicy>
52class ContentAddressedIndexedTree : public ContentAddressedAppendOnlyTree<Store, HashingPolicy> {
53 public:
55
56 // The public methods accept these function types as asynchronous callbacks
63
66
67 ContentAddressedIndexedTree(std::unique_ptr<Store> store,
69 const index_t& initial_size,
70 const std::vector<LeafValueType>& prefilled_values);
71 ContentAddressedIndexedTree(std::unique_ptr<Store> store,
73 const index_t& initial_size)
74 : ContentAddressedIndexedTree(std::move(store), workers, initial_size, std::vector<LeafValueType>()) {};
80
85
92 const AddCompletionCallbackWithWitness& completion);
93
101 uint32_t subtree_depth,
102 const AddCompletionCallbackWithWitness& completion);
103
107 void add_or_update_value(const LeafValueType& value, const AddCompletionCallback& completion);
108
114 void add_or_update_values(const std::vector<LeafValueType>& values, const AddCompletionCallback& completion);
115
123 uint32_t subtree_depth,
124 const AddCompletionCallback& completion);
125
133
140 const AddCompletionCallback& completion);
141
142 void get_leaf(const index_t& index, bool includeUncommitted, const LeafCallback& completion) const;
143
147 void find_low_leaf(const fr& leaf_key, bool includeUncommitted, const FindLowLeafCallback& on_completion) const;
148
149 void get_leaf(const index_t& index,
150 const block_number_t& blockNumber,
151 bool includeUncommitted,
152 const LeafCallback& completion) const;
153
157 void find_low_leaf(const fr& leaf_key,
158 const block_number_t& blockNumber,
159 bool includeUncommitted,
160 const FindLowLeafCallback& on_completion) const;
161
163
164 private:
168
169 struct Status {
171 std::string message;
172
173 void set_failure(const std::string& msg)
174 {
175 if (success.exchange(false)) {
176 message = msg;
177 }
178 }
179 };
180
185
186 void update_leaf_and_hash_to_root(const index_t& index,
187 const IndexedLeafValueType& leaf,
188 Signal& leader,
189 Signal& follower,
190 fr_sibling_path& previous_sibling_path);
191
193 const index_t& num_leaves_to_be_inserted,
194 const uint32_t& root_level,
195 const std::vector<LeafUpdate>& updates);
196
197 void sparse_batch_update(const std::vector<std::pair<index_t, fr>>& hashes_at_level, uint32_t level);
198
207 uint32_t subtree_depth,
208 const AddCompletionCallbackWithWitness& completion,
209 bool capture_witness);
210
219 bool capture_witness);
220
226
228 void generate_insertions(const std::shared_ptr<std::vector<std::pair<LeafValueType, index_t>>>& values_to_be_sorted,
229 const InsertionGenerationCallback& completion);
230
232 // On insertion, we always update a low leaf. If it's creating a new leaf, we need to update the pointer to
233 // point to the new one, if it's an update to an existing leaf, we need to change its payload.
235 // We don't create new leaves on update
237 };
238
243
247 const SequentialInsertionGenerationCallback& completion);
248
252
254 void perform_updates(size_t total_leaves,
255 std::shared_ptr<std::vector<LeafUpdate>> updates,
256 const UpdatesCompletionCallback& completion);
257 void perform_updates_without_witness(const index_t& highest_index,
258 std::shared_ptr<std::vector<LeafUpdate>> updates,
259 const UpdatesCompletionCallback& completion);
260
264
266 void generate_hashes_for_appending(std::shared_ptr<std::vector<IndexedLeafValueType>> leaves_to_hash,
267 const HashGenerationCallback& completion);
268
273
274 using ContentAddressedAppendOnlyTree<Store, HashingPolicy>::store_;
276 using ContentAddressedAppendOnlyTree<Store, HashingPolicy>::depth_;
279};
280
281template <typename Store, typename HashingPolicy>
283 std::unique_ptr<Store> store,
285 const index_t& initial_size,
286 const std::vector<LeafValueType>& prefilled_values)
287 : ContentAddressedAppendOnlyTree<Store, HashingPolicy>(std::move(store), workers, {}, false)
288{
289 if (initial_size < 2) {
290 throw std::runtime_error("Indexed trees must have initial size > 1");
291 }
292 if (prefilled_values.size() > initial_size) {
293 throw std::runtime_error("Number of prefilled values can't be more than initial size");
294 }
295 zero_hashes_.resize(depth_ + 1);
296
297 // Create the zero hashes for the tree
298 auto current = fr::zero();
299 for (uint32_t i = depth_; i > 0; --i) {
300 zero_hashes_[i] = current;
301 current = HashingPolicy::hash_pair(current, current);
302 }
303 zero_hashes_[0] = current;
304
305 TreeMeta meta;
306 store_->get_meta(meta);
307
308 // if the tree already contains leaves then it's been initialized in the past
309 if (meta.size > 0) {
310 return;
311 }
312
313 std::vector<IndexedLeafValueType> appended_leaves;
314 std::vector<bb::fr> appended_hashes;
315 std::vector<LeafValueType> initial_set;
316 auto num_default_values = static_cast<uint32_t>(initial_size - prefilled_values.size());
317 for (uint32_t i = 0; i < num_default_values; ++i) {
318 initial_set.push_back(LeafValueType::padding(i));
319 }
320 initial_set.insert(initial_set.end(), prefilled_values.begin(), prefilled_values.end());
321 for (uint32_t i = num_default_values; i < initial_size; ++i) {
322 if (i > 0 && (uint256_t(initial_set[i].get_key()) <= uint256_t(initial_set[i - 1].get_key()))) {
323 const auto* msg = i == num_default_values ? "Prefilled values must not be the same as the default values"
324 : "Prefilled values must be unique and sorted";
325 throw std::runtime_error(msg);
326 }
327 }
328 // Inserts the initial set of leaves as a chain in incrementing value order
329 for (uint32_t i = 0; i < initial_size; ++i) {
330 uint32_t next_index = i == (initial_size - 1) ? 0 : i + 1;
331 auto initial_leaf = IndexedLeafValueType(initial_set[i], next_index, initial_set[next_index].get_key());
332 fr leaf_hash = HashingPolicy::hash(initial_leaf.get_hash_inputs());
333 appended_leaves.push_back(initial_leaf);
334 appended_hashes.push_back(leaf_hash);
335 store_->set_leaf_key_at_index(i, initial_leaf);
336 store_->put_leaf_by_hash(leaf_hash, initial_leaf);
337 }
338 store_->put_leaf_by_hash(0, IndexedLeafValueType::empty());
339
340 TypedResponse<AddDataResponse> result;
341 Signal signal(1);
342 AppendCompletionCallback completion = [&](const TypedResponse<AddDataResponse>& _result) -> void {
343 result = _result;
344 signal.signal_level(0);
345 };
347 signal.wait_for_level(0);
348 if (!result.success) {
349 throw std::runtime_error(format("Failed to initialize tree: ", result.message));
350 }
351 store_->get_meta(meta);
352 meta.initialRoot = result.inner.root;
353 meta.initialSize = result.inner.size;
354 store_->put_meta(meta);
355 store_->commit_genesis_state();
356}
357
358template <typename Store, typename HashingPolicy>
360 bool includeUncommitted,
361 const LeafCallback& completion) const
362{
363 auto job = [=, this]() {
364 execute_and_report<GetIndexedLeafResponse<LeafValueType>>(
366 ReadTransactionPtr tx = store_->create_read_transaction();
367 RequestContext requestContext;
368 requestContext.includeUncommitted = includeUncommitted;
369 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
370 std::optional<fr> leaf_hash = find_leaf_hash(index, requestContext, *tx, false);
371 if (!leaf_hash.has_value()) {
372 response.success = false;
373 response.message = "Failed to find leaf hash for current root";
374 return;
375 }
377 store_->get_leaf_by_hash(leaf_hash.value(), *tx, includeUncommitted);
378 if (!leaf.has_value()) {
379 response.success = false;
380 response.message = "Failed to find leaf by it's hash";
381 return;
382 }
383 response.success = true;
384 response.inner.indexed_leaf = leaf.value();
385 },
386 completion);
387 };
388 workers_->enqueue(job);
389}
390
391template <typename Store, typename HashingPolicy>
393 const block_number_t& blockNumber,
394 bool includeUncommitted,
395 const LeafCallback& completion) const
396{
397 auto job = [=, this]() {
398 execute_and_report<GetIndexedLeafResponse<LeafValueType>>(
400 if (blockNumber == 0) {
401 throw std::runtime_error("Unable to get leaf for block number 0");
402 }
403 ReadTransactionPtr tx = store_->create_read_transaction();
404 BlockPayload blockData;
405 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
406 throw std::runtime_error(format("Unable to get leaf at index ",
407 index,
408 " for block ",
409 blockNumber,
410 ", failed to get block data."));
411 }
412 RequestContext requestContext;
413 requestContext.blockNumber = blockNumber;
414 requestContext.includeUncommitted = includeUncommitted;
415 requestContext.root = blockData.root;
416 std::optional<fr> leaf_hash = find_leaf_hash(index, requestContext, *tx, false);
417 if (!leaf_hash.has_value()) {
418 response.success = false;
419 response.message = format("Failed to find leaf hash for root of block ", blockNumber);
420 return;
421 }
423 store_->get_leaf_by_hash(leaf_hash.value(), *tx, includeUncommitted);
424 if (!leaf.has_value()) {
425 response.success = false;
426 response.message = format("Unable to get leaf at index ", index, " for block ", blockNumber);
427 return;
428 }
429 response.success = true;
430 response.inner.indexed_leaf = leaf.value();
431 },
432 completion);
433 };
434 workers_->enqueue(job);
435}
436
437template <typename Store, typename HashingPolicy>
439 bool includeUncommitted,
440 const FindLowLeafCallback& on_completion) const
441{
442 auto job = [=, this]() {
443 execute_and_report<GetLowIndexedLeafResponse>(
444 [=, this](TypedResponse<GetLowIndexedLeafResponse>& response) {
445 typename Store::ReadTransactionPtr tx = store_->create_read_transaction();
446 RequestContext requestContext;
447 requestContext.includeUncommitted = includeUncommitted;
448 requestContext.root = store_->get_current_root(*tx, includeUncommitted);
449 std::pair<bool, index_t> result = store_->find_low_value(leaf_key, requestContext, *tx);
450 response.inner.index = result.second;
451 response.inner.is_already_present = result.first;
452 },
453 on_completion);
454 };
455
456 workers_->enqueue(job);
457}
458
459template <typename Store, typename HashingPolicy>
461 const block_number_t& blockNumber,
462 bool includeUncommitted,
463 const FindLowLeafCallback& on_completion) const
464{
465 auto job = [=, this]() {
466 execute_and_report<GetLowIndexedLeafResponse>(
467 [=, this](TypedResponse<GetLowIndexedLeafResponse>& response) {
468 if (blockNumber == 0) {
469 throw std::runtime_error("Unable to find low leaf for block 0");
470 }
471 typename Store::ReadTransactionPtr tx = store_->create_read_transaction();
472 BlockPayload blockData;
473 if (!store_->get_block_data(blockNumber, blockData, *tx)) {
474 throw std::runtime_error(
475 format("Unable to find low leaf for block ", blockNumber, ", failed to get block data."));
476 }
477 RequestContext requestContext;
478 requestContext.blockNumber = blockNumber;
479 requestContext.includeUncommitted = includeUncommitted;
480 requestContext.root = blockData.root;
481 requestContext.maxIndex = blockData.size;
482 std::pair<bool, index_t> result = store_->find_low_value(leaf_key, requestContext, *tx);
483 response.inner.index = result.second;
484 response.inner.is_already_present = result.first;
485 },
486 on_completion);
487 };
488
489 workers_->enqueue(job);
490}
491
492template <typename Store, typename HashingPolicy>
498
499template <typename Store, typename HashingPolicy>
501 const AddCompletionCallback& completion)
502{
503 add_or_update_values(std::vector<LeafValueType>{ value }, 1, completion);
504}
505
506template <typename Store, typename HashingPolicy>
508 const std::vector<LeafValueType>& values, const AddCompletionCallbackWithWitness& completion)
509{
510 add_or_update_values(values, 0, completion);
511}
512
513template <typename Store, typename HashingPolicy>
515 const AddCompletionCallback& completion)
516{
517 add_or_update_values(values, 0, completion);
518}
519
520template <typename Store, typename HashingPolicy>
522 const std::vector<LeafValueType>& values,
523 uint32_t subtree_depth,
524 const AddCompletionCallbackWithWitness& completion)
525{
526 add_or_update_values_internal(values, subtree_depth, completion, true);
527}
528
529template <typename Store, typename HashingPolicy>
531 uint32_t subtree_depth,
532 const AddCompletionCallback& completion)
533{
534 auto final_completion = [=](const TypedResponse<AddIndexedDataResponse<LeafValueType>>& add_data_response) {
536 response.success = add_data_response.success;
537 response.message = add_data_response.message;
538 if (add_data_response.success) {
539 response.inner = add_data_response.inner.add_data_result;
540 }
541 // Trigger the client's provided callback
542 completion(response);
543 };
544 add_or_update_values_internal(values, subtree_depth, final_completion, false);
545}
546
547template <typename Store, typename HashingPolicy>
549 const std::vector<LeafValueType>& values,
550 uint32_t subtree_depth,
551 const AddCompletionCallbackWithWitness& completion,
552 bool capture_witness)
553{
554 // We first take a copy of the leaf values and their locations within the set given to us
557 for (size_t i = 0; i < values.size(); ++i) {
558 (*values_to_be_sorted)[i] = std::make_pair(values[i], i);
559 }
560
561 // This is to collect some state from the asynchronous operations we are about to perform
562 struct IntermediateResults {
563 // new hashes that will be appended to the tree
564 std::shared_ptr<std::vector<fr>> hashes_to_append;
565 // info about the low leaves that have been updated
567 fr_sibling_path subtree_path;
568 std::atomic<uint32_t> count;
569 Status status;
570
571 // We set to 2 here as we will kick off the 2 main async operations concurrently and we need to trakc thri
572 // completion
573 IntermediateResults()
574 : count(2)
575 {
576 // Default to success, set to false on error
577 status.success = true;
578 };
579 };
581
582 auto on_error = [=](const std::string& message) {
583 try {
585 response.success = false;
586 response.message = message;
587 completion(response);
588 } catch (std::exception&) {
589 }
590 };
591
592 // This is the final callback triggered once the leaves have been appended to the tree
593 auto final_completion = [=](const TypedResponse<AddDataResponse>& add_data_response) {
595 response.success = add_data_response.success;
596 response.message = add_data_response.message;
597 if (add_data_response.success) {
598 if (capture_witness) {
599 response.inner.subtree_path = std::move(results->subtree_path);
600 response.inner.sorted_leaves = std::move(values_to_be_sorted);
601 response.inner.low_leaf_witness_data = std::move(results->low_leaf_witness_data);
602 }
603 response.inner.add_data_result = std::move(add_data_response.inner);
604 }
605 // Trigger the client's provided callback
606 completion(response);
607 };
608
609 auto sibling_path_completion = [=, this](const TypedResponse<GetSiblingPathResponse>& response) {
610 if (!response.success) {
611 results->status.set_failure(response.message);
612 } else {
613 if (capture_witness) {
614 results->subtree_path = std::move(response.inner.path);
615 }
617 (*results->hashes_to_append), final_completion, false);
618 }
619 };
620
621 // This signals the completion of the appended hash generation
622 // If the low leaf updates are also completed then we will append the leaves
623 HashGenerationCallback hash_completion = [=, this](const TypedResponse<HashGenerationResponse>& hashes_response) {
624 if (!hashes_response.success) {
625 results->status.set_failure(hashes_response.message);
626 } else {
627 results->hashes_to_append = hashes_response.inner.hashes;
628 }
629
630 if (results->count.fetch_sub(1) == 1) {
631 if (!results->status.success) {
632 on_error(results->status.message);
633 return;
634 }
635 if (capture_witness) {
637 subtree_depth, sibling_path_completion, true);
638 return;
639 }
641 response.success = true;
642
643 sibling_path_completion(response);
644 }
645 };
646
647 // This signals the completion of the low leaf updates
648 // If the append hash generation has also copleted then the hashes can be appended
649 UpdatesCompletionCallback updates_completion =
650 [=, this](const TypedResponse<UpdatesCompletionResponse>& updates_response) {
651 if (!updates_response.success) {
652 results->status.set_failure(updates_response.message);
653 } else if (capture_witness) {
654 results->low_leaf_witness_data = updates_response.inner.update_witnesses;
655 }
656
657 if (results->count.fetch_sub(1) == 1) {
658 if (!results->status.success) {
659 on_error(results->status.message);
660 return;
661 }
662 if (capture_witness) {
664 subtree_depth, sibling_path_completion, true);
665 return;
666 }
668 response.success = true;
669
670 sibling_path_completion(response);
671 }
672 };
673
674 // This signals the completion of the insertion data generation
675 // Here we will enqueue both the generation of the appended hashes and the low leaf updates
676 InsertionGenerationCallback insertion_generation_completed =
677 [=, this](const TypedResponse<InsertionGenerationResponse>& insertion_response) {
678 if (!insertion_response.success) {
679 on_error(insertion_response.message);
680 return;
681 }
682 workers_->enqueue([=, this]() {
683 generate_hashes_for_appending(insertion_response.inner.leaves_to_append, hash_completion);
684 });
685 if (capture_witness) {
686 perform_updates(values.size(), insertion_response.inner.low_leaf_updates, updates_completion);
687 return;
688 }
689 perform_updates_without_witness(
690 insertion_response.inner.highest_index, insertion_response.inner.low_leaf_updates, updates_completion);
691 };
692
693 // We start by enqueueing the insertion data generation
694 workers_->enqueue([=, this]() { generate_insertions(values_to_be_sorted, insertion_generation_completed); });
695}
696
697// Performs a number of leaf updates in the tree, fetching witnesses for the updates in the order they've been applied,
698// with the caveat that all nodes fetched need to be in the cache. Otherwise, they'll be assumed to be empty,
699// potentially erasing part of the tree. This function won't fetch nodes from DB.
700template <typename Store, typename HashingPolicy>
702 size_t total_leaves, std::shared_ptr<std::vector<LeafUpdate>> updates, const UpdatesCompletionCallback& completion)
703{
705 total_leaves,
706 LeafUpdateWitnessData<LeafValueType>{ IndexedLeafValueType::empty(), 0, fr_sibling_path(depth_, fr::zero()) });
707
708 // early return, no updates to perform
709 if (updates->size() == 0) {
711 response.success = true;
712 response.inner.update_witnesses = update_witnesses;
713 completion(response);
714 return;
715 }
716
717 // We now kick off multiple workers to perform the low leaf updates
718 // We create set of signals to coordinate the workers as the move up the tree
719 // We don';t want to flood the provided thread pool with jobs that can't be processed so we throttle the rate
720 // at which jobs are added to the thread pool. This enables other trees to utilise the same pool
721 // NOTE: Wrapping signals with unique_ptr to make them movable (re: mac build).
722 // Feel free to reconsider and make Signal movable.
725 // The first signal is set to 0. This ensures the first worker up the tree is not impeded
726 signals->emplace_back(std::make_unique<Signal>(0));
727 // Workers will follow their leaders up the tree, being triggered by the signal in front of them
728 for (size_t i = 0; i < updates->size(); ++i) {
729 signals->emplace_back(std::make_unique<Signal>(static_cast<uint32_t>(1 + depth_)));
730 }
731
732 {
733 struct EnqueuedOps {
734 // This queue is to be accessed under the following mutex
735 std::queue<std::function<void()>> operations;
736 std::mutex enqueueMutex;
737
738 void enqueue_next(ThreadPool& workers)
739 {
740 std::unique_lock lock(enqueueMutex);
741 if (operations.empty()) {
742 return;
743 }
744 auto nextOp = operations.front();
745 operations.pop();
746 workers.enqueue(nextOp);
747 }
748
749 void enqueue_initial(ThreadPool& workers, size_t numJobs)
750 {
751 std::unique_lock lock(enqueueMutex);
752 for (size_t i = 0; i < numJobs && !operations.empty(); ++i) {
753 auto nextOp = operations.front();
754 operations.pop();
755 workers.enqueue(nextOp);
756 }
757 }
758
759 void add_job(std::function<void()>& job) { operations.push(job); }
760 };
761
763
764 for (uint32_t i = 0; i < updates->size(); ++i) {
765 std::function<void()> op = [=, this]() {
766 LeafUpdate& update = (*updates)[i];
767 Signal& leaderSignal = *(*signals)[i];
768 Signal& followerSignal = *(*signals)[i + 1];
769 try {
770 auto& current_witness_data = update_witnesses->at(i);
771 current_witness_data.leaf = update.original_leaf;
772 current_witness_data.index = update.leaf_index;
773 current_witness_data.path.clear();
774
775 update_leaf_and_hash_to_root(update.leaf_index,
776 update.updated_leaf,
777 leaderSignal,
778 followerSignal,
779 current_witness_data.path);
780 } catch (std::exception& e) {
781 status->set_failure(e.what());
782 // ensure that any followers are not blocked by our failure
783 followerSignal.signal_level(0);
784 }
785
786 {
787 // If there are more jobs then push another onto the thread pool
788 enqueuedOperations->enqueue_next(*workers_);
789 }
790
791 if (i == updates->size() - 1) {
793 response.success = status->success;
794 response.message = status->message;
795 if (response.success) {
796 response.inner.update_witnesses = update_witnesses;
797 }
798 completion(response);
799 }
800 };
801 enqueuedOperations->add_job(op);
802 }
803
804 {
805 // Kick off an initial set of jobs, capped at the depth of the tree or the size of the thread pool,
806 // whichever is lower
807 size_t initialSize = std::min(workers_->num_threads(), static_cast<size_t>(depth_));
808 enqueuedOperations->enqueue_initial(*workers_, initialSize);
809 }
810 }
811}
812
813// Performs a number of leaf updates in the tree, with the caveat that all nodes fetched need to be in the cache
814// Otherwise, they'll be assumed to be empty, potentially erasing part of the tree. This function won't fetch nodes from
815// DB.
816template <typename Store, typename HashingPolicy>
818 const index_t& highest_index,
819 std::shared_ptr<std::vector<LeafUpdate>> updates,
820 const UpdatesCompletionCallback& completion)
821{
822 // early return, no updates to perform
823 if (updates->size() == 0) {
825 response.success = true;
826 completion(response);
827 return;
828 }
829
831
832 auto log2Ceil = [=](uint64_t value) {
833 uint64_t log = numeric::get_msb(value);
834 uint64_t temp = static_cast<uint64_t>(1) << log;
835 return temp == value ? log : log + 1;
836 };
837
838 uint64_t indexPower2Ceil = log2Ceil(highest_index + 1);
839 index_t span = static_cast<index_t>(std::pow(2UL, indexPower2Ceil));
840 uint64_t numBatchesPower2Floor = numeric::get_msb(workers_->num_threads());
841 index_t numBatches = static_cast<index_t>(std::pow(2UL, numBatchesPower2Floor));
842 index_t batchSize = span / numBatches;
843 batchSize = std::max(batchSize, static_cast<index_t>(2));
844 index_t startIndex = 0;
845 indexPower2Ceil = log2Ceil(batchSize);
846 uint32_t rootLevel = depth_ - static_cast<uint32_t>(indexPower2Ceil);
847
848 // std::cout << "HIGHEST INDEX " << highest_index << " SPAN " << span << " NUM BATCHES " << numBatches
849 // << " BATCH SIZE " << batchSize << " NUM THREADS " << workers_->num_threads() << " ROOT LEVEL "
850 // << rootLevel << std::endl;
851
852 struct BatchInsertResults {
855
856 BatchInsertResults(uint32_t init)
857 : count(init)
858 , roots(init, std::make_pair(false, fr::zero()))
859 {}
860 };
862
863 for (uint32_t i = 0; i < numBatches; ++i) {
864 std::function<void()> op = [=, this]() {
865 try {
866 bool withinRange = startIndex <= highest_index;
867 if (withinRange) {
868 opCount->roots[i] = sparse_batch_update(startIndex, batchSize, rootLevel, *updates);
869 }
870 } catch (std::exception& e) {
871 status->set_failure(e.what());
872 }
873
874 if (opCount->count.fetch_sub(1) == 1) {
875 std::vector<std::pair<index_t, fr>> hashes_at_level;
876 for (size_t i = 0; i < opCount->roots.size(); i++) {
877 if (opCount->roots[i].first) {
878 hashes_at_level.push_back(std::make_pair(i, opCount->roots[i].second));
879 }
880 }
881 sparse_batch_update(hashes_at_level, rootLevel);
882
884 response.success = true;
885 completion(response);
886 }
887 };
888 startIndex += batchSize;
889 workers_->enqueue(op);
890 }
891}
892
893template <typename Store, typename HashingPolicy>
895 std::shared_ptr<std::vector<IndexedLeafValueType>> leaves_to_hash, const HashGenerationCallback& completion)
896{
897 execute_and_report<HashGenerationResponse>(
898 [=, this](TypedResponse<HashGenerationResponse>& response) {
899 response.inner.hashes = std::make_shared<std::vector<fr>>(leaves_to_hash->size(), 0);
900 std::vector<IndexedLeafValueType>& leaves = *leaves_to_hash;
901 for (uint32_t i = 0; i < leaves.size(); ++i) {
902 IndexedLeafValueType& leaf = leaves[i];
903 fr hash = leaf.is_empty() ? fr::zero() : HashingPolicy::hash(leaf.get_hash_inputs());
904 (*response.inner.hashes)[i] = hash;
905 store_->put_leaf_by_hash(hash, leaf);
906 }
907 },
908 completion);
909}
910
911template <typename Store, typename HashingPolicy>
913 const std::shared_ptr<std::vector<std::pair<LeafValueType, index_t>>>& values_to_be_sorted,
914 const InsertionGenerationCallback& completion)
915{
916 execute_and_report<InsertionGenerationResponse>(
917 [=, this](TypedResponse<InsertionGenerationResponse>& response) {
918 // The first thing we do is sort the values into descending order but maintain knowledge of their
919 // orignal order
920 struct {
922 {
923 uint256_t aValue = a.first.get_key();
924 uint256_t bValue = b.first.get_key();
925 return aValue == bValue ? a.second < b.second : aValue > bValue;
926 }
927 } comp;
928 std::sort(values_to_be_sorted->begin(), values_to_be_sorted->end(), comp);
929
930 std::vector<std::pair<LeafValueType, index_t>>& values = *values_to_be_sorted;
931
932 // std::cout << "Generating insertions " << std::endl;
933
934 // Now that we have the sorted values we need to identify the leaves that need updating.
935 // This is performed sequentially and is stored in this 'leaf_update' struct
936 response.inner.highest_index = 0;
937 response.inner.low_leaf_updates = std::make_shared<std::vector<LeafUpdate>>();
938 response.inner.low_leaf_updates->reserve(values.size());
939 response.inner.leaves_to_append =
940 std::make_shared<std::vector<IndexedLeafValueType>>(values.size(), IndexedLeafValueType::empty());
941 index_t num_leaves_to_be_inserted = values.size();
942 std::set<uint256_t> unique_values;
943
944 {
945 ReadTransactionPtr tx = store_->create_read_transaction();
946 TreeMeta meta;
947 store_->get_meta(meta);
948 RequestContext requestContext;
949 requestContext.includeUncommitted = true;
950 // Ensure that the tree is not going to be overfilled
951 index_t new_total_size = num_leaves_to_be_inserted + meta.size;
952 if (new_total_size > max_size_) {
953 throw std::runtime_error(format("Unable to insert values into tree ",
954 meta.name,
955 " new size: ",
956 new_total_size,
957 " max size: ",
958 max_size_));
959 }
960 for (size_t i = 0; i < values.size(); ++i) {
961 std::pair<LeafValueType, size_t>& value_pair = values[i];
962 size_t index_into_appended_leaves = value_pair.second;
963 index_t index_of_new_leaf = static_cast<index_t>(index_into_appended_leaves) + meta.size;
964 if (value_pair.first.is_empty()) {
965 continue;
966 }
967 fr value = value_pair.first.get_key();
968 auto it = unique_values.insert(value);
969 if (!it.second) {
970 throw std::runtime_error(format(
971 "Duplicate key not allowed in same batch, key value: ", value, ", tree: ", meta.name));
972 }
973
974 // This gives us the leaf that need updating
975 index_t low_leaf_index = 0;
976 bool is_already_present = false;
977
978 requestContext.root = store_->get_current_root(*tx, true);
979 std::tie(is_already_present, low_leaf_index) =
980 store_->find_low_value(value_pair.first.get_key(), requestContext, *tx);
981 // std::cout << "Found low leaf index " << low_leaf_index << std::endl;
982
983 // Try and retrieve the leaf pre-image from the cache first.
984 // If unsuccessful, derive from the tree and hash based lookup
985 std::optional<IndexedLeafValueType> optional_low_leaf =
986 store_->get_cached_leaf_by_index(low_leaf_index);
988
989 if (optional_low_leaf.has_value()) {
990 low_leaf = optional_low_leaf.value();
991 // std::cout << "Found cached low leaf at index: " << low_leaf_index << " : " << low_leaf
992 // << std::endl;
993 } else {
994 // std::cout << "Looking for leaf at index " << low_leaf_index << std::endl;
995 std::optional<fr> low_leaf_hash = find_leaf_hash(low_leaf_index, requestContext, *tx, true);
996
997 if (!low_leaf_hash.has_value()) {
998 // std::cout << "Failed to find low leaf" << std::endl;
999 throw std::runtime_error(format("Unable to insert values into tree ",
1000 meta.name,
1001 ", failed to find low leaf at index ",
1002 low_leaf_index,
1003 ", current size: ",
1004 meta.size));
1005 }
1006 // std::cout << "Low leaf hash " << low_leaf_hash.value() << std::endl;
1007
1008 std::optional<IndexedLeafValueType> low_leaf_option =
1009 store_->get_leaf_by_hash(low_leaf_hash.value(), *tx, true);
1010
1011 if (!low_leaf_option.has_value()) {
1012 // std::cout << "No pre-image" << std::endl;
1013 throw std::runtime_error(format("Unable to insert values into tree ",
1014 meta.name,
1015 " failed to get leaf pre-image by hash for index ",
1016 low_leaf_index));
1017 }
1018 // std::cout << "Low leaf pre-image " << low_leaf_option.value() << std::endl;
1019 low_leaf = low_leaf_option.value();
1020 }
1021
1022 LeafUpdate low_update = {
1023 .leaf_index = low_leaf_index,
1024 .updated_leaf = IndexedLeafValueType::empty(),
1025 .original_leaf = low_leaf,
1026 };
1027
1028 // Capture the index and original value of the 'low' leaf
1029
1030 if (!is_already_present) {
1031 // Update the current leaf to point it to the new leaf
1032 IndexedLeafValueType new_leaf =
1033 IndexedLeafValueType(value_pair.first, low_leaf.nextIndex, low_leaf.nextKey);
1034
1035 low_leaf.nextIndex = index_of_new_leaf;
1036 low_leaf.nextKey = value;
1037 store_->set_leaf_key_at_index(index_of_new_leaf, new_leaf);
1038
1039 // std::cout << "NEW LEAf TO BE INSERTED at index: " << index_of_new_leaf << " : " << new_leaf
1040 // << std::endl;
1041
1042 // std::cout << "Low leaf found at index " << low_leaf_index << " index of new leaf "
1043 // << index_of_new_leaf << std::endl;
1044
1045 store_->put_cached_leaf_by_index(low_leaf_index, low_leaf);
1046 // leaves_pre[low_leaf_index] = low_leaf;
1047 low_update.updated_leaf = low_leaf;
1048
1049 // Update the set of leaves to append
1050 (*response.inner.leaves_to_append)[index_into_appended_leaves] = new_leaf;
1051 } else if (IndexedLeafValueType::is_updateable()) {
1052 // Update the current leaf's value, don't change it's link
1053 IndexedLeafValueType replacement_leaf =
1054 IndexedLeafValueType(value_pair.first, low_leaf.nextIndex, low_leaf.nextKey);
1055 // IndexedLeafValueType empty_leaf = IndexedLeafValueType::empty();
1056 // don't update the index for this empty leaf
1057 // std::cout << "Low leaf updated at index " << low_leaf_index << " index of new leaf "
1058 // << index_of_new_leaf << std::endl;
1059 // store_->set_leaf_key_at_index(index_of_new_leaf, empty_leaf);
1060 store_->put_cached_leaf_by_index(low_leaf_index, replacement_leaf);
1061 low_update.updated_leaf = replacement_leaf;
1062 // The set of appended leaves already has an empty leaf in the slot at index
1063 // 'index_into_appended_leaves'
1064 } else {
1065 throw std::runtime_error(format("Unable to insert values into tree ",
1066 meta.name,
1067 " leaf type ",
1068 IndexedLeafValueType::name(),
1069 " is not updateable and ",
1070 value_pair.first.get_key(),
1071 " is already present"));
1072 }
1073 response.inner.highest_index = std::max(response.inner.highest_index, low_leaf_index);
1074
1075 response.inner.low_leaf_updates->push_back(low_update);
1076 }
1077 }
1078 },
1079 completion);
1080}
1081
1082template <typename Store, typename HashingPolicy>
1084 const index_t& leaf_index,
1085 const IndexedLeafValueType& leaf,
1086 Signal& leader,
1087 Signal& follower,
1088 fr_sibling_path& previous_sibling_path)
1089{
1090 auto get_optional_node = [&](uint32_t level, index_t index) -> std::optional<fr> {
1091 fr value = fr::zero();
1092 // std::cout << "Getting node at " << level << " : " << index << std::endl;
1093 bool success = store_->get_cached_node_by_index(level, index, value);
1094 return success ? std::optional<fr>(value) : std::nullopt;
1095 };
1096 // We are a worker at a specific leaf index.
1097 // We are going to move up the tree and at each node/level:
1098 // 1. Wait for the level above to become 'signalled' as clear for us to write into
1099 // 2. Read the node and it's sibling
1100 // 3. Write the new node value
1101 index_t index = leaf_index;
1102 uint32_t level = depth_;
1103 fr new_hash = leaf.leaf.is_empty() ? fr::zero() : HashingPolicy::hash(leaf.get_hash_inputs());
1104
1105 // Wait until we see that our leader has cleared 'depth_ - 1' (i.e. the level above the leaves that we are about
1106 // to write into) this ensures that our leader is not still reading the leaves
1107 uint32_t leader_level = depth_ - 1;
1108 leader.wait_for_level(leader_level);
1109
1110 // Write the new leaf hash in place
1111 store_->put_cached_node_by_index(level, index, new_hash);
1112 // std::cout << "Writing leaf hash: " << new_hash << " at index " << index << std::endl;
1113 store_->put_leaf_by_hash(new_hash, leaf);
1114 // std::cout << "Writing level: " << level << std::endl;
1115 store_->put_node_by_hash(new_hash, { .left = std::nullopt, .right = std::nullopt, .ref = 1 });
1116 // Signal that this level has been written
1117 follower.signal_level(level);
1118
1119 while (level > 0) {
1120 if (level > 1) {
1121 // Level is > 1. Therefore we need to wait for our leader to have written to the level above meaning we
1122 // can read from it
1123 leader_level = level - 1;
1124 leader.wait_for_level(leader_level);
1125 }
1126
1127 // Now that we have extracted the hash path from the row above
1128 // we can compute the new hash at that level and write it
1129 bool is_right = static_cast<bool>(index & 0x01);
1130 std::optional<fr> new_right_option = is_right ? new_hash : get_optional_node(level, index + 1);
1131 std::optional<fr> new_left_option = is_right ? get_optional_node(level, index - 1) : new_hash;
1132 fr new_right_value = new_right_option.has_value() ? new_right_option.value() : zero_hashes_[level];
1133 fr new_left_value = new_left_option.has_value() ? new_left_option.value() : zero_hashes_[level];
1134
1135 previous_sibling_path.emplace_back(is_right ? new_left_value : new_right_value);
1136 new_hash = HashingPolicy::hash_pair(new_left_value, new_right_value);
1137 index >>= 1;
1138 --level;
1139 if (level > 0) {
1140 // Before we write we need to ensure that our leader has already written to the row above it
1141 // otherwise it could still be reading from this level
1142 leader_level = level - 1;
1143 leader.wait_for_level(leader_level);
1144 }
1145
1146 // Write this node and signal that it is done
1147 store_->put_cached_node_by_index(level, index, new_hash);
1148 store_->put_node_by_hash(new_hash, { .left = new_left_option, .right = new_right_option, .ref = 1 });
1149 // if (level == 0) {
1150 // std::cout << "NEW VALUE AT LEVEL " << level << " : " << new_hash << " LEFT: " << new_left_value
1151 // << " RIGHT: " << new_right_value << std::endl;
1152 // }
1153
1154 follower.signal_level(level);
1155 }
1156}
1157
1158template <typename Store, typename HashingPolicy>
1160 const std::vector<std::pair<index_t, fr>>& hashes_at_level, uint32_t level)
1161{
1162 auto get_optional_node = [&](uint32_t level, index_t index) -> std::optional<fr> {
1163 fr value = fr::zero();
1164
1165 bool success = store_->get_cached_node_by_index(level, index, value);
1166 // std::cout << "Getting node at " << level << " : " << index << " success " << success << std::endl;
1167 return success ? std::optional<fr>(value) : std::nullopt;
1168 };
1169 std::vector<index_t> indices;
1170 indices.reserve(hashes_at_level.size());
1172 // grab the hashes
1173 for (size_t i = 0; i < hashes_at_level.size(); ++i) {
1174 index_t index = hashes_at_level[i].first;
1175 fr hash = hashes_at_level[i].second;
1176 hashes[index] = hash;
1177 indices.push_back(index);
1178 // std::cout << "index " << index << " hash " << hash << std::endl;
1179 }
1180 std::unordered_set<index_t> unique_indices;
1181 while (level > 0) {
1182 std::vector<index_t> next_indices;
1184 for (index_t index : indices) {
1185 index_t parent_index = index >> 1;
1186 auto it = unique_indices.insert(parent_index);
1187 if (!it.second) {
1188 continue;
1189 }
1190 next_indices.push_back(parent_index);
1191 bool is_right = static_cast<bool>(index & 0x01);
1192 fr new_hash = hashes[index];
1193 std::optional<fr> new_right_option = is_right ? new_hash : get_optional_node(level, index + 1);
1194 std::optional<fr> new_left_option = is_right ? get_optional_node(level, index - 1) : new_hash;
1195 fr new_right_value = new_right_option.has_value() ? new_right_option.value() : zero_hashes_[level];
1196 fr new_left_value = new_left_option.has_value() ? new_left_option.value() : zero_hashes_[level];
1197
1198 new_hash = HashingPolicy::hash_pair(new_left_value, new_right_value);
1199 store_->put_cached_node_by_index(level - 1, parent_index, new_hash);
1200 store_->put_node_by_hash(new_hash, { .left = new_left_option, .right = new_right_option, .ref = 1 });
1201 next_hashes[parent_index] = new_hash;
1202 }
1203 indices = std::move(next_indices);
1204 hashes = std::move(next_hashes);
1205 unique_indices.clear();
1206 --level;
1207 }
1208}
1209
1210template <typename Store, typename HashingPolicy>
1212 const index_t& start_index,
1213 const index_t& num_leaves_to_be_inserted,
1214 const uint32_t& root_level,
1215 const std::vector<LeafUpdate>& updates)
1216{
1217 auto get_optional_node = [&](uint32_t level, index_t index) -> std::optional<fr> {
1218 fr value = fr::zero();
1219 // std::cout << "Getting node at " << level << " : " << index << std::endl;
1220 bool success = store_->get_cached_node_by_index(level, index, value);
1221 return success ? std::optional<fr>(value) : std::nullopt;
1222 };
1223
1224 uint32_t level = depth_;
1225
1226 std::vector<index_t> indices;
1227 indices.reserve(updates.size());
1228
1229 fr new_hash = fr::zero();
1230
1231 std::unordered_set<index_t> unique_indices;
1233 index_t end_index = start_index + num_leaves_to_be_inserted;
1234 // Insert the leaves
1235 for (size_t i = 0; i < updates.size(); ++i) {
1236
1237 const LeafUpdate& update = updates[i];
1238 if (update.leaf_index < start_index || update.leaf_index >= end_index) {
1239 continue;
1240 }
1241
1242 // one of our leaves
1243 new_hash = update.updated_leaf.leaf.is_empty() ? fr::zero()
1244 : HashingPolicy::hash(update.updated_leaf.get_hash_inputs());
1245
1246 // std::cout << "Hashing leaf at level " << level << " index " << update.leaf_index << " batch start "
1247 // << start_index << " hash " << leaf_hash << std::endl;
1248
1249 // Write the new leaf hash in place
1250 store_->put_cached_node_by_index(level, update.leaf_index, new_hash);
1251 // std::cout << "Writing leaf hash: " << new_hash << " at index " << index << std::endl;
1252 store_->put_leaf_by_hash(new_hash, update.updated_leaf);
1253 // std::cout << "Writing level: " << level << std::endl;
1254 store_->put_node_by_hash(new_hash, { .left = std::nullopt, .right = std::nullopt, .ref = 1 });
1255 indices.push_back(update.leaf_index);
1256 hashes[update.leaf_index] = new_hash;
1257 // std::cout << "Leaf " << new_hash << " at index " << update.leaf_index << std::endl;
1258 }
1259
1260 if (indices.empty()) {
1261 return std::make_pair(false, fr::zero());
1262 }
1263
1264 while (level > root_level) {
1265 std::vector<index_t> next_indices;
1267 for (index_t index : indices) {
1268 index_t parent_index = index >> 1;
1269 auto it = unique_indices.insert(parent_index);
1270 if (!it.second) {
1271 continue;
1272 }
1273 next_indices.push_back(parent_index);
1274 bool is_right = static_cast<bool>(index & 0x01);
1275 new_hash = hashes[index];
1276 std::optional<fr> new_right_option = is_right ? new_hash : get_optional_node(level, index + 1);
1277 std::optional<fr> new_left_option = is_right ? get_optional_node(level, index - 1) : new_hash;
1278 fr new_right_value = new_right_option.has_value() ? new_right_option.value() : zero_hashes_[level];
1279 fr new_left_value = new_left_option.has_value() ? new_left_option.value() : zero_hashes_[level];
1280
1281 new_hash = HashingPolicy::hash_pair(new_left_value, new_right_value);
1282 store_->put_cached_node_by_index(level - 1, parent_index, new_hash);
1283 store_->put_node_by_hash(new_hash, { .left = new_left_option, .right = new_right_option, .ref = 1 });
1284 next_hashes[parent_index] = new_hash;
1285 // std::cout << "Created parent hash at level " << level - 1 << " index " << parent_index << " hash "
1286 // << new_hash << " left " << new_left_value << " right " << new_right_value << std::endl;
1287 }
1288 indices = std::move(next_indices);
1289 hashes = std::move(next_hashes);
1290 unique_indices.clear();
1291 --level;
1292 }
1293 // std::cout << "Returning hash " << new_hash << std::endl;
1294 return std::make_pair(true, new_hash);
1295}
1296
1297template <typename Store, typename HashingPolicy>
1300{
1301 add_or_update_values_sequentially_internal(values, completion, true);
1302}
1303
1304template <typename Store, typename HashingPolicy>
1306 const std::vector<LeafValueType>& values, const AddCompletionCallback& completion)
1307{
1308 auto final_completion =
1311 response.success = add_data_response.success;
1312 response.message = add_data_response.message;
1313 if (add_data_response.success) {
1314 response.inner = add_data_response.inner.add_data_result;
1315 }
1316 // Trigger the client's provided callback
1317 completion(response);
1318 };
1319 add_or_update_values_sequentially_internal(values, final_completion, false);
1320}
1321
1322template <typename Store, typename HashingPolicy>
1324 const std::vector<LeafValueType>& values,
1326 bool capture_witness)
1327{
1328
1329 // This struct is used to collect some state from the asynchronous operations we are about to perform
1330 struct IntermediateResults {
1331 std::vector<InsertionUpdates> updates_to_perform;
1332 size_t appended_leaves = 0;
1333 };
1335
1336 auto on_error = [=](const std::string& message) {
1337 try {
1339 response.success = false;
1340 response.message = message;
1341 completion(response);
1342 } catch (std::exception&) {
1343 }
1344 };
1345
1346 // This is the final callback triggered once all the leaves have been inserted in the tree
1347 auto final_completion = [=, this](const TypedResponse<UpdatesCompletionResponse>& updates_completion_response) {
1349 response.success = updates_completion_response.success;
1350 response.message = updates_completion_response.message;
1351 if (updates_completion_response.success) {
1352 {
1353 TreeMeta meta;
1354 ReadTransactionPtr tx = store_->create_read_transaction();
1355 store_->get_meta(meta);
1356
1357 index_t new_total_size = results->appended_leaves + meta.size;
1358 meta.size = new_total_size;
1359 meta.root = store_->get_current_root(*tx, true);
1360
1361 store_->put_meta(meta);
1362 }
1363
1364 if (capture_witness) {
1365 // Split results->update_witnesses between low_leaf_witness_data and insertion_witness_data
1366 response.inner.insertion_witness_data =
1368 response.inner.insertion_witness_data->reserve(results->updates_to_perform.size());
1369
1370 response.inner.low_leaf_witness_data =
1372 response.inner.low_leaf_witness_data->reserve(results->updates_to_perform.size());
1373
1374 size_t current_witness_index = 0;
1375 for (size_t i = 0; i < results->updates_to_perform.size(); ++i) {
1376 LeafUpdateWitnessData<LeafValueType> low_leaf_witness =
1377 updates_completion_response.inner.update_witnesses->at(current_witness_index++);
1378 response.inner.low_leaf_witness_data->push_back(low_leaf_witness);
1379
1380 // If this update has an insertion, append the real witness
1381 if (results->updates_to_perform.at(i).new_leaf.has_value()) {
1382 LeafUpdateWitnessData<LeafValueType> insertion_witness =
1383 updates_completion_response.inner.update_witnesses->at(current_witness_index++);
1384 response.inner.insertion_witness_data->push_back(insertion_witness);
1385 } else {
1386 // If it's an update, append an empty witness
1387 response.inner.insertion_witness_data->push_back(LeafUpdateWitnessData<LeafValueType>(
1388 IndexedLeafValueType::empty(), 0, std::vector<fr>(depth_)));
1389 }
1390 }
1391 }
1392 }
1393 // Trigger the client's provided callback
1394 completion(response);
1395 };
1396
1397 // This signals the completion of the insertion data generation
1398 // Here we'll perform all updates to the tree
1399 SequentialInsertionGenerationCallback insertion_generation_completed =
1400 [=, this](TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
1401 if (!insertion_response.success) {
1402 on_error(insertion_response.message);
1403 return;
1404 }
1405
1407 flat_updates->reserve(insertion_response.inner.updates_to_perform.size() * 2);
1408
1409 for (size_t i = 0; i < insertion_response.inner.updates_to_perform.size(); ++i) {
1410 InsertionUpdates& insertion_update = insertion_response.inner.updates_to_perform.at(i);
1411 flat_updates->push_back(insertion_update.low_leaf_update);
1412 if (insertion_update.new_leaf.has_value()) {
1413 results->appended_leaves++;
1414 IndexedLeafValueType new_leaf;
1415 index_t new_leaf_index = 0;
1416 std::tie(new_leaf, new_leaf_index) = insertion_update.new_leaf.value();
1417 flat_updates->push_back(LeafUpdate{
1418 .leaf_index = new_leaf_index,
1419 .updated_leaf = new_leaf,
1420 .original_leaf = IndexedLeafValueType::empty(),
1421 });
1422 }
1423 }
1424 // We won't use anymore updates_to_perform
1425 results->updates_to_perform = std::move(insertion_response.inner.updates_to_perform);
1426 assert(insertion_response.inner.updates_to_perform.size() == 0);
1427 if (capture_witness) {
1428 perform_updates(flat_updates->size(), flat_updates, final_completion);
1429 return;
1430 }
1431 perform_updates_without_witness(insertion_response.inner.highest_index, flat_updates, final_completion);
1432 };
1433
1434 // Enqueue the insertion data generation
1435 workers_->enqueue([=, this]() { generate_sequential_insertions(values, insertion_generation_completed); });
1436}
1437
1438template <typename Store, typename HashingPolicy>
1441{
1442 execute_and_report<SequentialInsertionGenerationResponse>(
1444 TreeMeta meta;
1445 ReadTransactionPtr tx = store_->create_read_transaction();
1446 store_->get_meta(meta);
1447
1448 RequestContext requestContext;
1449 requestContext.includeUncommitted = true;
1450 requestContext.root = store_->get_current_root(*tx, true);
1451 // Fetch the frontier (non empty nodes to the right) of the tree. This will ensure that perform_updates or
1452 // perform_updates_without_witness has all the cached nodes it needs to perform the insertions. See comment
1453 // above those functions.
1454 if (meta.size > 0) {
1455 find_leaf_hash(meta.size - 1, requestContext, *tx, true);
1456 }
1457
1458 index_t current_size = meta.size;
1459
1460 for (size_t i = 0; i < values.size(); ++i) {
1461 const LeafValueType& new_payload = values[i];
1462 // TODO(Alvaro) - Rethink this. I think it's fine for us to interpret empty values as a regular update
1463 // (it'd empty out the payload of the zero leaf)
1464 if (new_payload.is_empty()) {
1465 continue;
1466 }
1467 fr value = new_payload.get_key();
1468
1469 // This gives us the leaf that need updating
1470 index_t low_leaf_index = 0;
1471 bool is_already_present = false;
1472
1473 std::tie(is_already_present, low_leaf_index) =
1474 store_->find_low_value(new_payload.get_key(), requestContext, *tx);
1475
1476 // Try and retrieve the leaf pre-image from the cache first.
1477 // If unsuccessful, derive from the tree and hash based lookup
1478 std::optional<IndexedLeafValueType> optional_low_leaf =
1479 store_->get_cached_leaf_by_index(low_leaf_index);
1481
1482 if (optional_low_leaf.has_value()) {
1483 low_leaf = optional_low_leaf.value();
1484 } else {
1485 std::optional<fr> low_leaf_hash = find_leaf_hash(low_leaf_index, requestContext, *tx, true);
1486
1487 if (!low_leaf_hash.has_value()) {
1488 throw std::runtime_error(format("Unable to insert values into tree ",
1489 meta.name,
1490 ", failed to find low leaf at index ",
1491 low_leaf_index));
1492 }
1493
1494 std::optional<IndexedLeafValueType> low_leaf_option =
1495 store_->get_leaf_by_hash(low_leaf_hash.value(), *tx, true);
1496
1497 if (!low_leaf_option.has_value()) {
1498 throw std::runtime_error(format("Unable to insert values into tree ",
1499 meta.name,
1500 " failed to get leaf pre-image by hash for index ",
1501 low_leaf_index));
1502 }
1503 low_leaf = low_leaf_option.value();
1504 };
1505
1506 InsertionUpdates insertion_update = {
1508 LeafUpdate{
1509 .leaf_index = low_leaf_index,
1510 .updated_leaf = IndexedLeafValueType::empty(),
1511 .original_leaf = low_leaf,
1512 },
1513 .new_leaf = std::nullopt,
1514 };
1515
1516 if (!is_already_present) {
1517 // Update the current leaf to point it to the new leaf
1518 IndexedLeafValueType new_leaf =
1519 IndexedLeafValueType(new_payload, low_leaf.nextIndex, low_leaf.nextKey);
1520 index_t index_of_new_leaf = current_size;
1521 low_leaf.nextIndex = index_of_new_leaf;
1522 low_leaf.nextKey = value;
1523 current_size++;
1524 // Cache the new leaf
1525 store_->set_leaf_key_at_index(index_of_new_leaf, new_leaf);
1526 store_->put_cached_leaf_by_index(index_of_new_leaf, new_leaf);
1527 // Update cached low leaf
1528 store_->put_cached_leaf_by_index(low_leaf_index, low_leaf);
1529
1530 insertion_update.low_leaf_update.updated_leaf = low_leaf;
1531 insertion_update.new_leaf = std::pair(new_leaf, index_of_new_leaf);
1532 } else if (IndexedLeafValueType::is_updateable()) {
1533 // Update the current leaf's value, don't change it's link
1534 IndexedLeafValueType replacement_leaf =
1535 IndexedLeafValueType(new_payload, low_leaf.nextIndex, low_leaf.nextKey);
1536
1537 store_->put_cached_leaf_by_index(low_leaf_index, replacement_leaf);
1538 insertion_update.low_leaf_update.updated_leaf = replacement_leaf;
1539 } else {
1540 throw std::runtime_error(format("Unable to insert values into tree ",
1541 meta.name,
1542 " leaf type ",
1543 IndexedLeafValueType::name(),
1544 " is not updateable and ",
1545 new_payload.get_key(),
1546 " is already present"));
1547 }
1548
1549 response.inner.updates_to_perform.push_back(insertion_update);
1550 }
1551
1552 // Ensure that the tree is not going to be overfilled
1553 if (current_size > max_size_) {
1554 throw std::runtime_error(format("Unable to insert values into tree ",
1555 meta.name,
1556 " new size: ",
1557 current_size,
1558 " max size: ",
1559 max_size_));
1560 }
1561 // The highest index touched will be current_size - 1
1562 response.inner.highest_index = current_size - 1;
1563 },
1564 completion);
1565}
1566
1567} // namespace bb::crypto::merkle_tree
void enqueue(const std::function< void()> &task)
Implements a simple append-only merkle tree All methods are asynchronous unless specified as otherwis...
void get_sibling_path(const index_t &index, const HashPathCallback &on_completion, bool includeUncommitted) const
Returns the sibling path from the leaf at the given index to the root.
void add_values_internal(std::shared_ptr< std::vector< fr > > values, fr &new_root, index_t &new_size, bool update_index)
virtual void add_values(const std::vector< fr > &values, const AppendCompletionCallback &on_completion)
Adds the given set of values to the end of the tree.
std::function< void(TypedResponse< AddDataResponse > &)> AppendCompletionCallback
virtual void add_value(const fr &value, const AppendCompletionCallback &on_completion)
Adds a single value to the end of the tree.
std::optional< fr > find_leaf_hash(const index_t &leaf_index, const RequestContext &requestContext, ReadTransaction &tx, bool updateNodesByIndexCache=false) const
void get_subtree_sibling_path(uint32_t subtree_depth, const HashPathCallback &on_completion, bool includeUncommitted) const
Get the subtree sibling path object.
Serves as a key-value node store for merkle trees. Caches all changes in memory before persisting the...
Implements a parallelized batch insertion indexed tree Accepts template argument of the type of store...
ContentAddressedIndexedTree(ContentAddressedIndexedTree const &other)=delete
ContentAddressedIndexedTree(ContentAddressedIndexedTree &&other)=delete
std::function< void(TypedResponse< GetLowIndexedLeafResponse > &)> FindLowLeafCallback
void find_low_leaf(const fr &leaf_key, bool includeUncommitted, const FindLowLeafCallback &on_completion) const
Find the leaf with the value immediately lower then the value provided.
void add_or_update_value(const LeafValueType &value, const AddCompletionCallbackWithWitness &completion)
Adds or updates a single value in the tree.
std::pair< bool, fr > sparse_batch_update(const index_t &start_index, const index_t &num_leaves_to_be_inserted, const uint32_t &root_level, const std::vector< LeafUpdate > &updates)
std::function< void(TypedResponse< SequentialInsertionGenerationResponse > &)> SequentialInsertionGenerationCallback
std::function< void(const TypedResponse< InsertionGenerationResponse > &)> InsertionGenerationCallback
std::function< void(TypedResponse< AddIndexedDataSequentiallyResponse< LeafValueType > > &)> AddSequentiallyCompletionCallbackWithWitness
void add_or_update_values_sequentially(const std::vector< LeafValueType > &values, const AddSequentiallyCompletionCallbackWithWitness &completion)
Adds or updates the given set of values in the tree one by one, fetching witnesses at every step.
void add_or_update_values(const std::vector< LeafValueType > &values, const AddCompletionCallbackWithWitness &completion)
Adds or updates the given set of values in the tree using subtree insertion.
ContentAddressedIndexedTree(std::unique_ptr< Store > store, std::shared_ptr< ThreadPool > workers, const index_t &initial_size)
void perform_updates_without_witness(const index_t &highest_index, std::shared_ptr< std::vector< LeafUpdate > > updates, const UpdatesCompletionCallback &completion)
void generate_insertions(const std::shared_ptr< std::vector< std::pair< LeafValueType, index_t > > > &values_to_be_sorted, const InsertionGenerationCallback &completion)
std::function< void(const TypedResponse< UpdatesCompletionResponse > &)> UpdatesCompletionCallback
ContentAddressedIndexedTree & operator=(const ContentAddressedIndexedTree &other)=delete
ContentAddressedIndexedTree(std::unique_ptr< Store > store, std::shared_ptr< ThreadPool > workers, const index_t &initial_size, const std::vector< LeafValueType > &prefilled_values)
void generate_sequential_insertions(const std::vector< LeafValueType > &values, const SequentialInsertionGenerationCallback &completion)
std::function< void(TypedResponse< AddDataResponse > &)> AddCompletionCallback
std::function< void(TypedResponse< AddIndexedDataResponse< LeafValueType > > &)> AddCompletionCallbackWithWitness
std::function< void(TypedResponse< GetIndexedLeafResponse< LeafValueType > > &)> LeafCallback
std::function< void(const TypedResponse< HashGenerationResponse > &)> HashGenerationCallback
void add_or_update_values_internal(const std::vector< LeafValueType > &values, uint32_t subtree_depth, const AddCompletionCallbackWithWitness &completion, bool capture_witness)
Adds or updates the given set of values in the tree.
void update_leaf_and_hash_to_root(const index_t &index, const IndexedLeafValueType &leaf, Signal &leader, Signal &follower, fr_sibling_path &previous_sibling_path)
void get_leaf(const index_t &index, bool includeUncommitted, const LeafCallback &completion) const
void add_or_update_values_sequentially_internal(const std::vector< LeafValueType > &values, const AddSequentiallyCompletionCallbackWithWitness &completion, bool capture_witness)
Adds or updates the given set of values in the tree, capturing sequential insertion witnesses.
void perform_updates(size_t total_leaves, std::shared_ptr< std::vector< LeafUpdate > > updates, const UpdatesCompletionCallback &completion)
ContentAddressedIndexedTree & operator=(ContentAddressedIndexedTree &&other)=delete
void generate_hashes_for_appending(std::shared_ptr< std::vector< IndexedLeafValueType > > leaves_to_hash, const HashGenerationCallback &completion)
Used in parallel insertions in the the IndexedTree. Workers signal to other following workes as they ...
Definition signal.hpp:17
void signal_level(uint32_t level=0)
Signals that the given level has been passed.
Definition signal.hpp:54
void wait_for_level(uint32_t level=0)
Causes the thread to wait until the required level has been signalled.
Definition signal.hpp:40
std::string format(Args... args)
Definition log.hpp:20
FF a
FF b
NullifierTreeLeafPreimage low_leaf
ContentAddressedCachedTreeStore< bb::fr > Store
const auto init
Definition fr.bench.cpp:141
void hash(State &state) noexcept
uint32_t block_number_t
Definition types.hpp:19
std::vector< fr > fr_sibling_path
Definition hash_path.hpp:16
Key get_key(int64_t keyCount)
Definition fixtures.hpp:30
constexpr T get_msb(const T in)
Definition get_msb.hpp:47
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::optional< std::pair< IndexedLeafValueType, index_t > > new_leaf
std::shared_ptr< std::vector< LeafUpdateWitnessData< LeafValueType > > > update_witnesses
static PublicDataLeafValue padding(index_t i)
std::optional< index_t > maxIndex
Definition types.hpp:29
std::optional< block_number_t > blockNumber
Definition types.hpp:27
static constexpr field zero()