Skip to content

Commit af48366

Browse files
added multiproof endpoint to partial tries
1 parent 3bf3c11 commit af48366

File tree

17 files changed

+472
-1474
lines changed

17 files changed

+472
-1474
lines changed

Cargo.lock

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

crates/core/src/backend/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,8 @@ where
252252
}
253253

254254
info!(genesis_hash = %local_hash, "Genesis has already been initialized");
255-
} else {
256-
// Initialize the dev genesis block
257-
255+
} else if !is_forking {
256+
// Initialize the dev genesis block (only for non-forked instances)
258257
let block = chain_spec.block();
259258
let states = chain_spec.state_updates();
260259

@@ -273,6 +272,7 @@ where
273272

274273
info!(genesis_hash = %outcome.block_hash, "Genesis initialized");
275274
}
275+
// For forked instances, genesis is not created (fork already has its state)
276276

277277
Ok(())
278278
}

crates/core/src/service/block_producer.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,9 @@ where
422422
let provider = backend.storage.provider();
423423

424424
let latest_num = provider.latest_number()?;
425+
eprintln!("🔍 Creating executor for next block, latest_num: {}", latest_num);
425426
let updated_state = provider.latest()?;
427+
eprintln!("✅ Got updated_state for block {}", latest_num + 1);
426428

427429
let mut block_env = provider.block_env_at(latest_num.into())?.unwrap();
428430
backend.update_block_env(&mut block_env);

crates/rpc/rpc-server/src/starknet/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,13 @@ where
422422
let state = self.state(&block_id)?;
423423

