Skip to content

Commit b2de623

Browse files
committed
starknet_committer: add index layout leaves
1 parent 7319de2 commit b2de623

File tree

6 files changed

+276
-1
lines changed

6 files changed

+276
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/starknet_committer/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ testing = ["starknet_patricia/testing"]
1313
apollo_config.workspace = true
1414
async-trait.workspace = true
1515
csv.workspace = true
16+
derive_more.workspace = true
1617
ethnum.workspace = true
1718
hex.workspace = true
1819
pretty_assertions.workspace = true
@@ -21,7 +22,7 @@ rand_distr.workspace = true
2122
rstest.workspace = true
2223
serde = { workspace = true, features = ["derive"] }
2324
serde_json.workspace = true
24-
starknet-types-core = { workspace = true, features = ["hash"] }
25+
starknet-types-core = { workspace = true, features = ["hash", "papyrus-serialization"] }
2526
starknet_api.workspace = true
2627
starknet_patricia.workspace = true
2728
starknet_patricia_storage.workspace = true

crates/starknet_committer/src/db.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ mod db_layout;
33
pub mod external_test_utils;
44
pub mod facts_db;
55
pub mod forest_trait;
6+
pub mod index_db;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use std::sync::LazyLock;
2+
3+
use starknet_api::core::{ClassHash, ContractAddress, Nonce, PATRICIA_KEY_UPPER_BOUND};
4+
use starknet_api::hash::HashOutput;
5+
use starknet_patricia::patricia_merkle_tree::node_data::errors::LeafResult;
6+
use starknet_patricia::patricia_merkle_tree::node_data::leaf::Leaf;
7+
use starknet_patricia_storage::db_object::{
8+
DBObject,
9+
EmptyDeserializationContext,
10+
HasStaticPrefix,
11+
};
12+
use starknet_patricia_storage::errors::{DeserializationError, SerializationError};
13+
use starknet_patricia_storage::storage_trait::{DbKeyPrefix, DbValue};
14+
use starknet_types_core::felt::Felt;
15+
16+
use crate::block_committer::input::StarknetStorageValue;
17+
use crate::patricia_merkle_tree::leaf::leaf_impl::ContractState;
18+
use crate::patricia_merkle_tree::types::CompiledClassHash;
19+
20+
// Wrap the leaves types so that we can implement the [DBObject] trait differently in index
21+
// layout.
22+
#[derive(Clone, Debug, Default, Eq, PartialEq, derive_more::AsRef, derive_more::From)]
23+
pub struct IndexLayoutContractState(pub ContractState);
24+
25+
#[derive(Clone, Debug, Default, Eq, PartialEq, derive_more::AsRef, derive_more::From)]
26+
pub struct IndexLayoutCompiledClassHash(pub CompiledClassHash);
27+
28+
#[derive(Clone, Debug, Default, Eq, PartialEq, derive_more::From)]
29+
pub struct IndexLayoutStarknetStorageValue(pub StarknetStorageValue);
30+
31+
/// Set to 2^251 + 1 to avoid collisions with contract addresses prefixes.
32+
static FIRST_AVAILABLE_PREFIX_FELT: LazyLock<Felt> =
33+
LazyLock::new(|| Felt::from_hex_unchecked(PATRICIA_KEY_UPPER_BOUND) + Felt::ONE);
34+
35+
/// The db key prefix of nodes in the contracts trie.
36+
///
37+
/// Set to FIRST_AVAILABLE_PREFIX_FELT
38+
static CONTRACTS_TREE_PREFIX: LazyLock<[u8; 32]> =
39+
LazyLock::new(|| FIRST_AVAILABLE_PREFIX_FELT.to_bytes_be());
40+
41+
/// The db key prefix of nodes in the contracts trie.
42+
///
43+
/// Set to FIRST_AVAILABLE_PREFIX_FELT + 1
44+
static CLASSES_TREE_PREFIX: LazyLock<[u8; 32]> =
45+
LazyLock::new(|| (*FIRST_AVAILABLE_PREFIX_FELT + Felt::ONE).to_bytes_be());
46+
47+
// TODO(Ariel): Delete this enum and use `CommitmentType` instead.
48+
#[derive(Debug, PartialEq)]
49+
pub enum TrieType {
50+
ContractsTrie,
51+
ClassesTrie,
52+
StorageTrie(ContractAddress),
53+
}
54+
55+
impl TrieType {
56+
fn db_prefix(&self) -> DbKeyPrefix {
57+
match self {
58+
Self::ContractsTrie => DbKeyPrefix::new((&CONTRACTS_TREE_PREFIX[..]).into()),
59+
Self::ClassesTrie => DbKeyPrefix::new((&CLASSES_TREE_PREFIX[..]).into()),
60+
Self::StorageTrie(contract_address) => {
61+
let prefix = contract_address.to_bytes_be().to_vec();
62+
DbKeyPrefix::new(prefix.into())
63+
}
64+
}
65+
}
66+
}
67+
68+
macro_rules! impl_has_static_prefix_for_index_layouts {
69+
($($ty:ty),* $(,)?) => {
70+
$(
71+
impl HasStaticPrefix for $ty {
72+
type KeyContext = TrieType;
73+
fn get_static_prefix(key_context: &Self::KeyContext) -> DbKeyPrefix {
74+
key_context.db_prefix()
75+
}
76+
}
77+
)*
78+
};
79+
}
80+
81+
impl_has_static_prefix_for_index_layouts! {
82+
IndexLayoutContractState,
83+
IndexLayoutCompiledClassHash,
84+
IndexLayoutStarknetStorageValue,
85+
}
86+
87+
macro_rules! impl_leaf_for_wrappers {
88+
($($wrapper:ty => $inner:ty),+ $(,)?) => {
89+
$(
90+
impl Leaf for $wrapper {
91+
type Input = <$inner as Leaf>::Input;
92+
type Output = <$inner as Leaf>::Output;
93+
94+
fn is_empty(&self) -> bool {
95+
// assumes `pub struct Wrapper(pub Inner);`
96+
self.0.is_empty()
97+
}
98+
99+
async fn create(
100+
input: Self::Input,
101+
) -> LeafResult<(Self, Self::Output)> {
102+
let (created_leaf, output) = <$inner as Leaf>::create(input).await?;
103+
Ok((Self(created_leaf), output))
104+
}
105+
}
106+
)+
107+
};
108+
}
109+
110+
impl_leaf_for_wrappers!(
111+
IndexLayoutContractState => ContractState,
112+
IndexLayoutStarknetStorageValue => StarknetStorageValue,
113+
IndexLayoutCompiledClassHash => CompiledClassHash,
114+
);
115+
116+
impl DBObject for IndexLayoutContractState {
117+
type DeserializeContext = EmptyDeserializationContext;
118+
fn serialize(&self) -> Result<DbValue, SerializationError> {
119+
serialize_felts(&[self.0.class_hash.0, self.0.storage_root_hash.0, self.0.nonce.0])
120+
}
121+
122+
fn deserialize(
123+
value: &DbValue,
124+
_deserialize_context: &Self::DeserializeContext,
125+
) -> Result<Self, DeserializationError> {
126+
let mut cursor: &[u8] = &value.0;
127+
let err = || DeserializationError::FeltDeserialization(value.clone());
128+
129+
let class_hash = deserialize_felt(&mut cursor, err)?;
130+
let storage_root_hash = deserialize_felt(&mut cursor, err)?;
131+
let nonce = deserialize_felt(&mut cursor, err)?;
132+
133+
Ok(Self(ContractState {
134+
class_hash: ClassHash(class_hash),
135+
storage_root_hash: HashOutput(storage_root_hash),
136+
nonce: Nonce(nonce),
137+
}))
138+
}
139+
}
140+
141+
impl DBObject for IndexLayoutCompiledClassHash {
142+
type DeserializeContext = EmptyDeserializationContext;
143+
144+
fn serialize(&self) -> Result<DbValue, SerializationError> {
145+
serialize_felts(&[self.0.0])
146+
}
147+
148+
fn deserialize(
149+
value: &DbValue,
150+
_deserialize_context: &Self::DeserializeContext,
151+
) -> Result<Self, DeserializationError> {
152+
Ok(Self(CompiledClassHash(deserialize_felt(&mut &value.0[..], || {
153+
DeserializationError::FeltDeserialization(value.clone())
154+
})?)))
155+
}
156+
}
157+
158+
impl DBObject for IndexLayoutStarknetStorageValue {
159+
type DeserializeContext = EmptyDeserializationContext;
160+
161+
fn serialize(&self) -> Result<DbValue, SerializationError> {
162+
serialize_felts(&[self.0.0])
163+
}
164+
165+
fn deserialize(
166+
value: &DbValue,
167+
_deserialize_context: &Self::DeserializeContext,
168+
) -> Result<Self, DeserializationError> {
169+
Ok(Self(StarknetStorageValue(deserialize_felt(&mut &value.0[..], || {
170+
DeserializationError::FeltDeserialization(value.clone())
171+
})?)))
172+
}
173+
}
174+
175+
fn deserialize_felt(
176+
cursor: &mut &[u8],
177+
mk_err: impl Fn() -> DeserializationError,
178+
) -> Result<Felt, DeserializationError> {
179+
Felt::deserialize(cursor).ok_or_else(mk_err)
180+
}
181+
182+
fn serialize_felts(felts: &[Felt]) -> Result<DbValue, SerializationError> {
183+
let mut buffer = Vec::new();
184+
for felt in felts {
185+
felt.serialize(&mut buffer)?;
186+
}
187+
Ok(DbValue(buffer))
188+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use rstest::rstest;
2+
use starknet_api::core::{ClassHash, Nonce};
3+
use starknet_api::hash::HashOutput;
4+
use starknet_patricia::patricia_merkle_tree::node_data::leaf::Leaf;
5+
use starknet_patricia_storage::db_object::EmptyDeserializationContext;
6+
use starknet_patricia_storage::storage_trait::DbValue;
7+
use starknet_types_core::felt::Felt;
8+
9+
use crate::block_committer::input::StarknetStorageValue;
10+
use crate::db::index_db::leaves::{
11+
IndexLayoutCompiledClassHash,
12+
IndexLayoutContractState,
13+
IndexLayoutStarknetStorageValue,
14+
};
15+
use crate::patricia_merkle_tree::leaf::leaf_impl::ContractState;
16+
use crate::patricia_merkle_tree::types::CompiledClassHash;
17+
18+
fn contract_state_leaf() -> IndexLayoutContractState {
19+
IndexLayoutContractState(ContractState {
20+
class_hash: ClassHash(Felt::from(1)),
21+
storage_root_hash: HashOutput(Felt::from(2)),
22+
nonce: Nonce(Felt::from(3)),
23+
})
24+
}
25+
26+
fn compiled_class_hash_leaf() -> IndexLayoutCompiledClassHash {
27+
IndexLayoutCompiledClassHash(CompiledClassHash(Felt::ONE))
28+
}
29+
30+
fn starknet_storage_value_leaf() -> IndexLayoutStarknetStorageValue {
31+
IndexLayoutStarknetStorageValue(StarknetStorageValue(Felt::ONE))
32+
}
33+
34+
fn starknet_storage_value_leaf_96_bits() -> IndexLayoutStarknetStorageValue {
35+
// 2^96 (12 bytes, under the 27 nibbles threshold)
36+
IndexLayoutStarknetStorageValue(StarknetStorageValue(Felt::from(1_u128 << 95)))
37+
}
38+
39+
fn starknet_storage_value_leaf_136_bits() -> IndexLayoutStarknetStorageValue {
40+
// 2^136 (reaching the 34 nibbles / 17 bytes serialization threshold)
41+
IndexLayoutStarknetStorageValue(StarknetStorageValue(Felt::from_bytes_be(&[
42+
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,
43+
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,
44+
0_u8, 0_u8,
45+
])))
46+
}
47+
48+
#[rstest]
49+
#[case::index_layout_contract_state(contract_state_leaf())]
50+
#[case::index_layout_compiled_class_hash(compiled_class_hash_leaf())]
51+
#[case::index_layout_starknet_storage_value(starknet_storage_value_leaf())]
52+
fn test_index_layout_leaf_serde<L: Leaf>(#[case] leaf: L) {
53+
let serialized = leaf.serialize().unwrap();
54+
let deserialized = L::deserialize(&serialized, &EmptyDeserializationContext).unwrap();
55+
assert_eq!(leaf, deserialized);
56+
}
57+
58+
#[rstest]
59+
#[case(contract_state_leaf(), DbValue(vec![1, 2, 3]))]
60+
#[case(compiled_class_hash_leaf(), DbValue(vec![1]))]
61+
#[case(starknet_storage_value_leaf(), DbValue(vec![1]))]
62+
// We are serializing 2^96. The 4 MSB of the first byte are the chooser. For values >= 16 but under
63+
// 27 nibbles, the chooser is the number of bytes. In this case, the first byte will be 11000000
64+
// (chooser 12, i.e. we need 12 bytes) followed by the value.
65+
#[case(starknet_storage_value_leaf_96_bits(), DbValue(vec![192, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))]
66+
// We are serializing 2^136, which exceeds the 34 nibbles threshold where the encoding utilizes the
67+
// full 32 bytes. This case is marked by chooser = 15, followed by the value, starting immediately
68+
// after the chooser (hence the first 116 bits after the chooser are 0).
69+
#[case(starknet_storage_value_leaf_136_bits(), DbValue(vec![
70+
240, 0, 0, 0, 0, 0, 0, 0,
71+
0, 0, 0, 0, 0, 0, 0, 128,
72+
0, 0, 0, 0, 0, 0, 0, 0,
73+
0, 0, 0, 0, 0, 0, 0, 0
74+
]))]
75+
fn test_leaf_serialization_regression<L: Leaf>(
76+
#[case] leaf: L,
77+
#[case] expected_serialize: DbValue,
78+
) {
79+
let actual_serialize = leaf.serialize().unwrap();
80+
assert_eq!(actual_serialize, expected_serialize);
81+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod leaves;
2+
#[cfg(test)]
3+
pub mod leaves_test;

0 commit comments

Comments
 (0)