Skip to content

Commit 1c1145c

Browse files
committed
starknet_committer: add index layout nodes
1 parent f0ae1a9 commit 1c1145c

File tree

6 files changed

+403
-85
lines changed

6 files changed

+403
-85
lines changed

crates/starknet_committer/src/db/index_db/leaves_test.rs

Lines changed: 0 additions & 81 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pub mod leaves;
22
#[cfg(test)]
3-
pub mod leaves_test;
3+
pub mod serde_tests;
44
pub mod types;
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
use ethnum::U256;
2+
use rstest::rstest;
3+
use starknet_api::core::{ClassHash, Nonce};
4+
use starknet_api::hash::HashOutput;
5+
use starknet_patricia::patricia_merkle_tree::filled_tree::node::FilledNode;
6+
use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{
7+
BinaryData,
8+
EdgeData,
9+
EdgePath,
10+
EdgePathLength,
11+
NodeData,
12+
PathToBottom,
13+
};
14+
use starknet_patricia::patricia_merkle_tree::node_data::leaf::Leaf;
15+
use starknet_patricia::patricia_merkle_tree::updated_skeleton_tree::hash_function::TreeHashFunction;
16+
use starknet_patricia_storage::db_object::{DBObject, EmptyDeserializationContext};
17+
use starknet_patricia_storage::storage_trait::DbValue;
18+
use starknet_types_core::felt::Felt;
19+
20+
use crate::block_committer::input::StarknetStorageValue;
21+
use crate::db::index_db::leaves::{
22+
IndexLayoutCompiledClassHash,
23+
IndexLayoutContractState,
24+
IndexLayoutStarknetStorageValue,
25+
};
26+
use crate::db::index_db::types::{IndexFilledNode, IndexNodeContext};
27+
use crate::hash_function::hash::TreeHashFunctionImpl;
28+
use crate::patricia_merkle_tree::leaf::leaf_impl::ContractState;
29+
use crate::patricia_merkle_tree::types::CompiledClassHash;
30+
31+
/// Converts leaf node data from NodeData<L, HashOutput> to NodeData<L, ()>.
32+
///
33+
/// Useful for using the same data for computing the hash and creating an index layout leaf
34+
/// instance.
35+
fn index_leaf_data_from_hash_data<L: Leaf>(data: NodeData<L, HashOutput>) -> NodeData<L, ()> {
36+
match data {
37+
NodeData::Binary(_) | NodeData::Edge(_) => {
38+
unreachable!("this helper is intended for leaf-only test data")
39+
}
40+
NodeData::Leaf(leaf) => NodeData::Leaf(leaf),
41+
}
42+
}
43+
44+
fn contract_state_leaf() -> IndexFilledNode<IndexLayoutContractState> {
45+
let data = NodeData::Leaf(IndexLayoutContractState(ContractState {
46+
class_hash: ClassHash(Felt::from(1)),
47+
storage_root_hash: HashOutput(Felt::from(2)),
48+
nonce: Nonce(Felt::from(3)),
49+
}));
50+
let hash = TreeHashFunctionImpl::compute_node_hash(&data);
51+
IndexFilledNode(FilledNode { hash, data: index_leaf_data_from_hash_data(data) })
52+
}
53+
54+
fn compiled_class_hash_leaf() -> IndexFilledNode<IndexLayoutCompiledClassHash> {
55+
let data = NodeData::Leaf(IndexLayoutCompiledClassHash(CompiledClassHash(Felt::from(1))));
56+
let hash = TreeHashFunctionImpl::compute_node_hash(&data);
57+
IndexFilledNode(FilledNode { hash, data: index_leaf_data_from_hash_data(data) })
58+
}
59+
60+
fn starknet_storage_value_leaf() -> IndexFilledNode<IndexLayoutStarknetStorageValue> {
61+
let data = NodeData::Leaf(IndexLayoutStarknetStorageValue(StarknetStorageValue(Felt::from(1))));
62+
let hash = TreeHashFunctionImpl::compute_node_hash(&data);
63+
IndexFilledNode(FilledNode { hash, data: index_leaf_data_from_hash_data(data) })
64+
}
65+
66+
fn starknet_storage_value_leaf_96_bits() -> IndexLayoutStarknetStorageValue {
67+
// 2^96 (12 bytes, under the 27 nibbles threshold)
68+
IndexLayoutStarknetStorageValue(StarknetStorageValue(Felt::from(1_u128 << 95)))
69+
}
70+
71+
fn starknet_storage_value_leaf_136_bits() -> IndexLayoutStarknetStorageValue {
72+
// 2^136 (reaching the 34 nibbles / 17 bytes serialization threshold)
73+
IndexLayoutStarknetStorageValue(StarknetStorageValue(Felt::from_bytes_be(&[
74+
0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8,
75+
128_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8,
76+
0_u8, 0_u8,
77+
])))
78+
}
79+
80+
fn binary_node() -> IndexFilledNode<IndexLayoutContractState> {
81+
IndexFilledNode(FilledNode {
82+
hash: HashOutput(Felt::from(1)),
83+
data: NodeData::Binary(BinaryData { left_data: (), right_data: () }),
84+
})
85+
}
86+
87+
fn edge_node_short_path_len_3() -> IndexFilledNode<IndexLayoutContractState> {
88+
IndexFilledNode(FilledNode {
89+
hash: HashOutput(Felt::from(1)),
90+
data: NodeData::Edge(EdgeData {
91+
bottom_data: (),
92+
// 110, right, right, left
93+
path_to_bottom: PathToBottom::new(
94+
EdgePath(U256::from(6_u128)),
95+
EdgePathLength::new(3).unwrap(),
96+
)
97+
.unwrap(),
98+
}),
99+
})
100+
}
101+
102+
fn edge_node_short_path_len_10() -> IndexFilledNode<IndexLayoutContractState> {
103+
IndexFilledNode(FilledNode {
104+
hash: HashOutput(Felt::from(1)),
105+
data: NodeData::Edge(EdgeData {
106+
bottom_data: (),
107+
// 0...0 seven times followed by 110
108+
path_to_bottom: PathToBottom::new(
109+
EdgePath(U256::from(6_u128)),
110+
EdgePathLength::new(10).unwrap(),
111+
)
112+
.unwrap(),
113+
}),
114+
})
115+
}
116+
117+
fn edge_node_path_divisible_by_8() -> IndexFilledNode<IndexLayoutContractState> {
118+
IndexFilledNode(FilledNode {
119+
hash: HashOutput(Felt::from(1)),
120+
data: NodeData::Edge(EdgeData {
121+
bottom_data: (),
122+
path_to_bottom: PathToBottom::new(
123+
// 1...1 24 times
124+
EdgePath(U256::from((1_u128 << 24) - 1)),
125+
EdgePathLength::new(24).unwrap(),
126+
)
127+
.unwrap(),
128+
}),
129+
})
130+
}
131+
132+
fn edge_node_path_not_divisible_by_8() -> IndexFilledNode<IndexLayoutContractState> {
133+
IndexFilledNode(FilledNode {
134+
hash: HashOutput(Felt::from(1)),
135+
data: NodeData::Edge(EdgeData {
136+
bottom_data: (),
137+
// 000 followed by 1...1 19 times
138+
path_to_bottom: PathToBottom::new(
139+
EdgePath(U256::from((1_u128 << 19) - 1)),
140+
EdgePathLength::new(22).unwrap(),
141+
)
142+
.unwrap(),
143+
}),
144+
})
145+
}
146+
147+
fn edge_node_long_zero_path() -> IndexFilledNode<IndexLayoutContractState> {
148+
IndexFilledNode(FilledNode {
149+
hash: HashOutput(Felt::from(1)),
150+
data: NodeData::Edge(EdgeData {
151+
bottom_data: (),
152+
// 0...0 path of length 250
153+
path_to_bottom: PathToBottom::new(
154+
EdgePath(U256::from_words(0_u128, 0_u128)),
155+
EdgePathLength::new(250).unwrap(),
156+
)
157+
.unwrap(),
158+
}),
159+
})
160+
}
161+
162+
fn edge_node_long_non_zero_path() -> IndexFilledNode<IndexLayoutContractState> {
163+
IndexFilledNode(FilledNode {
164+
hash: HashOutput(Felt::from(1)),
165+
data: NodeData::Edge(EdgeData {
166+
bottom_data: (),
167+
// 1 followed by 250 zeros path of length 251
168+
path_to_bottom: PathToBottom::new(
169+
EdgePath(U256::from_words(1 >> 122, 0_u128)),
170+
EdgePathLength::new(251).unwrap(),
171+
)
172+
.unwrap(),
173+
}),
174+
})
175+
}
176+
177+
fn extract_leaf<L: Leaf>(node: IndexFilledNode<L>) -> L {
178+
if let NodeData::Leaf(leaf) = node.0.data {
179+
leaf
180+
} else {
181+
unreachable!("attempted to extract a leaf from a non-leaf node");
182+
}
183+
}
184+
185+
#[rstest]
186+
#[case::index_layout_contract_state(extract_leaf(contract_state_leaf()))]
187+
#[case::index_layout_compiled_class_hash(extract_leaf(compiled_class_hash_leaf()))]
188+
#[case::index_layout_starknet_storage_value(extract_leaf(starknet_storage_value_leaf()))]
189+
fn test_index_layout_leaf_serde<L: Leaf>(#[case] leaf: L) {
190+
let serialized = leaf.serialize().unwrap();
191+
let deserialized = L::deserialize(&serialized, &EmptyDeserializationContext).unwrap();
192+
assert_eq!(leaf, deserialized);
193+
}
194+
195+
#[rstest]
196+
#[case(extract_leaf(contract_state_leaf()), DbValue(vec![1, 2, 3]))]
197+
#[case(extract_leaf(compiled_class_hash_leaf()), DbValue(vec![1]))]
198+
#[case(extract_leaf(starknet_storage_value_leaf()), DbValue(vec![1]))]
199+
// We are serializing 2^96. The 4 MSB of the first byte are the chooser. For values >= 16 but under
200+
// 27 nibbles, the chooser is the number of bytes. In this case, the first byte will be 11000000
201+
// (chooser 12, i.e. we need 12 bytes) followed by the value.
202+
#[case(starknet_storage_value_leaf_96_bits(), DbValue(vec![192, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))]
203+
// We are serializing 2^136, which exceeds the 34 nibbles threshold where the encoding utilizes the
204+
// full 32 bytes. This case is marked by chooser = 15, followed by the value, starting immediately
205+
// after the chooser (hence the first 116 bits after the chooser are 0).
206+
#[case(starknet_storage_value_leaf_136_bits(), DbValue(vec![
207+
240, 0, 0, 0, 0, 0, 0, 0,
208+
0, 0, 0, 0, 0, 0, 0, 128,
209+
0, 0, 0, 0, 0, 0, 0, 0,
210+
0, 0, 0, 0, 0, 0, 0, 0
211+
]))]
212+
fn test_leaf_serialization_regression<L: Leaf>(
213+
#[case] leaf: L,
214+
#[case] expected_serialize: DbValue,
215+
) {
216+
let actual_serialize = leaf.serialize().unwrap();
217+
assert_eq!(actual_serialize, expected_serialize);
218+
}
219+
220+
#[rstest]
221+
#[case::index_layout_contract_state_leaf(contract_state_leaf(), IndexNodeContext { is_leaf: true })]
222+
#[case::index_layout_compiled_class_hash_leaf(compiled_class_hash_leaf(), IndexNodeContext { is_leaf: true })]
223+
#[case::index_layout_starknet_storage_value_leaf(starknet_storage_value_leaf(), IndexNodeContext { is_leaf: true })]
224+
#[case::index_layout_binary_node(binary_node(), IndexNodeContext { is_leaf: false })]
225+
#[case::index_layout_edge_node_short_path(edge_node_short_path_len_3(), IndexNodeContext { is_leaf: false })]
226+
#[case::index_layout_edge_node_short_path(edge_node_short_path_len_10(), IndexNodeContext { is_leaf: false })]
227+
#[case::index_layout_edge_node_path_divisible_by_8(edge_node_path_divisible_by_8(), IndexNodeContext { is_leaf: false })]
228+
#[case::index_layout_edge_node_path_not_divisible_by_8(edge_node_path_not_divisible_by_8(), IndexNodeContext { is_leaf: false })]
229+
#[case::index_layout_edge_node_long_zero_path(edge_node_long_zero_path(), IndexNodeContext { is_leaf: false })]
230+
#[case::index_layout_edge_node_long_non_zero_path(edge_node_long_non_zero_path(), IndexNodeContext { is_leaf: false })]
231+
232+
fn test_index_layout_node_serde<L: Leaf>(
233+
#[case] node: IndexFilledNode<L>,
234+
#[case] deserialization_context: IndexNodeContext,
235+
) where
236+
TreeHashFunctionImpl: TreeHashFunction<L>,
237+
{
238+
let serialized = node.serialize().unwrap();
239+
let deserialized =
240+
IndexFilledNode::<L>::deserialize(&serialized, &deserialization_context).unwrap();
241+
assert_eq!(node, deserialized);
242+
}

0 commit comments

Comments
 (0)