424424
// Check that contract exist by checking the class hash of the contract,
425-
// unless its address 0x1 which is special system contract and does not
426-
// have a class. See https://docs.starknet.io/architecture-and-concepts/network-architecture/starknet-state/#address_0x1.
425+
// unless its address 0x1 or 0x2 which are special system contracts and does not
426+
// have a class.
427+
// See:
428+
// https://docs.starknet.io/learn/protocol/state#address-0x1.
429+
// https://docs.starknet.io/learn/protocol/data-availability#v0-13-4
427430
if contract_address.0 != Felt::ONE
431+
&& contract_address.0 != Felt::TWO
428432
&& contract_address.0 != Felt::TWO
429433
&& state.class_hash_of_contract(contract_address)?.is_none()
430434
{

crates/rpc/rpc-server/tests/forking.rs

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -666,14 +666,17 @@ mod tests {
666666
use katana_core::service::block_producer::IntervalBlockProducer;
667667
use katana_db::Db;
668668
use katana_node::config::fork::ForkingConfig;
669+
use katana_primitives::block::{
670+
Block, BlockNumber, FinalityStatus, Header, SealedBlockWithStatus,
671+
};
669672
use katana_primitives::chain::ChainId;
670673
use katana_primitives::class::ClassHash;
671-
use katana_primitives::state::StateUpdates;
674+
use katana_primitives::state::{StateUpdates, StateUpdatesWithClasses};
672675
use katana_primitives::ContractAddress;
673676
use katana_primitives::Felt;
674-
use katana_provider::api::block::BlockHashProvider;
675-
use katana_provider::api::block::BlockNumberProvider;
676677
use katana_provider::api::block::BlockWriter;
678+
use katana_provider::api::block::{BlockHashProvider, BlockNumberProvider};
679+
use katana_provider::api::env::BlockEnvProvider;
677680
use katana_provider::api::trie::TrieWriter;
678681
use katana_provider::MutableProvider;
679682
use katana_provider::{ForkProviderFactory, ProviderFactory};
@@ -801,6 +804,20 @@ mod tests {
801804
(address, class_hash, storage, nonce)
802805
}
803806

807+
fn create_test_block_with_state_updates(
808+
block_number: BlockNumber,
809+
_state_updates: StateUpdates,
810+
) -> SealedBlockWithStatus {
811+
SealedBlockWithStatus {
812+
block: Block {
813+
header: Header { number: block_number, ..Default::default() },
814+
body: Vec::new(),
815+
}
816+
.seal(),
817+
status: FinalityStatus::AcceptedOnL2,
818+
}
819+
}
820+
804821
/// To run this test you need to comment out global cache part in Node::build() "let global_class_cache = class_cache.build_global()?";
805822
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
806823
async fn test_commit_new_state_root_two_katana_instances() {
@@ -811,11 +828,6 @@ mod tests {
811828

812829
// initialize state and mine at least one block before starting the fork
813830
let mut producer = IntervalBlockProducer::new(backend.clone(), None);
814-
let block_number = provider.latest_number().unwrap();
815-
let state_updates = setup_mainnet_updates_randomized(5);
816-
let provider_mut = backend.storage.provider_mut();
817-
provider_mut.compute_state_root(block_number, &state_updates).unwrap();
818-
provider_mut.commit().unwrap();
819831
producer.force_mine();
820832

821833
let fork_from_block = provider.latest_number().unwrap();
@@ -842,34 +854,71 @@ mod tests {
842854
let fork_block_number = fork_provider.latest_number().unwrap();
843855

844856
let fork_minimal_updates = setup_mainnet_updates_randomized(5);
857+
858+
// Insert block with state updates on fork
845859
let fork_provider_mut = fork_backend.storage.provider_mut();
846-
let state_root =
847-
fork_provider_mut.compute_state_root(fork_block_number, &fork_minimal_updates).unwrap();
860+
let new_fork_block_number = fork_block_number + 1;
861+
let fork_state_root = fork_provider_mut
862+
.compute_state_root(new_fork_block_number, &fork_minimal_updates)
863+
.unwrap();
864+
865+
// Create and insert block with the state updates
866+
let fork_block = create_test_block_with_state_updates(
867+
new_fork_block_number,
868+
fork_minimal_updates.clone(),
869+
);
870+
fork_provider_mut
871+
.insert_block_with_states_and_receipts(
872+
fork_block,
873+
StateUpdatesWithClasses {
874+
state_updates: fork_minimal_updates.clone(),
875+
..Default::default()
876+
},
877+
vec![],
878+
vec![],
879+
)
880+
.unwrap();
848881
fork_provider_mut.commit().unwrap();
882+
883+
// Insert block with same state updates on mainnet
849884
let provider_mut = backend.storage.provider_mut();
850-
let mainnet_state_root_same_updates =
851-
provider_mut.compute_state_root(block_number, &fork_minimal_updates).unwrap();
885+
let new_block_number = block_number + 1;
886+
let mainnet_state_root =
887+
provider_mut.compute_state_root(new_block_number, &fork_minimal_updates).unwrap();
888+
889+
let mainnet_block =
890+
create_test_block_with_state_updates(new_block_number, fork_minimal_updates.clone());
891+
provider_mut
892+
.insert_block_with_states_and_receipts(
893+
mainnet_block,
894+
StateUpdatesWithClasses {
895+
state_updates: fork_minimal_updates.clone(),
896+
..Default::default()
897+
},
898+
vec![],
899+
vec![],
900+
)
901+
.unwrap();
852902
provider_mut.commit().unwrap();
853903

854-
producer.force_mine();
855-
fork_producer.force_mine();
856-
857904
assert_eq!(
858-
state_root, mainnet_state_root_same_updates,
905+
fork_state_root, mainnet_state_root,
859906
"State roots do not match on first run: fork={:?}, mainnet={:?}",
860-
state_root, mainnet_state_root_same_updates
907+
fork_state_root, mainnet_state_root
861908
);
862909

863910
let block_number = provider.latest_number().unwrap();
864911
let fork_block_number = fork_provider.latest_number().unwrap();
865912
let state_updates = setup_mainnet_updates_randomized(5);
866913
let fork_provider_mut = fork_backend.storage.provider_mut();
914+
let new_fork_block_number = fork_block_number + 1;
867915
let fork_state_root =
868-
fork_provider_mut.compute_state_root(fork_block_number, &state_updates).unwrap();
916+
fork_provider_mut.compute_state_root(new_fork_block_number, &state_updates).unwrap();
869917
fork_provider_mut.commit().unwrap();
870918
let provider_mut = backend.storage.provider_mut();
919+
let new_block_number = block_number + 1;
871920
let mainnet_state_root =
872-
provider_mut.compute_state_root(block_number, &state_updates).unwrap();
921+
provider_mut.compute_state_root(new_block_number, &state_updates).unwrap();
873922
provider_mut.commit().unwrap();
874923

875924
producer.force_mine();
@@ -886,12 +935,14 @@ mod tests {
886935

887936
let state_updates = setup_mainnet_updates_randomized(5);
888937
let fork_provider_mut = fork_backend.storage.provider_mut();
938+
let new_fork_block_number = fork_block_number + 1;
889939
let fork_state_root =
890-
fork_provider_mut.compute_state_root(fork_block_number, &state_updates).unwrap();
940+
fork_provider_mut.compute_state_root(new_fork_block_number, &state_updates).unwrap();
891941
fork_provider_mut.commit().unwrap();
892942
let provider_mut = backend.storage.provider_mut();
943+
let new_block_number = block_number + 1;
893944
let mainnet_state_root =
894-
provider_mut.compute_state_root(block_number, &state_updates).unwrap();
945+
provider_mut.compute_state_root(new_block_number, &state_updates).unwrap();
895946
provider_mut.commit().unwrap();
896947

897948
assert_eq!(

crates/storage/db/src/trie/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,25 @@ where
6464
katana_trie::ContractsTrie::new(TrieDb::new(self.tx.clone()))
6565
}
6666

67+
/// Returns the partial contracts trie (for forked instances that use insert_with_proof).
68+
pub fn partial_contracts_trie(
69+
&self,
70+
) -> katana_trie::PartialContractsTrie<TrieDb<'a, tables::ContractsTrie, Tx>> {
71+
katana_trie::PartialContractsTrie::new_partial(TrieDb::new(self.tx.clone()))
72+
}
73+
6774
/// Returns the classes trie.
6875
pub fn classes_trie(&self) -> katana_trie::ClassesTrie<TrieDb<'a, tables::ClassesTrie, Tx>> {
6976
katana_trie::ClassesTrie::new(TrieDb::new(self.tx.clone()))
7077
}
7178

79+
/// Returns the partial classes trie (for forked instances that use insert_with_proof).
80+
pub fn partial_classes_trie(
81+
&self,
82+
) -> katana_trie::PartialClassesTrie<TrieDb<'a, tables::ClassesTrie, Tx>> {
83+
katana_trie::PartialClassesTrie::new_partial(TrieDb::new(self.tx.clone()))
84+
}
85+
7286
// TODO: makes this return an Option
7387
/// Returns the storages trie.
7488
pub fn storages_trie(
@@ -77,6 +91,14 @@ where
7791
) -> katana_trie::StoragesTrie<TrieDb<'a, tables::StoragesTrie, Tx>> {
7892
katana_trie::StoragesTrie::new(TrieDb::new(self.tx.clone()), address)
7993
}
94+
95+
/// Returns the partial storages trie (for forked instances that use insert_with_proof).
96+
pub fn partial_storages_trie(
97+
&self,
98+
address: ContractAddress,
99+
) -> katana_trie::PartialStoragesTrie<TrieDb<'a, tables::StoragesTrie, Tx>> {
100+
katana_trie::PartialStoragesTrie::new_partial(TrieDb::new(self.tx.clone()), address)
101+
}
80102
}
81103

