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