Skip to content

Commit 0f68339

Browse files
authored
Revert "feat: Avoid inserting an empty leaf in indexed trees on update (#10281)"
This reverts commit 5a04ca8.
1 parent 90d4385 commit 0f68339

File tree

13 files changed

+191
-267
lines changed

13 files changed

+191
-267
lines changed

barretenberg/cpp/src/barretenberg/crypto/merkle_tree/indexed_tree/content_addressed_indexed_tree.hpp

Lines changed: 47 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

barretenberg/cpp/src/barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ struct PublicDataLeafValue {
107107

108108
fr get_key() const { return slot; }
109109

110-
bool is_empty() const { return slot == fr::zero() && value == fr::zero(); }
110+
bool is_empty() const { return slot == fr::zero(); }
111111

112112
std::vector<fr> get_hash_inputs(fr nextValue, fr nextIndex) const
113113
{

barretenberg/cpp/src/barretenberg/vm/avm/trace/gadgets/merkle_tree.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ FF AvmMerkleTreeTraceBuilder::perform_storage_write([[maybe_unused]] uint32_t cl
102102
// We update the low value
103103
low_preimage.value = value;
104104
FF low_preimage_hash = unconstrained_hash_public_data_preimage(low_preimage);
105-
// Update the low leaf
106-
return unconstrained_update_leaf_index(low_preimage_hash, static_cast<uint64_t>(low_index), low_path);
105+
// Update the low leaf - this will be returned in future
106+
[[maybe_unused]] FF root =
107+
unconstrained_update_leaf_index(low_preimage_hash, static_cast<uint64_t>(low_index), low_path);
108+
// TEMPORARY UNTIL WE CHANGE HOW UPDATES WORK
109+
// Insert a zero leaf at the insertion index
110+
return unconstrained_update_leaf_index(FF::zero(), static_cast<uint64_t>(insertion_index), insertion_path);
107111
}
108112
// The new leaf for an insertion is
109113
PublicDataTreeLeafPreimage new_preimage{

noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_data_tree.nr

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,16 @@ pub(crate) fn public_data_tree_insert(
5757
},
5858
|write: PublicDataTreeLeaf, low_preimage: PublicDataTreeLeafPreimage| {
5959
// Build insertion leaf
60-
PublicDataTreeLeafPreimage {
61-
slot: write.slot,
62-
value: write.value,
63-
next_slot: low_preimage.next_slot,
64-
next_index: low_preimage.next_index,
60+
let is_update = low_preimage.slot == write.slot;
61+
if is_update {
62+
PublicDataTreeLeafPreimage::empty()
63+
} else {
64+
PublicDataTreeLeafPreimage {
65+
slot: write.slot,
66+
value: write.value,
67+
next_slot: low_preimage.next_slot,
68+
next_index: low_preimage.next_index,
69+
}
6570
}
6671
},
6772
)

noir-projects/noir-protocol-circuits/crates/types/src/data/public_data_tree_leaf.nr

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{merkle_tree::leaf_preimage::IndexedTreeLeafValue, traits::Empty};
1+
use crate::traits::Empty;
22

33
pub struct PublicDataTreeLeaf {
44
pub slot: Field,
@@ -17,12 +17,6 @@ impl Empty for PublicDataTreeLeaf {
1717
}
1818
}
1919

20-
impl IndexedTreeLeafValue for PublicDataTreeLeaf {
21-
fn get_key(self) -> Field {
22-
self.slot
23-
}
24-
}
25-
2620
impl PublicDataTreeLeaf {
2721
pub fn is_empty(self) -> bool {
2822
(self.slot == 0) & (self.value == 0)

noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pub mod check_valid_low_leaf;
33
use crate::{
44
abis::append_only_tree_snapshot::AppendOnlyTreeSnapshot,
55
merkle_tree::{
6-
leaf_preimage::{IndexedTreeLeafPreimage, IndexedTreeLeafValue},
76
membership::{assert_check_membership, MembershipWitness},
87
root::{calculate_empty_tree_root, calculate_subtree_root, root_from_sibling_path},
98
},
@@ -113,14 +112,14 @@ pub fn insert<Value, Leaf, let TreeHeight: u32>(
113112
build_insertion_leaf: fn(Value, Leaf) -> Leaf,
114113
) -> AppendOnlyTreeSnapshot
115114
where
116-
Value: IndexedTreeLeafValue,
117-
Leaf: IndexedTreeLeafPreimage,
115+
Value: Eq + Empty,
116+
Leaf: Hash + Empty,
118117
{
119118
assert(is_valid_low_leaf(low_leaf_preimage, value), "Invalid low leaf");
120119

121120
// perform membership check for the low leaf against the original root
122121
assert_check_membership(
123-
low_leaf_preimage.as_leaf(),
122+
low_leaf_preimage.hash(),
124123
low_leaf_membership_witness.leaf_index,
125124
low_leaf_membership_witness.sibling_path,
126125
snapshot.root,
@@ -130,34 +129,29 @@ where
130129
let updated_low_leaf =
131130
update_low_leaf(low_leaf_preimage, value, snapshot.next_available_leaf_index);
132131

133-
// Update low leaf
134132
snapshot.root = root_from_sibling_path(
135-
updated_low_leaf.as_leaf(),
133+
updated_low_leaf.hash(),
136134
low_leaf_membership_witness.leaf_index,
137135
low_leaf_membership_witness.sibling_path,
138136
);
139137

140-
if low_leaf_preimage.get_key() == value.get_key() {
141-
// If it's an update, we don't need to insert the new leaf and advance the tree
142-
snapshot
143-
} else {
144-
let insertion_leaf = build_insertion_leaf(value, low_leaf_preimage);
145-
assert_check_membership(
146-
0,
147-
snapshot.next_available_leaf_index as Field,
148-
insertion_sibling_path,
149-
snapshot.root,
150-
);
151-
152-
// Calculate the new root
153-
snapshot.root = root_from_sibling_path(
154-
insertion_leaf.as_leaf(),
155-
snapshot.next_available_leaf_index as Field,
156-
insertion_sibling_path,
157-
);
158-
159-
snapshot.next_available_leaf_index += 1;
160-
161-
snapshot
162-
}
138+
let insertion_leaf = build_insertion_leaf(value, low_leaf_preimage);
139+
140+
assert_check_membership(
141+
0,
142+
snapshot.next_available_leaf_index as Field,
143+
insertion_sibling_path,
144+
snapshot.root,
145+
);
146+
147+
// Calculate the new root
148+
snapshot.root = root_from_sibling_path(
149+
insertion_leaf.hash(),
150+
snapshot.next_available_leaf_index as Field,
151+
insertion_sibling_path,
152+
);
153+
154+
snapshot.next_available_leaf_index += 1;
155+
156+
snapshot
163157
}

noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree/check_valid_low_leaf.nr

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,12 @@ mod tests {
1616
indexed_tree::check_valid_low_leaf::assert_check_valid_low_leaf,
1717
leaf_preimage::IndexedTreeLeafPreimage,
1818
};
19-
use crate::traits::Empty;
2019

2120
struct TestLeafPreimage {
2221
value: Field,
2322
next_value: Field,
2423
}
2524

26-
impl Empty for TestLeafPreimage {
27-
fn empty() -> Self {
28-
Self { value: 0, next_value: 0 }
29-
}
30-
}
31-
3225
impl IndexedTreeLeafPreimage for TestLeafPreimage {
3326
fn get_key(self) -> Field {
3427
self.value

0 commit comments

Comments
 (0)