82104
/// Historical tries, allowing access to the state tries at each block.
@@ -101,6 +123,14 @@ where
101123
katana_trie::ContractsTrie::new(SnapshotTrieDb::new(self.tx.clone(), commit))
102124
}
103125

126+
/// Returns the partial historical contracts trie (for forked instances that use insert_with_proof).
127+
pub fn partial_contracts_trie(
128+
&self,
129+
) -> katana_trie::PartialContractsTrie<SnapshotTrieDb<'a, tables::ContractsTrie, Tx>> {
130+
let commit = CommitId::new(self.block);
131+
katana_trie::PartialContractsTrie::new_partial(SnapshotTrieDb::new(self.tx.clone(), commit))
132+
}
133+
104134
/// Returns the historical classes trie.
105135
pub fn classes_trie(
106136
&self,
@@ -109,6 +139,14 @@ where
109139
katana_trie::ClassesTrie::new(SnapshotTrieDb::new(self.tx.clone(), commit))
110140
}
111141

142+
/// Returns the partial historical classes trie (for forked instances that use insert_with_proof).
143+
pub fn partial_classes_trie(
144+
&self,
145+
) -> katana_trie::PartialClassesTrie<SnapshotTrieDb<'a, tables::ClassesTrie, Tx>> {
146+
let commit = CommitId::new(self.block);
147+
katana_trie::PartialClassesTrie::new_partial(SnapshotTrieDb::new(self.tx.clone(), commit))
148+
}
149+
112150
// TODO: makes this return an Option
113151
/// Returns the historical storages trie.
114152
pub fn storages_trie(
@@ -118,6 +156,18 @@ where
118156
let commit = CommitId::new(self.block);
119157
katana_trie::StoragesTrie::new(SnapshotTrieDb::new(self.tx.clone(), commit), address)
120158
}
159+
160+
/// Returns the partial historical storages trie (for forked instances that use insert_with_proof).
161+
pub fn partial_storages_trie(
162+
&self,
163+
address: ContractAddress,
164+
) -> katana_trie::PartialStoragesTrie<SnapshotTrieDb<'a, tables::StoragesTrie, Tx>> {
165+
let commit = CommitId::new(self.block);
166+
katana_trie::PartialStoragesTrie::new_partial(
167+
SnapshotTrieDb::new(self.tx.clone(), commit),
168+
address,
169+
)
170+
}
121171
}
122172

123173
// --- Trie's database implementations. These are implemented based on the Bonsai Trie

