@@ -252,20 +252,18 @@ class ContentAddressedIndexedTree : public ContentAddressedAppendOnlyTree<Store,
252252 const InsertionGenerationCallback& completion);
253253
254254 struct InsertionUpdates {
255- // On insertion, we always update a low leaf. If it's creating a new leaf, we need to update the pointer to
256- // point to the new one, if it's an update to an existing leaf, we need to change its payload.
257255 LeafUpdate low_leaf_update;
258- // We don't create new leaves on update
259- std::optional<std::pair<IndexedLeafValueType, index_t >> new_leaf ;
256+ IndexedLeafValueType new_leaf;
257+ index_t new_leaf_index ;
260258 };
261259
262260 struct SequentialInsertionGenerationResponse {
263- std::vector<InsertionUpdates> updates_to_perform;
261+ std::shared_ptr<std:: vector<InsertionUpdates> > updates_to_perform;
264262 index_t highest_index;
265263 };
266264
267265 using SequentialInsertionGenerationCallback =
268- std::function<void (TypedResponse<SequentialInsertionGenerationResponse>&)>;
266+ std::function<void (const TypedResponse<SequentialInsertionGenerationResponse>&)>;
269267 void generate_sequential_insertions (const std::vector<LeafValueType>& values,
270268 const SequentialInsertionGenerationCallback& completion);
271269
@@ -1424,14 +1422,6 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
14241422 const AddSequentiallyCompletionCallbackWithWitness& completion,
14251423 bool capture_witness)
14261424{
1427-
1428- // This struct is used to collect some state from the asynchronous operations we are about to perform
1429- struct IntermediateResults {
1430- std::vector<InsertionUpdates> updates_to_perform;
1431- size_t appended_leaves = 0 ;
1432- };
1433- auto results = std::make_shared<IntermediateResults>();
1434-
14351425 auto on_error = [=](const std::string& message) {
14361426 try {
14371427 TypedResponse<AddIndexedDataSequentiallyResponse<LeafValueType>> response;
@@ -1453,7 +1443,7 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
14531443 ReadTransactionPtr tx = store_->create_read_transaction ();
14541444 store_->get_meta (meta, *tx, true );
14551445
1456- index_t new_total_size = results-> appended_leaves + meta.size ;
1446+ index_t new_total_size = values. size () + meta.size ;
14571447 meta.size = new_total_size;
14581448 meta.root = store_->get_current_root (*tx, true );
14591449
@@ -1462,29 +1452,23 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
14621452
14631453 if (capture_witness) {
14641454 // Split results->update_witnesses between low_leaf_witness_data and insertion_witness_data
1455+ // Currently we always insert an empty leaf, even if it's an update, so we can split based
1456+ // on the index of the witness data
14651457 response.inner .insertion_witness_data =
14661458 std::make_shared<std::vector<LeafUpdateWitnessData<LeafValueType>>>();
1467- response.inner .insertion_witness_data ->reserve (results->updates_to_perform .size ());
1468-
1459+ ;
14691460 response.inner .low_leaf_witness_data =
14701461 std::make_shared<std::vector<LeafUpdateWitnessData<LeafValueType>>>();
1471- response.inner .low_leaf_witness_data ->reserve (results->updates_to_perform .size ());
1472-
1473- size_t current_witness_index = 0 ;
1474- for (size_t i = 0 ; i < results->updates_to_perform .size (); ++i) {
1475- LeafUpdateWitnessData<LeafValueType> low_leaf_witness =
1476- updates_completion_response.inner .update_witnesses ->at (current_witness_index++);
1477- response.inner .low_leaf_witness_data ->push_back (low_leaf_witness);
1478-
1479- // If this update has an insertion, append the real witness
1480- if (results->updates_to_perform .at (i).new_leaf .has_value ()) {
1481- LeafUpdateWitnessData<LeafValueType> insertion_witness =
1482- updates_completion_response.inner .update_witnesses ->at (current_witness_index++);
1483- response.inner .insertion_witness_data ->push_back (insertion_witness);
1462+ ;
1463+
1464+ for (size_t i = 0 ; i < updates_completion_response.inner .update_witnesses ->size (); ++i) {
1465+ LeafUpdateWitnessData<LeafValueType>& witness_data =
1466+ updates_completion_response.inner .update_witnesses ->at (i);
1467+ // If even, it's a low leaf, if odd, it's an insertion witness
1468+ if (i % 2 == 0 ) {
1469+ response.inner .low_leaf_witness_data ->push_back (witness_data);
14841470 } else {
1485- // If it's an update, append an empty witness
1486- response.inner .insertion_witness_data ->push_back (LeafUpdateWitnessData<LeafValueType>{
1487- .leaf = IndexedLeafValueType::empty (), .index = 0 , .path = std::vector<fr>(depth_) });
1471+ response.inner .insertion_witness_data ->push_back (witness_data);
14881472 }
14891473 }
14901474 }
@@ -1496,33 +1480,23 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
14961480 // This signals the completion of the insertion data generation
14971481 // Here we'll perform all updates to the tree
14981482 SequentialInsertionGenerationCallback insertion_generation_completed =
1499- [=, this ](TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
1483+ [=, this ](const TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
15001484 if (!insertion_response.success ) {
15011485 on_error (insertion_response.message );
15021486 return ;
15031487 }
15041488
15051489 std::shared_ptr<std::vector<LeafUpdate>> flat_updates = std::make_shared<std::vector<LeafUpdate>>();
1506- flat_updates->reserve (insertion_response.inner .updates_to_perform .size () * 2 );
1507-
1508- for (size_t i = 0 ; i < insertion_response.inner .updates_to_perform .size (); ++i) {
1509- InsertionUpdates& insertion_update = insertion_response.inner .updates_to_perform .at (i);
1490+ for (size_t i = 0 ; i < insertion_response.inner .updates_to_perform ->size (); ++i) {
1491+ InsertionUpdates& insertion_update = insertion_response.inner .updates_to_perform ->at (i);
15101492 flat_updates->push_back (insertion_update.low_leaf_update );
1511- if (insertion_update.new_leaf .has_value ()) {
1512- results->appended_leaves ++;
1513- IndexedLeafValueType new_leaf;
1514- index_t new_leaf_index = 0 ;
1515- std::tie (new_leaf, new_leaf_index) = insertion_update.new_leaf .value ();
1516- flat_updates->push_back (LeafUpdate{
1517- .leaf_index = new_leaf_index,
1518- .updated_leaf = new_leaf,
1519- .original_leaf = IndexedLeafValueType::empty (),
1520- });
1521- }
1493+ flat_updates->push_back (LeafUpdate{
1494+ .leaf_index = insertion_update.new_leaf_index ,
1495+ .updated_leaf = insertion_update.new_leaf ,
1496+ .original_leaf = IndexedLeafValueType::empty (),
1497+ });
15221498 }
1523- // We won't use anymore updates_to_perform
1524- results->updates_to_perform = std::move (insertion_response.inner .updates_to_perform );
1525- assert (insertion_response.inner .updates_to_perform .size () == 0 );
1499+
15261500 if (capture_witness) {
15271501 perform_updates (flat_updates->size (), flat_updates, final_completion);
15281502 return ;
@@ -1540,12 +1514,27 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
15401514{
15411515 execute_and_report<SequentialInsertionGenerationResponse>(
15421516 [=, this ](TypedResponse<SequentialInsertionGenerationResponse>& response) {
1517+ response.inner .highest_index = 0 ;
1518+ response.inner .updates_to_perform = std::make_shared<std::vector<InsertionUpdates>>();
1519+
15431520 TreeMeta meta;
15441521 ReadTransactionPtr tx = store_->create_read_transaction ();
15451522 store_->get_meta (meta, *tx, true );
15461523
15471524 RequestContext requestContext;
15481525 requestContext.includeUncommitted = true ;
1526+ // Ensure that the tree is not going to be overfilled
1527+ index_t new_total_size = values.size () + meta.size ;
1528+ if (new_total_size > max_size_) {
1529+ throw std::runtime_error (format (" Unable to insert values into tree " ,
1530+ meta.name ,
1531+ " new size: " ,
1532+ new_total_size,
1533+ " max size: " ,
1534+ max_size_));
1535+ }
1536+ // The highest index touched will be the last leaf index, since we append a zero for updates
1537+ response.inner .highest_index = new_total_size - 1 ;
15491538 requestContext.root = store_->get_current_root (*tx, true );
15501539 // Fetch the frontier (non empty nodes to the right) of the tree. This will ensure that perform_updates or
15511540 // perform_updates_without_witness has all the cached nodes it needs to perform the insertions. See comment
@@ -1554,15 +1543,12 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
15541543 find_leaf_hash (meta.size - 1 , requestContext, *tx, true );
15551544 }
15561545
1557- index_t current_size = meta.size ;
1558-
15591546 for (size_t i = 0 ; i < values.size (); ++i) {
15601547 const LeafValueType& new_payload = values[i];
1561- // TODO(Alvaro) - Rethink this. I think it's fine for us to interpret empty values as a regular update
1562- // (it'd empty out the payload of the zero leaf)
15631548 if (new_payload.is_empty ()) {
15641549 continue ;
15651550 }
1551+ index_t index_of_new_leaf = i + meta.size ;
15661552 fr value = new_payload.get_key ();
15671553
15681554 // This gives us the leaf that need updating
@@ -1609,25 +1595,25 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
16091595 .updated_leaf = IndexedLeafValueType::empty (),
16101596 .original_leaf = low_leaf,
16111597 },
1612- .new_leaf = std::nullopt ,
1598+ .new_leaf = IndexedLeafValueType::empty (),
1599+ .new_leaf_index = index_of_new_leaf,
16131600 };
16141601
16151602 if (!is_already_present) {
16161603 // Update the current leaf to point it to the new leaf
16171604 IndexedLeafValueType new_leaf =
16181605 IndexedLeafValueType (new_payload, low_leaf.nextIndex , low_leaf.nextValue );
1619- index_t index_of_new_leaf = current_size;
1606+
16201607 low_leaf.nextIndex = index_of_new_leaf;
16211608 low_leaf.nextValue = value;
1622- current_size++;
16231609 // Cache the new leaf
16241610 store_->set_leaf_key_at_index (index_of_new_leaf, new_leaf);
16251611 store_->put_cached_leaf_by_index (index_of_new_leaf, new_leaf);
16261612 // Update cached low leaf
16271613 store_->put_cached_leaf_by_index (low_leaf_index, low_leaf);
16281614
16291615 insertion_update.low_leaf_update .updated_leaf = low_leaf;
1630- insertion_update.new_leaf = std::pair ( new_leaf, index_of_new_leaf) ;
1616+ insertion_update.new_leaf = new_leaf;
16311617 } else if (IndexedLeafValueType::is_updateable ()) {
16321618 // Update the current leaf's value, don't change it's link
16331619 IndexedLeafValueType replacement_leaf =
@@ -1645,20 +1631,8 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
16451631 " is already present" ));
16461632 }
16471633
1648- response.inner .updates_to_perform .push_back (insertion_update);
1649- }
1650-
1651- // Ensure that the tree is not going to be overfilled
1652- if (current_size > max_size_) {
1653- throw std::runtime_error (format (" Unable to insert values into tree " ,
1654- meta.name ,
1655- " new size: " ,
1656- current_size,
1657- " max size: " ,
1658- max_size_));
1634+ response.inner .updates_to_perform ->push_back (insertion_update);
16591635 }
1660- // The highest index touched will be current_size - 1
1661- response.inner .highest_index = current_size - 1 ;
16621636 },
16631637 completion);
16641638}
0 commit comments