@@ -251,18 +251,20 @@ class ContentAddressedIndexedTree : public ContentAddressedAppendOnlyTree<Store,
251
251
const InsertionGenerationCallback& completion);
252
252
253
253
struct InsertionUpdates {
254
+ // On insertion, we always update a low leaf. If it's creating a new leaf, we need to update the pointer to
255
+ // point to the new one, if it's an update to an existing leaf, we need to change its payload.
254
256
LeafUpdate low_leaf_update;
255
- IndexedLeafValueType new_leaf;
256
- index_t new_leaf_index ;
257
+ // We don't create new leaves on update
258
+ std::optional<std::pair<IndexedLeafValueType, index_t >> new_leaf ;
257
259
};
258
260
259
261
struct SequentialInsertionGenerationResponse {
260
- std::shared_ptr<std:: vector<InsertionUpdates> > updates_to_perform;
262
+ std::vector<InsertionUpdates> updates_to_perform;
261
263
index_t highest_index;
262
264
};
263
265
264
266
using SequentialInsertionGenerationCallback =
265
- std::function<void (const TypedResponse<SequentialInsertionGenerationResponse>&)>;
267
+ std::function<void (TypedResponse<SequentialInsertionGenerationResponse>&)>;
266
268
void generate_sequential_insertions (const std::vector<LeafValueType>& values,
267
269
const SequentialInsertionGenerationCallback& completion);
268
270
@@ -1421,6 +1423,14 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1421
1423
const AddSequentiallyCompletionCallbackWithWitness& completion,
1422
1424
bool capture_witness)
1423
1425
{
1426
+
1427
+ // This struct is used to collect some state from the asynchronous operations we are about to perform
1428
+ struct IntermediateResults {
1429
+ std::vector<InsertionUpdates> updates_to_perform;
1430
+ size_t appended_leaves = 0 ;
1431
+ };
1432
+ auto results = std::make_shared<IntermediateResults>();
1433
+
1424
1434
auto on_error = [=](const std::string& message) {
1425
1435
try {
1426
1436
TypedResponse<AddIndexedDataSequentiallyResponse<LeafValueType>> response;
@@ -1442,7 +1452,7 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1442
1452
ReadTransactionPtr tx = store_->create_read_transaction ();
1443
1453
store_->get_meta (meta, *tx, true );
1444
1454
1445
- index_t new_total_size = values. size () + meta.size ;
1455
+ index_t new_total_size = results-> appended_leaves + meta.size ;
1446
1456
meta.size = new_total_size;
1447
1457
meta.root = store_->get_current_root (*tx, true );
1448
1458
@@ -1451,23 +1461,29 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1451
1461
1452
1462
if (capture_witness) {
1453
1463
// Split results->update_witnesses between low_leaf_witness_data and insertion_witness_data
1454
- // Currently we always insert an empty leaf, even if it's an update, so we can split based
1455
- // on the index of the witness data
1456
1464
response.inner .insertion_witness_data =
1457
1465
std::make_shared<std::vector<LeafUpdateWitnessData<LeafValueType>>>();
1458
- ;
1466
+ response.inner .insertion_witness_data ->reserve (results->updates_to_perform .size ());
1467
+
1459
1468
response.inner .low_leaf_witness_data =
1460
1469
std::make_shared<std::vector<LeafUpdateWitnessData<LeafValueType>>>();
1461
- ;
1462
-
1463
- for (size_t i = 0 ; i < updates_completion_response.inner .update_witnesses ->size (); ++i) {
1464
- LeafUpdateWitnessData<LeafValueType>& witness_data =
1465
- updates_completion_response.inner .update_witnesses ->at (i);
1466
- // If even, it's a low leaf, if odd, it's an insertion witness
1467
- if (i % 2 == 0 ) {
1468
- response.inner .low_leaf_witness_data ->push_back (witness_data);
1470
+ response.inner .low_leaf_witness_data ->reserve (results->updates_to_perform .size ());
1471
+
1472
+ size_t current_witness_index = 0 ;
1473
+ for (size_t i = 0 ; i < results->updates_to_perform .size (); ++i) {
1474
+ LeafUpdateWitnessData<LeafValueType> low_leaf_witness =
1475
+ updates_completion_response.inner .update_witnesses ->at (current_witness_index++);
1476
+ response.inner .low_leaf_witness_data ->push_back (low_leaf_witness);
1477
+
1478
+ // If this update has an insertion, append the real witness
1479
+ if (results->updates_to_perform .at (i).new_leaf .has_value ()) {
1480
+ LeafUpdateWitnessData<LeafValueType> insertion_witness =
1481
+ updates_completion_response.inner .update_witnesses ->at (current_witness_index++);
1482
+ response.inner .insertion_witness_data ->push_back (insertion_witness);
1469
1483
} else {
1470
- response.inner .insertion_witness_data ->push_back (witness_data);
1484
+ // If it's an update, append an empty witness
1485
+ response.inner .insertion_witness_data ->push_back (LeafUpdateWitnessData<LeafValueType>(
1486
+ IndexedLeafValueType::empty (), 0 , std::vector<fr>(depth_)));
1471
1487
}
1472
1488
}
1473
1489
}
@@ -1479,23 +1495,33 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::add_or_update_values_seq
1479
1495
// This signals the completion of the insertion data generation
1480
1496
// Here we'll perform all updates to the tree
1481
1497
SequentialInsertionGenerationCallback insertion_generation_completed =
1482
- [=, this ](const TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
1498
+ [=, this ](TypedResponse<SequentialInsertionGenerationResponse>& insertion_response) {
1483
1499
if (!insertion_response.success ) {
1484
1500
on_error (insertion_response.message );
1485
1501
return ;
1486
1502
}
1487
1503
1488
1504
std::shared_ptr<std::vector<LeafUpdate>> flat_updates = std::make_shared<std::vector<LeafUpdate>>();
1489
- for (size_t i = 0 ; i < insertion_response.inner .updates_to_perform ->size (); ++i) {
1490
- InsertionUpdates& insertion_update = insertion_response.inner .updates_to_perform ->at (i);
1505
+ flat_updates->reserve (insertion_response.inner .updates_to_perform .size () * 2 );
1506
+
1507
+ for (size_t i = 0 ; i < insertion_response.inner .updates_to_perform .size (); ++i) {
1508
+ InsertionUpdates& insertion_update = insertion_response.inner .updates_to_perform .at (i);
1491
1509
flat_updates->push_back (insertion_update.low_leaf_update );
1492
- flat_updates->push_back (LeafUpdate{
1493
- .leaf_index = insertion_update.new_leaf_index ,
1494
- .updated_leaf = insertion_update.new_leaf ,
1495
- .original_leaf = IndexedLeafValueType::empty (),
1496
- });
1510
+ if (insertion_update.new_leaf .has_value ()) {
1511
+ results->appended_leaves ++;
1512
+ IndexedLeafValueType new_leaf;
1513
+ index_t new_leaf_index = 0 ;
1514
+ std::tie (new_leaf, new_leaf_index) = insertion_update.new_leaf .value ();
1515
+ flat_updates->push_back (LeafUpdate{
1516
+ .leaf_index = new_leaf_index,
1517
+ .updated_leaf = new_leaf,
1518
+ .original_leaf = IndexedLeafValueType::empty (),
1519
+ });
1520
+ }
1497
1521
}
1498
-
1522
+ // We won't use anymore updates_to_perform
1523
+ results->updates_to_perform = std::move (insertion_response.inner .updates_to_perform );
1524
+ assert (insertion_response.inner .updates_to_perform .size () == 0 );
1499
1525
if (capture_witness) {
1500
1526
perform_updates (flat_updates->size (), flat_updates, final_completion);
1501
1527
return ;
@@ -1513,27 +1539,12 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1513
1539
{
1514
1540
execute_and_report<SequentialInsertionGenerationResponse>(
1515
1541
[=, this ](TypedResponse<SequentialInsertionGenerationResponse>& response) {
1516
- response.inner .highest_index = 0 ;
1517
- response.inner .updates_to_perform = std::make_shared<std::vector<InsertionUpdates>>();
1518
-
1519
1542
TreeMeta meta;
1520
1543
ReadTransactionPtr tx = store_->create_read_transaction ();
1521
1544
store_->get_meta (meta, *tx, true );
1522
1545
1523
1546
RequestContext requestContext;
1524
1547
requestContext.includeUncommitted = true ;
1525
- // Ensure that the tree is not going to be overfilled
1526
- index_t new_total_size = values.size () + meta.size ;
1527
- if (new_total_size > max_size_) {
1528
- throw std::runtime_error (format (" Unable to insert values into tree " ,
1529
- meta.name ,
1530
- " new size: " ,
1531
- new_total_size,
1532
- " max size: " ,
1533
- max_size_));
1534
- }
1535
- // The highest index touched will be the last leaf index, since we append a zero for updates
1536
- response.inner .highest_index = new_total_size - 1 ;
1537
1548
requestContext.root = store_->get_current_root (*tx, true );
1538
1549
// Fetch the frontier (non empty nodes to the right) of the tree. This will ensure that perform_updates or
1539
1550
// perform_updates_without_witness has all the cached nodes it needs to perform the insertions. See comment
@@ -1542,12 +1553,15 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1542
1553
find_leaf_hash (meta.size - 1 , requestContext, *tx, true );
1543
1554
}
1544
1555
1556
+ index_t current_size = meta.size ;
1557
+
1545
1558
for (size_t i = 0 ; i < values.size (); ++i) {
1546
1559
const LeafValueType& new_payload = values[i];
1560
+ // TODO(Alvaro) - Rethink this. I think it's fine for us to interpret empty values as a regular update
1561
+ // (it'd empty out the payload of the zero leaf)
1547
1562
if (new_payload.is_empty ()) {
1548
1563
continue ;
1549
1564
}
1550
- index_t index_of_new_leaf = i + meta.size ;
1551
1565
fr value = new_payload.get_key ();
1552
1566
1553
1567
// This gives us the leaf that need updating
@@ -1594,25 +1608,25 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1594
1608
.updated_leaf = IndexedLeafValueType::empty (),
1595
1609
.original_leaf = low_leaf,
1596
1610
},
1597
- .new_leaf = IndexedLeafValueType::empty (),
1598
- .new_leaf_index = index_of_new_leaf,
1611
+ .new_leaf = std::nullopt,
1599
1612
};
1600
1613
1601
1614
if (!is_already_present) {
1602
1615
// Update the current leaf to point it to the new leaf
1603
1616
IndexedLeafValueType new_leaf =
1604
1617
IndexedLeafValueType (new_payload, low_leaf.nextIndex , low_leaf.nextValue );
1605
-
1618
+ index_t index_of_new_leaf = current_size;
1606
1619
low_leaf.nextIndex = index_of_new_leaf;
1607
1620
low_leaf.nextValue = value;
1621
+ current_size++;
1608
1622
// Cache the new leaf
1609
1623
store_->set_leaf_key_at_index (index_of_new_leaf, new_leaf);
1610
1624
store_->put_cached_leaf_by_index (index_of_new_leaf, new_leaf);
1611
1625
// Update cached low leaf
1612
1626
store_->put_cached_leaf_by_index (low_leaf_index, low_leaf);
1613
1627
1614
1628
insertion_update.low_leaf_update .updated_leaf = low_leaf;
1615
- insertion_update.new_leaf = new_leaf;
1629
+ insertion_update.new_leaf = std::pair ( new_leaf, index_of_new_leaf) ;
1616
1630
} else if (IndexedLeafValueType::is_updateable ()) {
1617
1631
// Update the current leaf's value, don't change it's link
1618
1632
IndexedLeafValueType replacement_leaf =
@@ -1630,8 +1644,20 @@ void ContentAddressedIndexedTree<Store, HashingPolicy>::generate_sequential_inse
1630
1644
" is already present" ));
1631
1645
}
1632
1646
1633
- response.inner .updates_to_perform ->push_back (insertion_update);
1647
+ response.inner .updates_to_perform .push_back (insertion_update);
1648
+ }
1649
+
1650
+ // Ensure that the tree is not going to be overfilled
1651
+ if (current_size > max_size_) {
1652
+ throw std::runtime_error (format (" Unable to insert values into tree " ,
1653
+ meta.name ,
1654
+ " new size: " ,
1655
+ current_size,
1656
+ " max size: " ,
1657
+ max_size_));
1634
1658
}
1659
+ // The highest index touched will be current_size - 1
1660
+ response.inner .highest_index = current_size - 1 ;
1635
1661
},
1636
1662
completion);
1637
1663
}
0 commit comments