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