crates/storage/provider/provider/src/providers/fork/mod.rs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use katana_provider_api::block::{
2121
};
2222
use katana_provider_api::env::BlockEnvProvider;
2323
use katana_provider_api::stage::StageCheckpointProvider;
24+
use katana_provider_api::state::StateFactoryProvider;
2425
use katana_provider_api::state_update::StateUpdateProvider;
2526
use katana_provider_api::transaction::{
2627
ReceiptProvider, TransactionProvider, TransactionStatusProvider, TransactionTraceProvider,
@@ -207,22 +208,53 @@ impl<Tx1: DbTx> BlockNumberProvider for ForkedProvider<Tx1> {
207208
}
208209

209210
fn latest_number(&self) -> ProviderResult<BlockNumber> {
210-
match self.local_db.latest_number() {
211-
Ok(num) => Ok(num),
212-
// return the fork block number if local db return this error. this can only happen whne
213-
// the ForkedProvider is constructed without inserting any locally produced
214-
// blocks.
215-
Err(ProviderError::MissingLatestBlockNumber) => Ok(self.block_id()),
216-
Err(err) => Err(err),
217-
}
211+
let fork_point = self.block_id();
212+
let local_latest = match self.local_db.latest_number() {
213+
Ok(num) => num,
214+
Err(ProviderError::MissingLatestBlockNumber) => fork_point,
215+
Err(err) => return Err(err),
216+
};
217+
Ok(local_latest.max(fork_point))
218218
}
219219
}
220220

221221
impl<Tx1: DbTx> BlockIdReader for ForkedProvider<Tx1> {}
222222

223223
impl<Tx1: DbTx> BlockHashProvider for ForkedProvider<Tx1> {
224224
fn latest_hash(&self) -> ProviderResult<BlockHash> {
225-
self.local_db.latest_hash()
225+
// Use the same logic as latest_number() - if local_db has blocks, use local hash
226+
let fork_point = self.block_id();
227+
let local_latest = match self.local_db.latest_number() {
228+
Ok(num) => num,
229+
Err(ProviderError::MissingLatestBlockNumber) => fork_point,
230+
Err(err) => return Err(err),
231+
};
232+
233+
// If we have local blocks after fork point, use local hash
234+
if local_latest > fork_point {
235+
return self.local_db.latest_hash();
236+
}
237+
238+
// Otherwise, use fork point hash (either local_latest == fork_point or local_db is empty)
239+
if let Ok(hash) = self.local_db.latest_hash() {
240+
Ok(hash)
241+
} else {
242+
// If local_db is empty, return the hash of the fork point block
243+
if let Some(hash) = self.fork_db.db.provider().block_hash_by_num(fork_point)? {
244+
Ok(hash)
245+
} else {
246+
// Fetch the fork point block if not cached
247+
if self.fork_db.fetch_historical_blocks(fork_point.into())? {
248+
self.fork_db
249+
.db
250+
.provider()
251+
.block_hash_by_num(fork_point)?
252+
.ok_or(ProviderError::MissingLatestBlockHash)
253+
} else {
254+
Err(ProviderError::MissingLatestBlockHash)
255+
}
256+
}
257+
}
226258
}
227259

228260
fn block_hash_by_num(&self, num: BlockNumber) -> ProviderResult<Option<BlockHash>> {
@@ -668,6 +700,28 @@ impl<Tx1: DbTxMut> BlockWriter for ForkedProvider<Tx1> {
668700
receipts: Vec<Receipt>,
669701
executions: Vec<TypedTransactionExecutionInfo>,
670702
) -> ProviderResult<()> {
703+
// BUGFIX: Before inserting state updates, ensure all contracts referenced in nonce_updates
704+
// have their ContractInfo in local_db. For forked contracts, the class_hash may only exist
705+
// in fork_cache (in-memory) or on the remote fork. We need to copy it to local_db first.
706+
use katana_db::tables;
707+
use katana_provider_api::state::StateProvider;
708+
709+
for addr in states.state_updates.nonce_updates.keys() {
710+
// Check if ContractInfo exists in local_db
711+
if self.local_db.tx().get::<tables::ContractInfo>(*addr)?.is_none() {
712+
// Contract info not in local_db, check if it exists in fork cache or remote
713+
// Create a state provider to search in fork
714+
let state = self.latest()?;
715+
if let Some(class_hash) = state.class_hash_of_contract(*addr)? {
716+
// Found in fork - copy to local_db before processing state updates
717+
let nonce = state.nonce(*addr)?.unwrap_or_default();
718+
let contract_info =
719+
katana_primitives::contract::GenericContractInfo { class_hash, nonce };
720+
self.local_db.tx().put::<tables::ContractInfo>(*addr, contract_info)?;
721+
}
722+
}
723+
}
724+
671725
self.local_db.insert_block_with_states_and_receipts(block, states, receipts, executions)
672726
}
673727
}

0 commit comments

Comments
 (0)