@@ -252,18 +252,20 @@ class ContentAddressedIndexedTree : public ContentAddressedAppendOnlyTree<Store,
252
252
const InsertionGenerationCallback& completion);
253
253
254
254
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.
255
257
LeafUpdate low_leaf_update;
256
- IndexedLeafValueType new_leaf;
257
- index_t new_leaf_index ;
258
+ // We don't create new leaves on update
259
+ std::optional<std::pair<IndexedLeafValueType, index_t >> new_leaf ;
258
260
};
259
261
260
262
struct SequentialInsertionGenerationResponse {
261
- std::shared_ptr<std:: vector<InsertionUpdates> > updates_to_perform;
263
+ std::vector<InsertionUpdates> updates_to_perform;
262
264
index_t highest_index;
263
265
};
264
266
265
267
using SequentialInsertionGenerationCallback =
266
- std::function<void (const TypedResponse<SequentialInsertionGenerationResponse>&)>;
268
+ std::function<void (TypedResponse<SequentialInsertionGenerationResponse>&)>;
267
269
void generate_sequential_insertions (const std::vector<LeafValueType>& values,
268
270
const SequentialInsertionGenerationCallback& completion);
269
271
@@ -1422,6 +1424,14 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1422
1424
const AddSequentiallyCompletionCallbackWithWitness& completion,
1423
1425
bool capture_witness)
1424
1426
{
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
+
1425
1435
auto on_error = [=](const std::string& message) {
1426
1436
try {
1427
1437
TypedResponse<AddIndexedDataSequentiallyResponse<LeafValueType>> response;
@@ -1443,7 +1453,7 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1443
1453
ReadTransactionPtr tx = store_->create_read_transaction ();
1444
1454
store_->get_meta (meta, *tx, true );
1445
1455
1446
- index_t new_total_size = values. size () + meta.size ;
1456
+ index_t new_total_size = results-> appended_leaves + meta.size ;
1447
1457
meta.size = new_total_size;
1448
1458
meta.root = store_->get_current_root (*tx, true );
1449
1459
@@ -1452,23 +1462,29 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1452
1462
1453
1463
if (capture_witness) {
1454
1464
// 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
1457
1465
response.inner .insertion_witness_data =
1458
1466
std::make_shared<std::vector<LeafUpdateWitnessData<LeafValueType>>>();
1459
- ;
1467
+ response.inner .insertion_witness_data ->reserve (results->updates_to_perform .size ());
1468
+
1460
1469
response.inner .low_leaf_witness_data =
1461
1470
std::make_shared<std::vector<LeafUpdateWitnessData<LeafValueType>>>();
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);
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);
1470
1484
} else {
1471
- response.inner .insertion_witness_data ->push_back (witness_data);
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_) });
1472
1488
}
1473
1489
}
1474
1490
}
@@ -1480,23 +1496,33 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1480
1496
// This signals the completion of the insertion data generation
1481
1497
// Here we'll perform all updates to the tree
1482
1498
SequentialInsertionGenerationCallback insertion_generation_completed =
1483
- [=, this ](const TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
1499
+ [=, this ](TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
1484
1500
if (!insertion_response.success ) {
1485
1501
on_error (insertion_response.message );
1486
1502
return ;
1487
1503
}
1488
1504
1489
1505
std::shared_ptr<std::vector<LeafUpdate>> flat_updates = std::make_shared<std::vector<LeafUpdate>>();
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);
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);
1492
1510
flat_updates->push_back (insertion_update.low_leaf_update );
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
- });
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
+ }
1498
1522
}
1499
-
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 );
1500
1526
if (capture_witness) {
1501
1527
perform_updates (flat_updates->size (), flat_updates, final_completion);
1502
1528
return ;
@@ -1514,27 +1540,12 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1514
1540
{
1515
1541
execute_and_report<SequentialInsertionGenerationResponse>(
1516
1542
[=, this ](TypedResponse<SequentialInsertionGenerationResponse>& response) {
1517
- response.inner .highest_index = 0 ;
1518
- response.inner .updates_to_perform = std::make_shared<std::vector<InsertionUpdates>>();
1519
-
1520
1543
TreeMeta meta;
1521
1544
ReadTransactionPtr tx = store_->create_read_transaction ();
1522
1545
store_->get_meta (meta, *tx, true );
1523
1546
1524
1547
RequestContext requestContext;
1525
1548
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 ;
1538
1549
requestContext.root = store_->get_current_root (*tx, true );
1539
1550
// Fetch the frontier (non empty nodes to the right) of the tree. This will ensure that perform_updates or
1540
1551
// perform_updates_without_witness has all the cached nodes it needs to perform the insertions. See comment
@@ -1543,12 +1554,15 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1543
1554
find_leaf_hash (meta.size - 1 , requestContext, *tx, true );
1544
1555
}
1545
1556
1557
+ index_t current_size = meta.size ;
1558
+
1546
1559
for (size_t i = 0 ; i < values.size (); ++i) {
1547
1560
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)
1548
1563
if (new_payload.is_empty ()) {
1549
1564
continue ;
1550
1565
}
1551
- index_t index_of_new_leaf = i + meta.size ;
1552
1566
fr value = new_payload.get_key ();
1553
1567
1554
1568
// This gives us the leaf that need updating
@@ -1595,25 +1609,25 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1595
1609
.updated_leaf = IndexedLeafValueType::empty (),
1596
1610
.original_leaf = low_leaf,
1597
1611
},
1598
- .new_leaf = IndexedLeafValueType::empty (),
1599
- .new_leaf_index = index_of_new_leaf,
1612
+ .new_leaf = std::nullopt,
1600
1613
};
1601
1614
1602
1615
if (!is_already_present) {
1603
1616
// Update the current leaf to point it to the new leaf
1604
1617
IndexedLeafValueType new_leaf =
1605
1618
IndexedLeafValueType (new_payload, low_leaf.nextIndex , low_leaf.nextValue );
1606
-
1619
+ index_t index_of_new_leaf = current_size;
1607
1620
low_leaf.nextIndex = index_of_new_leaf;
1608
1621
low_leaf.nextValue = value;
1622
+ current_size++;
1609
1623
// Cache the new leaf
1610
1624
store_->set_leaf_key_at_index (index_of_new_leaf, new_leaf);
1611
1625
store_->put_cached_leaf_by_index (index_of_new_leaf, new_leaf);
1612
1626
// Update cached low leaf
1613
1627
store_->put_cached_leaf_by_index (low_leaf_index, low_leaf);
1614
1628
1615
1629
insertion_update.low_leaf_update .updated_leaf = low_leaf;
1616
- insertion_update.new_leaf = new_leaf;
1630
+ insertion_update.new_leaf = std::pair ( new_leaf, index_of_new_leaf) ;
1617
1631
} else if (IndexedLeafValueType::is_updateable ()) {
1618
1632
// Update the current leaf's value, don't change it's link
1619
1633
IndexedLeafValueType replacement_leaf =
@@ -1631,8 +1645,20 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1631
1645
" is already present" ));
1632
1646
}
1633
1647
1634
- response.inner .updates_to_perform ->push_back (insertion_update);
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_));
1635
1659
}
1660
+ // The highest index touched will be current_size - 1
1661
+ response.inner .highest_index = current_size - 1 ;
1636
1662
},
1637
1663
completion);
1638
1664
}
0 commit comments