diff --git a/Cargo.lock b/Cargo.lock index 6598bbc812..3c6e267b0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10983,6 +10983,7 @@ dependencies = [ "assert_matches", "async-trait", "indexmap 2.7.0", + "itertools 0.12.1", "papyrus_base_layer", "papyrus_config", "pretty_assertions", diff --git a/crates/starknet_l1_provider/Cargo.toml b/crates/starknet_l1_provider/Cargo.toml index d9351168a1..e0861208ce 100644 --- a/crates/starknet_l1_provider/Cargo.toml +++ b/crates/starknet_l1_provider/Cargo.toml @@ -24,6 +24,7 @@ validator.workspace = true [dev-dependencies] assert_matches.workspace = true +itertools.workspace = true pretty_assertions.workspace = true starknet_api = { workspace = true, features = ["testing"] } diff --git a/crates/starknet_l1_provider/src/l1_provider.rs b/crates/starknet_l1_provider/src/l1_provider.rs index 6bb0ec60cf..de1576646f 100644 --- a/crates/starknet_l1_provider/src/l1_provider.rs +++ b/crates/starknet_l1_provider/src/l1_provider.rs @@ -2,7 +2,7 @@ use starknet_api::block::BlockNumber; use starknet_api::executable_transaction::L1HandlerTransaction; use starknet_api::transaction::TransactionHash; use starknet_l1_provider_types::errors::L1ProviderError; -use starknet_l1_provider_types::{Event, L1ProviderResult, ValidationStatus}; +use starknet_l1_provider_types::{Event, L1ProviderResult, SessionState, ValidationStatus}; use starknet_sequencer_infra::component_definitions::ComponentStarter; use crate::transaction_manager::TransactionManager; @@ -23,6 +23,17 @@ impl L1Provider { todo!("Init crawler in uninitialized_state from config, to initialize call `reset`."); } + pub fn start_block( + &mut self, + height: BlockNumber, + state: SessionState, + ) -> L1ProviderResult<()> { + self.validate_height(height)?; + self.state = state.into(); + self.tx_manager.start_block(); + Ok(()) + } + /// Retrieves up to `n_txs` transactions that have yet to be proposed or accepted on L2. pub fn get_txs( &mut self, @@ -65,22 +76,10 @@ impl L1Provider { ) } - pub fn validation_start(&mut self, height: BlockNumber) -> L1ProviderResult<()> { - self.validate_height(height)?; - self.state = self.state.transition_to_validate()?; - Ok(()) - } - pub fn process_l1_events(&mut self, _events: Vec) -> L1ProviderResult<()> { todo!() } - pub fn proposal_start(&mut self, height: BlockNumber) -> L1ProviderResult<()> { - self.validate_height(height)?; - self.state = self.state.transition_to_propose()?; - Ok(()) - } - /// Simple recovery from L1 and L2 reorgs by reseting the service, which rewinds L1 and L2 /// information. pub async fn handle_reorg(&mut self) -> L1ProviderResult<()> { diff --git a/crates/starknet_l1_provider/src/l1_provider_tests.rs b/crates/starknet_l1_provider/src/l1_provider_tests.rs index 3523e65446..d312e0fa63 100644 --- a/crates/starknet_l1_provider/src/l1_provider_tests.rs +++ b/crates/starknet_l1_provider/src/l1_provider_tests.rs @@ -1,15 +1,21 @@ use assert_matches::assert_matches; +use itertools::Itertools; use pretty_assertions::assert_eq; use starknet_api::block::BlockNumber; use starknet_api::test_utils::l1_handler::executable_l1_handler_tx; use starknet_api::transaction::TransactionHash; use starknet_api::{l1_handler_tx_args, tx_hash}; use starknet_l1_provider_types::errors::L1ProviderError; +use starknet_l1_provider_types::SessionState::{ + self, + Propose as ProposeSession, + Validate as ValidateSession, +}; use starknet_l1_provider_types::ValidationStatus; use crate::l1_provider::L1Provider; use crate::test_utils::L1ProviderContentBuilder; -use crate::ProviderState::{Pending, Propose, Uninitialized, Validate}; +use crate::ProviderState; macro_rules! tx { (tx_hash: $tx_hash:expr) => {{ @@ -27,7 +33,7 @@ fn get_txs_happy_flow() { let txs = [tx!(tx_hash: 0), tx!(tx_hash: 1), tx!(tx_hash: 2)]; let mut l1_provider = L1ProviderContentBuilder::new() .with_txs(txs.clone()) - .with_state(Propose) + .with_state(ProviderState::Propose) .build_into_l1_provider(); // Test. @@ -43,7 +49,7 @@ fn validate_happy_flow() { let mut l1_provider = L1ProviderContentBuilder::new() .with_txs([tx!(tx_hash: 1)]) .with_committed([tx_hash!(2)]) - .with_state(Validate) + .with_state(ProviderState::Validate) .build_into_l1_provider(); // Test. @@ -70,7 +76,7 @@ fn validate_happy_flow() { fn pending_state_errors() { // Setup. let mut l1_provider = L1ProviderContentBuilder::new() - .with_state(Pending) + .with_state(ProviderState::Pending) .with_txs([tx!(tx_hash: 1)]) .build_into_l1_provider(); @@ -90,7 +96,7 @@ fn pending_state_errors() { #[should_panic(expected = "Uninitialized L1 provider")] fn uninitialized_get_txs() { let mut uninitialized_l1_provider = L1Provider::default(); - assert_eq!(uninitialized_l1_provider.state, Uninitialized); + assert_eq!(uninitialized_l1_provider.state, ProviderState::Uninitialized); uninitialized_l1_provider.get_txs(1, BlockNumber(1)).unwrap(); } @@ -99,44 +105,21 @@ fn uninitialized_get_txs() { #[should_panic(expected = "Uninitialized L1 provider")] fn uninitialized_validate() { let mut uninitialized_l1_provider = L1Provider::default(); - assert_eq!(uninitialized_l1_provider.state, Uninitialized); + assert_eq!(uninitialized_l1_provider.state, ProviderState::Uninitialized); uninitialized_l1_provider.validate(TransactionHash::default(), BlockNumber(1)).unwrap(); } #[test] -fn proposal_start_errors() { +fn proposal_start_multiple_proposals_same_height() { // Setup. let mut l1_provider = - L1ProviderContentBuilder::new().with_state(Pending).build_into_l1_provider(); - // Test. - l1_provider.proposal_start(BlockNumber(1)).unwrap(); - - assert_eq!( - l1_provider.proposal_start(BlockNumber(1)).unwrap_err(), - L1ProviderError::unexpected_transition(Propose, Propose) - ); - assert_eq!( - l1_provider.validation_start(BlockNumber(1)).unwrap_err(), - L1ProviderError::unexpected_transition(Propose, Validate) - ); -} - -#[test] -fn validation_start_errors() { - // Setup. - let mut l1_provider = - L1ProviderContentBuilder::new().with_state(Pending).build_into_l1_provider(); - - // Test. - l1_provider.validation_start(BlockNumber(1)).unwrap(); - - assert_eq!( - l1_provider.validation_start(BlockNumber(1)).unwrap_err(), - L1ProviderError::unexpected_transition(Validate, Validate) - ); - assert_eq!( - l1_provider.proposal_start(BlockNumber(1)).unwrap_err(), - L1ProviderError::unexpected_transition(Validate, Propose) - ); + L1ProviderContentBuilder::new().with_state(ProviderState::Pending).build_into_l1_provider(); + + // Test all single-height combinations. + const SESSION_TYPES: [SessionState; 2] = [ProposeSession, ValidateSession]; + for (session_1, session_2) in SESSION_TYPES.into_iter().cartesian_product(SESSION_TYPES) { + l1_provider.start_block(BlockNumber(1), session_1).unwrap(); + l1_provider.start_block(BlockNumber(1), session_2).unwrap(); + } } diff --git a/crates/starknet_l1_provider/src/lib.rs b/crates/starknet_l1_provider/src/lib.rs index 8dbbb8c3ad..e6f8395755 100644 --- a/crates/starknet_l1_provider/src/lib.rs +++ b/crates/starknet_l1_provider/src/lib.rs @@ -23,8 +23,7 @@ use papyrus_config::converters::deserialize_milliseconds_to_duration; use papyrus_config::dumping::{ser_param, SerializeConfig}; use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; use serde::{Deserialize, Serialize}; -use starknet_l1_provider_types::errors::L1ProviderError; -use starknet_l1_provider_types::L1ProviderResult; +use starknet_l1_provider_types::SessionState; use validator::Validate; #[cfg(test)] @@ -42,24 +41,6 @@ pub enum ProviderState { } impl ProviderState { - fn transition_to_propose(self) -> L1ProviderResult { - match self { - ProviderState::Pending => Ok(ProviderState::Propose), - _ => Err(L1ProviderError::unexpected_transition(self, ProviderState::Propose)), - } - } - - fn transition_to_validate(self) -> L1ProviderResult { - match self { - ProviderState::Pending => Ok(ProviderState::Validate), - _ => Err(L1ProviderError::unexpected_transition(self, ProviderState::Validate)), - } - } - - fn _transition_to_pending(self) -> L1ProviderResult { - todo!() - } - pub fn as_str(&self) -> &str { match self { ProviderState::Pending => "Pending", @@ -70,6 +51,15 @@ impl ProviderState { } } +impl From for ProviderState { + fn from(state: SessionState) -> Self { + match state { + SessionState::Propose => ProviderState::Propose, + SessionState::Validate => ProviderState::Validate, + } + } +} + impl std::fmt::Display for ProviderState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) diff --git a/crates/starknet_l1_provider/src/soft_delete_index_map.rs b/crates/starknet_l1_provider/src/soft_delete_index_map.rs index f369fb4a65..f638445803 100644 --- a/crates/starknet_l1_provider/src/soft_delete_index_map.rs +++ b/crates/starknet_l1_provider/src/soft_delete_index_map.rs @@ -69,7 +69,7 @@ impl SoftDeleteIndexMap { // every new tx is inserted to the map with key counter++ and the counter is not reduced // when removing entries. Once the counter reaches u32::MAX/2 we recreate the DS in Theta(n). pub fn _commit(&mut self, tx_hashes: &[TransactionHash]) -> Vec { - self._rollback_staging(); + self.rollback_staging(); let tx_hashes: HashSet<_> = tx_hashes.iter().copied().collect(); if tx_hashes.is_empty() { return Vec::new(); @@ -84,7 +84,7 @@ impl SoftDeleteIndexMap { } /// Rolls back all staged transactions, converting them to unstaged. - pub fn _rollback_staging(&mut self) { + pub fn rollback_staging(&mut self) { for tx_hash in self.staged_txs.drain() { self.txs.entry(tx_hash).and_modify(|entry| entry.set_state(TxState::Unstaged)); } diff --git a/crates/starknet_l1_provider/src/transaction_manager.rs b/crates/starknet_l1_provider/src/transaction_manager.rs index 33c92635f1..248aac01b8 100644 --- a/crates/starknet_l1_provider/src/transaction_manager.rs +++ b/crates/starknet_l1_provider/src/transaction_manager.rs @@ -12,6 +12,10 @@ pub struct TransactionManager { } impl TransactionManager { + pub fn start_block(&mut self) { + self.txs.rollback_staging(); + } + pub fn get_txs(&mut self, n_txs: usize) -> Vec { let mut txs = Vec::with_capacity(n_txs); diff --git a/crates/starknet_l1_provider_types/src/lib.rs b/crates/starknet_l1_provider_types/src/lib.rs index a20cb4cbdf..735936d9fb 100644 --- a/crates/starknet_l1_provider_types/src/lib.rs +++ b/crates/starknet_l1_provider_types/src/lib.rs @@ -108,3 +108,9 @@ pub enum Event { TransactionCancellationStarted(L1Event), TransactionConsumed(L1Event), } + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum SessionState { + Propose, + Validate, +}