|
| 1 | +use async_recursion::async_recursion; |
| 2 | +use starknet_api::core::ContractAddress; |
| 3 | +use starknet_api::hash::{HashOutput, StateRoots}; |
| 4 | +use starknet_patricia::patricia_merkle_tree::filled_tree::node::{FactDbFilledNode, FilledNode}; |
| 5 | +use starknet_patricia::patricia_merkle_tree::filled_tree::node_serde::{ |
| 6 | + FactNodeDeserializationContext, |
| 7 | + PatriciaPrefix, |
| 8 | + FACT_LAYOUT_DB_KEY_SEPARATOR, |
| 9 | +}; |
| 10 | +use starknet_patricia::patricia_merkle_tree::node_data::inner_node::{ |
| 11 | + BinaryData, |
| 12 | + EdgeData, |
| 13 | + NodeData, |
| 14 | +}; |
| 15 | +use starknet_patricia::patricia_merkle_tree::node_data::leaf::Leaf; |
| 16 | +use starknet_patricia::patricia_merkle_tree::types::NodeIndex; |
| 17 | +use starknet_patricia::patricia_merkle_tree::updated_skeleton_tree::hash_function::TreeHashFunction; |
| 18 | +use starknet_patricia_storage::db_object::{DBObject, EmptyKeyContext, HasStaticPrefix}; |
| 19 | +use starknet_patricia_storage::map_storage::MapStorage; |
| 20 | +use starknet_patricia_storage::storage_trait::{create_db_key, DbHashMap, DbValue, Storage}; |
| 21 | + |
| 22 | +use crate::block_committer::input::{try_node_index_into_contract_address, StarknetStorageValue}; |
| 23 | +use crate::db::index_db::leaves::{ |
| 24 | + IndexLayoutCompiledClassHash, |
| 25 | + IndexLayoutContractState, |
| 26 | + IndexLayoutStarknetStorageValue, |
| 27 | + INDEX_LAYOUT_DB_KEY_SEPARATOR, |
| 28 | +}; |
| 29 | +use crate::db::index_db::types::{EmptyNodeData, IndexFilledNode}; |
| 30 | +use crate::hash_function::hash::TreeHashFunctionImpl; |
| 31 | +use crate::patricia_merkle_tree::leaf::leaf_impl::ContractState; |
| 32 | +use crate::patricia_merkle_tree::types::CompiledClassHash; |
| 33 | + |
| 34 | +/// Converts a complete Facts-layout forest DB to Index-layout. |
| 35 | +/// Expects all nodes to exist (panics if a node is missing). |
| 36 | +/// Use this for converting input storage before running tests. |
| 37 | +pub async fn convert_facts_forest_db_to_index_db( |
| 38 | + storage: &mut MapStorage, |
| 39 | + roots: StateRoots, |
| 40 | +) -> MapStorage { |
| 41 | + convert_forest(storage, roots, false).await |
| 42 | +} |
| 43 | + |
| 44 | +/// Converts a Facts-layout filled forest (delta) to Index-layout. |
| 45 | +/// Skips missing branches (nodes that weren't updated). |
| 46 | +/// Use this for converting expected_facts which only contains updated nodes. |
| 47 | +pub async fn convert_facts_filled_forest_to_index( |
| 48 | + storage: &mut MapStorage, |
| 49 | + roots: StateRoots, |
| 50 | +) -> MapStorage { |
| 51 | + convert_forest(storage, roots, true).await |
| 52 | +} |
| 53 | + |
| 54 | +/// Converts a single Facts-layout trie to Index-layout. |
| 55 | +/// Expects all nodes to exist (panics if a node is missing). |
| 56 | +pub async fn convert_facts_db_to_index_db<FactsLeaf, IndexLeaf, KeyContext>( |
| 57 | + storage: &mut MapStorage, |
| 58 | + root_hash: HashOutput, |
| 59 | + key_context: &KeyContext, |
| 60 | + current_leaves: &mut Option<Vec<(NodeIndex, FactsLeaf)>>, |
| 61 | +) -> MapStorage |
| 62 | +where |
| 63 | + FactsLeaf: Leaf + Into<IndexLeaf> + HasStaticPrefix<KeyContext = KeyContext>, |
| 64 | + IndexLeaf: Leaf + HasStaticPrefix<KeyContext = KeyContext>, |
| 65 | + TreeHashFunctionImpl: TreeHashFunction<IndexLeaf>, |
| 66 | + KeyContext: Sync, |
| 67 | +{ |
| 68 | + convert_single_trie(storage, root_hash, key_context, current_leaves, false).await |
| 69 | +} |
| 70 | + |
| 71 | +/// Converts all tries in a forest from Facts-layout to Index-layout. |
| 72 | +/// |
| 73 | +/// If skip_missing is true, missing nodes are silently skipped. Used when the original facts DB |
| 74 | +/// only contains the tree subset that was necessary for tests. Use false when the input is known to |
| 75 | +/// contain a full tree. |
| 76 | +async fn convert_forest( |
| 77 | + storage: &mut MapStorage, |
| 78 | + roots: StateRoots, |
| 79 | + skip_missing: bool, |
| 80 | +) -> MapStorage { |
| 81 | + let mut contract_leaves: Option<Vec<(NodeIndex, ContractState)>> = Some(Vec::new()); |
| 82 | + let mut index_storage = |
| 83 | + convert_single_trie::<ContractState, IndexLayoutContractState, EmptyKeyContext>( |
| 84 | + storage, |
| 85 | + roots.contracts_trie_root_hash, |
| 86 | + &EmptyKeyContext, |
| 87 | + &mut contract_leaves, |
| 88 | + skip_missing, |
| 89 | + ) |
| 90 | + .await |
| 91 | + .0; |
| 92 | + |
| 93 | + for (index, contract_leaf) in contract_leaves.unwrap() { |
| 94 | + let contract_address = try_node_index_into_contract_address(&index).unwrap(); |
| 95 | + let storage_root = contract_leaf.storage_root_hash; |
| 96 | + let contract_storage_entries = |
| 97 | + convert_single_trie::< |
| 98 | + StarknetStorageValue, |
| 99 | + IndexLayoutStarknetStorageValue, |
| 100 | + ContractAddress, |
| 101 | + >(storage, storage_root, &contract_address, &mut None, skip_missing) |
| 102 | + .await; |
| 103 | + index_storage.extend(contract_storage_entries.0); |
| 104 | + } |
| 105 | + |
| 106 | + let classes_storage = |
| 107 | + convert_single_trie::<CompiledClassHash, IndexLayoutCompiledClassHash, EmptyKeyContext>( |
| 108 | + storage, |
| 109 | + roots.classes_trie_root_hash, |
| 110 | + &EmptyKeyContext, |
| 111 | + &mut None, |
| 112 | + skip_missing, |
| 113 | + ) |
| 114 | + .await; |
| 115 | + index_storage.extend(classes_storage.0); |
| 116 | + |
| 117 | + MapStorage(index_storage) |
| 118 | +} |
| 119 | + |
| 120 | +/// Converts a single trie from Facts-layout to Index-layout. |
| 121 | +async fn convert_single_trie<FactsLeaf, IndexLeaf, KeyContext>( |
| 122 | + storage: &mut MapStorage, |
| 123 | + root_hash: HashOutput, |
| 124 | + key_context: &KeyContext, |
| 125 | + current_leaves: &mut Option<Vec<(NodeIndex, FactsLeaf)>>, |
| 126 | + skip_missing: bool, |
| 127 | +) -> MapStorage |
| 128 | +where |
| 129 | + FactsLeaf: Leaf + Into<IndexLeaf> + HasStaticPrefix<KeyContext = KeyContext>, |
| 130 | + IndexLeaf: Leaf + HasStaticPrefix<KeyContext = KeyContext>, |
| 131 | + TreeHashFunctionImpl: TreeHashFunction<IndexLeaf>, |
| 132 | + KeyContext: Sync, |
| 133 | +{ |
| 134 | + let mut index_layout_storage = MapStorage(DbHashMap::new()); |
| 135 | + traverse_and_convert::<FactsLeaf, IndexLeaf, KeyContext>( |
| 136 | + storage, |
| 137 | + &mut index_layout_storage, |
| 138 | + NodeIndex::ROOT, |
| 139 | + root_hash, |
| 140 | + key_context, |
| 141 | + current_leaves, |
| 142 | + skip_missing, |
| 143 | + ) |
| 144 | + .await; |
| 145 | + index_layout_storage |
| 146 | +} |
| 147 | + |
| 148 | +/// Recursively traverses a Facts-layout trie and converts each node to Index-layout. |
| 149 | +#[async_recursion] |
| 150 | +async fn traverse_and_convert<FactsLeaf, IndexLeaf, KeyContext>( |
| 151 | + facts_storage: &mut MapStorage, |
| 152 | + index_layout_storage: &mut MapStorage, |
| 153 | + current_index: NodeIndex, |
| 154 | + current_hash: HashOutput, |
| 155 | + key_context: &KeyContext, |
| 156 | + current_leaves: &mut Option<Vec<(NodeIndex, FactsLeaf)>>, |
| 157 | + skip_missing: bool, |
| 158 | +) where |
| 159 | + FactsLeaf: Leaf + Into<IndexLeaf> + HasStaticPrefix<KeyContext = KeyContext>, |
| 160 | + IndexLeaf: Leaf + HasStaticPrefix<KeyContext = KeyContext>, |
| 161 | + TreeHashFunctionImpl: TreeHashFunction<IndexLeaf>, |
| 162 | + KeyContext: Sync, |
| 163 | +{ |
| 164 | + if current_hash == HashOutput::ROOT_OF_EMPTY_TREE { |
| 165 | + return; |
| 166 | + } |
| 167 | + let db_prefix = if current_index.is_leaf() { |
| 168 | + FactsLeaf::get_static_prefix(key_context) |
| 169 | + } else { |
| 170 | + PatriciaPrefix::InnerNode.into() |
| 171 | + }; |
| 172 | + let facts_db_key = |
| 173 | + create_db_key(db_prefix, FACT_LAYOUT_DB_KEY_SEPARATOR, ¤t_hash.0.to_bytes_be()); |
| 174 | + |
| 175 | + // Try to get the node from storage. |
| 176 | + let filled_node_raw: Option<DbValue> = facts_storage.get(&facts_db_key).await.unwrap(); |
| 177 | + |
| 178 | + // Handle missing nodes based on the skip_missing flag. |
| 179 | + let Some(filled_node_raw) = filled_node_raw else { |
| 180 | + if skip_missing { |
| 181 | + return; |
| 182 | + } else { |
| 183 | + panic!( |
| 184 | + "Node not found in storage: index={:?}, hash={:?}. If converting a filled forest, \ |
| 185 | + use convert_facts_filled_forest_to_index.", |
| 186 | + current_index, current_hash |
| 187 | + ); |
| 188 | + } |
| 189 | + }; |
| 190 | + |
| 191 | + let facts_filled_node = FactDbFilledNode::<FactsLeaf>::deserialize( |
| 192 | + &filled_node_raw, |
| 193 | + &FactNodeDeserializationContext { |
| 194 | + node_hash: current_hash, |
| 195 | + is_leaf: current_index.is_leaf(), |
| 196 | + }, |
| 197 | + ) |
| 198 | + .unwrap(); |
| 199 | + |
| 200 | + let index_filled_node: IndexFilledNode<IndexLeaf> = match facts_filled_node.data { |
| 201 | + NodeData::Binary(binary_data) => { |
| 202 | + let children_indices = current_index.get_children_indices(); |
| 203 | + traverse_and_convert::<FactsLeaf, IndexLeaf, KeyContext>( |
| 204 | + facts_storage, |
| 205 | + index_layout_storage, |
| 206 | + children_indices[0], |
| 207 | + binary_data.left_data, |
| 208 | + key_context, |
| 209 | + current_leaves, |
| 210 | + skip_missing, |
| 211 | + ) |
| 212 | + .await; |
| 213 | + traverse_and_convert::<FactsLeaf, IndexLeaf, KeyContext>( |
| 214 | + facts_storage, |
| 215 | + index_layout_storage, |
| 216 | + children_indices[1], |
| 217 | + binary_data.right_data, |
| 218 | + key_context, |
| 219 | + current_leaves, |
| 220 | + skip_missing, |
| 221 | + ) |
| 222 | + .await; |
| 223 | + IndexFilledNode(FilledNode { |
| 224 | + hash: current_hash, |
| 225 | + data: NodeData::Binary(BinaryData { |
| 226 | + left_data: EmptyNodeData, |
| 227 | + right_data: EmptyNodeData, |
| 228 | + }), |
| 229 | + }) |
| 230 | + } |
| 231 | + NodeData::Edge(edge_data) => { |
| 232 | + let bottom_index = |
| 233 | + NodeIndex::compute_bottom_index(current_index, &edge_data.path_to_bottom); |
| 234 | + traverse_and_convert::<FactsLeaf, IndexLeaf, KeyContext>( |
| 235 | + facts_storage, |
| 236 | + index_layout_storage, |
| 237 | + bottom_index, |
| 238 | + edge_data.bottom_data, |
| 239 | + key_context, |
| 240 | + current_leaves, |
| 241 | + skip_missing, |
| 242 | + ) |
| 243 | + .await; |
| 244 | + IndexFilledNode(FilledNode { |
| 245 | + hash: current_hash, |
| 246 | + data: NodeData::Edge(EdgeData { |
| 247 | + bottom_data: EmptyNodeData, |
| 248 | + path_to_bottom: edge_data.path_to_bottom, |
| 249 | + }), |
| 250 | + }) |
| 251 | + } |
| 252 | + NodeData::Leaf(leaf) => { |
| 253 | + if let Some(leaves) = current_leaves { |
| 254 | + leaves.push((current_index, leaf.clone())); |
| 255 | + } |
| 256 | + |
| 257 | + IndexFilledNode(FilledNode { hash: current_hash, data: NodeData::Leaf(leaf.into()) }) |
| 258 | + } |
| 259 | + }; |
| 260 | + |
| 261 | + let index_db_key = create_db_key( |
| 262 | + IndexLeaf::get_static_prefix(key_context), |
| 263 | + INDEX_LAYOUT_DB_KEY_SEPARATOR, |
| 264 | + ¤t_index.0.to_be_bytes(), |
| 265 | + ); |
| 266 | + index_layout_storage.set(index_db_key, index_filled_node.serialize().unwrap()).await.unwrap(); |
| 267 | +} |
0 commit comments