diff --git a/Cargo.lock b/Cargo.lock index 6c1dd252..0950bca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6977,6 +6977,7 @@ dependencies = [ "sharp-service", "starknet", "starknet-core 0.9.0", + "starknet-da-client", "starknet-os", "starknet-settlement-client", "strum 0.26.3", @@ -9518,6 +9519,36 @@ dependencies = [ "starknet-types-core", ] +[[package]] +name = "starknet-da-client" +version = "0.1.0" +dependencies = [ + "alloy 0.1.0", + "async-trait", + "c-kzg", + "color-eyre", + "da-client-interface", + "dotenvy", + "mockall", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", + "reqwest 0.12.7", + "rstest 0.22.0", + "serde", + "starknet", + "tokio", + "tokio-test", + "tracing", + "tracing-core", + "tracing-opentelemetry", + "tracing-subscriber", + "url", + "utils", +] + [[package]] name = "starknet-ff" version = "0.3.7" diff --git a/Cargo.toml b/Cargo.toml index ea9ac4f8..d1d5447f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/orchestrator", "crates/da-clients/da-client-interface", "crates/da-clients/ethereum", + "crates/da-clients/starknet", "crates/prover-clients/prover-client-interface", "crates/prover-clients/gps-fact-checker", "crates/prover-clients/sharp-service", @@ -147,6 +148,7 @@ majin-blob-types = { git = "https://github.com/AbdelStark/majin-blob", branch = # Project da-client-interface = { path = "crates/da-clients/da-client-interface" } ethereum-da-client = { path = "crates/da-clients/ethereum" } +starknet-da-client = { path = "crates/da-clients/starknet" } settlement-client-interface = { path = "crates/settlement-clients/settlement-client-interface" } ethereum-settlement-client = { path = "crates/settlement-clients/ethereum" } diff --git a/crates/da-clients/starknet/Cargo.toml b/crates/da-clients/starknet/Cargo.toml new file mode 100644 index 00000000..4729c688 --- /dev/null +++ b/crates/da-clients/starknet/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "starknet-da-client" +version.workspace = true +edition.workspace = true + +[dependencies] +# TODO: update this to the workspace +alloy = { git = "https://github.com/alloy-rs/alloy", rev = "68952c0", features = [ + "consensus", + "providers", + "rpc-client", + "transport-http", + "network", + "eips", + "signers", + "signer-wallet", +] } +async-trait = { workspace = true } +c-kzg = { workspace = true } +color-eyre = { workspace = true } +da-client-interface = { workspace = true } +dotenvy.workspace = true +mockall = { workspace = true } +reqwest = { workspace = true } +rstest = { workspace = true } +serde = { workspace = true, features = ["derive"] } +starknet = { workspace = true } +tokio = { workspace = true } +url = { workspace = true } +utils = { workspace = true } + +#Instrumentation +opentelemetry = { workspace = true, features = ["metrics", "logs"] } +opentelemetry-appender-tracing = { workspace = true, default-features = false } +opentelemetry-otlp = { workspace = true, features = [ + "tonic", + "metrics", + "logs", +] } +opentelemetry-semantic-conventions = { workspace = true } +opentelemetry_sdk = { workspace = true, features = ["rt-tokio", "logs"] } +tracing = { workspace = true } +tracing-core = { workspace = true, default-features = false } +tracing-opentelemetry = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } + + +[dev-dependencies] +tokio-test = "*" diff --git a/crates/da-clients/starknet/src/lib.rs b/crates/da-clients/starknet/src/lib.rs new file mode 100644 index 00000000..a44a2314 --- /dev/null +++ b/crates/da-clients/starknet/src/lib.rs @@ -0,0 +1,56 @@ +#![allow(missing_docs)] +#![allow(clippy::missing_docs_in_private_items)] + +use std::str::FromStr; +use std::sync::Arc; + +use async_trait::async_trait; +use color_eyre::Result; +use da_client_interface::{DaClient, DaVerificationStatus}; +use mockall::automock; +use mockall::predicate::*; +use serde::{Deserialize, Serialize}; +use starknet::providers::jsonrpc::HttpTransport; +use starknet::providers::JsonRpcClient; +use url::Url; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StarknetDaValidatedArgs { + pub starknet_da_rpc_url: Url, +} + +pub struct StarknetDaClient { + #[allow(dead_code)] + provider: Arc>, +} + +impl StarknetDaClient { + pub async fn new_with_args(starknet_da_params: &StarknetDaValidatedArgs) -> Self { + let client = JsonRpcClient::new(HttpTransport::new( + Url::from_str(starknet_da_params.starknet_da_rpc_url.as_str()).expect("invalid url provided"), + )); + Self { provider: Arc::new(client) } + } +} + +#[automock] +#[async_trait] +impl DaClient for StarknetDaClient { + async fn publish_state_diff(&self, _state_diff: Vec>, _to: &[u8; 32]) -> Result { + // Here in case of starknet we are not publishing the state diff because we are doing it all + // together in proving and update_state job. So we don't need to send anything here. + Ok("NA".to_string()) + } + + async fn verify_inclusion(&self, _external_id: &str) -> Result { + Ok(DaVerificationStatus::Verified) + } + + async fn max_blob_per_txn(&self) -> u64 { + 6 + } + + async fn max_bytes_per_blob(&self) -> u64 { + 131072 + } +} diff --git a/crates/orchestrator/Cargo.toml b/crates/orchestrator/Cargo.toml index e52076a0..36b1fcdb 100644 --- a/crates/orchestrator/Cargo.toml +++ b/crates/orchestrator/Cargo.toml @@ -37,6 +37,7 @@ color-eyre = { workspace = true } da-client-interface = { workspace = true } dotenvy = { workspace = true } ethereum-da-client = { workspace = true, optional = true } +starknet-da-client = { workspace = true, optional = true } ethereum-settlement-client = { workspace = true } futures = { workspace = true } hex = { workspace = true } @@ -94,8 +95,9 @@ tracing-opentelemetry = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } [features] -default = ["ethereum", "with_mongodb", "with_sqs"] +default = ["ethereum", "with_mongodb", "with_sqs", "starknet"] ethereum = ["ethereum-da-client"] +starknet = ["starknet-da-client"] with_mongodb = ["mongodb"] with_sqs = ["omniqueue"] testing = [] diff --git a/crates/orchestrator/src/cli/da/mod.rs b/crates/orchestrator/src/cli/da/mod.rs index 54c98496..f530c3ae 100644 --- a/crates/orchestrator/src/cli/da/mod.rs +++ b/crates/orchestrator/src/cli/da/mod.rs @@ -1,8 +1,11 @@ use ethereum_da_client::EthereumDaValidatedArgs; +use starknet_da_client::StarknetDaValidatedArgs; pub mod ethereum; +pub mod starknet; #[derive(Debug, Clone)] pub enum DaValidatedArgs { Ethereum(EthereumDaValidatedArgs), + Starknet(StarknetDaValidatedArgs), } diff --git a/crates/orchestrator/src/cli/da/starknet.rs b/crates/orchestrator/src/cli/da/starknet.rs new file mode 100644 index 00000000..f84df60f --- /dev/null +++ b/crates/orchestrator/src/cli/da/starknet.rs @@ -0,0 +1,15 @@ +use clap::Args; +use url::Url; + +/// Parameters used to config Starknet. +#[derive(Debug, Clone, Args)] +#[group(requires_all = ["starknet_da_rpc_url"])] +pub struct StarknetDaCliArgs { + /// Use the Starknet DA layer. + #[arg(long)] + pub da_on_starknet: bool, + + /// The RPC URL of the Ethereum node. + #[arg(env = "MADARA_ORCHESTRATOR_STARKNET_DA_RPC_URL", long)] + pub starknet_da_rpc_url: Option, +} diff --git a/crates/orchestrator/src/cli/mod.rs b/crates/orchestrator/src/cli/mod.rs index b17d9369..d68d5b85 100644 --- a/crates/orchestrator/src/cli/mod.rs +++ b/crates/orchestrator/src/cli/mod.rs @@ -95,7 +95,7 @@ pub enum Commands { ), group( ArgGroup::new("da_layer") - .args(&["da_on_ethereum"]) + .args(&["da_on_ethereum", "da_on_starknet"]) .required(true) .multiple(false) ), @@ -136,6 +136,9 @@ pub struct RunCmd { #[clap(flatten)] pub ethereum_da_args: da::ethereum::EthereumDaCliArgs, + #[clap(flatten)] + pub starknet_da_args: da::starknet::StarknetDaCliArgs, + // Prover #[clap(flatten)] pub sharp_args: prover::sharp::SharpCliArgs, @@ -182,7 +185,7 @@ impl RunCmd { } pub fn validate_da_params(&self) -> Result { - validate_params::validate_da_params(&self.ethereum_da_args) + validate_params::validate_da_params(&self.ethereum_da_args, &self.starknet_da_args) } pub fn validate_settlement_params(&self) -> Result { @@ -306,6 +309,7 @@ pub mod validate_params { use ethereum_da_client::EthereumDaValidatedArgs; use ethereum_settlement_client::EthereumSettlementValidatedArgs; use sharp_service::SharpValidatedArgs; + use starknet_da_client::StarknetDaValidatedArgs; use starknet_settlement_client::StarknetSettlementValidatedArgs; use url::Url; @@ -334,6 +338,7 @@ pub mod validate_params { use super::storage::aws_s3::AWSS3CliArgs; use super::storage::StorageValidatedArgs; use crate::alerts::aws_sns::AWSSNSValidatedArgs; + use crate::cli::da::starknet::StarknetDaCliArgs; use crate::cli::prover_layout::ProverLayoutCliArgs; use crate::config::ServiceParams; use crate::cron::event_bridge::AWSEventBridgeValidatedArgs; @@ -451,16 +456,25 @@ pub mod validate_params { } } - pub(crate) fn validate_da_params(ethereum_da_args: &EthereumDaCliArgs) -> Result { - if ethereum_da_args.da_on_ethereum { - Ok(DaValidatedArgs::Ethereum(EthereumDaValidatedArgs { + pub(crate) fn validate_da_params( + ethereum_da_args: &EthereumDaCliArgs, + starknet_da_args: &StarknetDaCliArgs, + ) -> Result { + match (ethereum_da_args.da_on_ethereum, starknet_da_args.da_on_starknet) { + (true, true) => Err("Cannot use both Ethereum and Starknet as DA Layer".to_string()), + (true, false) => Ok(DaValidatedArgs::Ethereum(EthereumDaValidatedArgs { ethereum_da_rpc_url: ethereum_da_args .ethereum_da_rpc_url .clone() .expect("Ethereum DA RPC URL is required"), - })) - } else { - Err("Only Ethereum is supported as of now".to_string()) + })), + (false, true) => Ok(DaValidatedArgs::Starknet(StarknetDaValidatedArgs { + starknet_da_rpc_url: starknet_da_args + .starknet_da_rpc_url + .clone() + .expect("Starknet DA RPC URL is required"), + })), + (false, false) => Err("Settlement layer is required".to_string()), } } @@ -615,7 +629,7 @@ pub mod validate_params { } pub(crate) fn validate_snos_params(snos_args: &SNOSCliArgs) -> Result { - Ok(SNOSParams { rpc_for_snos: snos_args.rpc_for_snos.clone() }) + Ok(SNOSParams { rpc_for_snos: snos_args.rpc_for_snos.clone(), snos_full_output: snos_args.snos_full_output }) } #[cfg(test)] @@ -627,6 +641,7 @@ pub mod validate_params { use crate::cli::alert::aws_sns::AWSSNSCliArgs; use crate::cli::cron::event_bridge::AWSEventBridgeCliArgs; use crate::cli::da::ethereum::EthereumDaCliArgs; + use crate::cli::da::starknet::StarknetDaCliArgs; use crate::cli::database::mongodb::MongoDBCliArgs; use crate::cli::instrumentation::InstrumentationCliArgs; use crate::cli::prover::atlantic::AtlanticCliArgs; @@ -756,15 +771,21 @@ pub mod validate_params { } #[rstest] - #[case(true)] - #[case(false)] - fn test_validate_da_params(#[case] is_ethereum: bool) { + #[case(true, false)] + #[case(false, true)] + #[case(false, false)] + #[case(true, true)] + fn test_validate_da_params(#[case] is_ethereum: bool, #[case] is_starknet: bool) { let ethereum_da_args: EthereumDaCliArgs = EthereumDaCliArgs { da_on_ethereum: is_ethereum, ethereum_da_rpc_url: Some(Url::parse("http://localhost:8545").unwrap()), }; - let da_params = validate_da_params(ðereum_da_args); - if is_ethereum { + let starknet_da_args: StarknetDaCliArgs = StarknetDaCliArgs { + da_on_starknet: is_starknet, + starknet_da_rpc_url: Some(Url::parse("http://localhost:8545").unwrap()), + }; + let da_params = validate_da_params(ðereum_da_args, &starknet_da_args); + if is_ethereum ^ is_starknet { assert!(da_params.is_ok()); } else { assert!(da_params.is_err()); @@ -879,7 +900,8 @@ pub mod validate_params { #[rstest] fn test_validate_snos_params() { - let snos_args: SNOSCliArgs = SNOSCliArgs { rpc_for_snos: Url::parse("http://localhost:8545").unwrap() }; + let snos_args: SNOSCliArgs = + SNOSCliArgs { rpc_for_snos: Url::parse("http://localhost:8545").unwrap(), snos_full_output: true }; let snos_params = validate_snos_params(&snos_args); assert!(snos_params.is_ok()); } diff --git a/crates/orchestrator/src/cli/snos.rs b/crates/orchestrator/src/cli/snos.rs index 12851ac8..a72b9c6e 100644 --- a/crates/orchestrator/src/cli/snos.rs +++ b/crates/orchestrator/src/cli/snos.rs @@ -4,6 +4,8 @@ use url::Url; #[derive(Debug, Clone, Args)] #[group(requires_all = ["rpc_for_snos"])] pub struct SNOSCliArgs { + pub snos_full_output: bool, + /// The RPC URL for SNOS. #[arg(env = "MADARA_ORCHESTRATOR_RPC_FOR_SNOS", long)] pub rpc_for_snos: Url, @@ -11,5 +13,6 @@ pub struct SNOSCliArgs { #[derive(Debug, Clone)] pub struct SNOSParams { + pub snos_full_output: bool, pub rpc_for_snos: Url, } diff --git a/crates/orchestrator/src/config.rs b/crates/orchestrator/src/config.rs index 0027f842..0f117f87 100644 --- a/crates/orchestrator/src/config.rs +++ b/crates/orchestrator/src/config.rs @@ -17,6 +17,7 @@ use settlement_client_interface::SettlementClient; use sharp_service::SharpProverService; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Url}; +use starknet_da_client::StarknetDaClient; use starknet_settlement_client::StarknetSettlementClient; use crate::alerts::aws_sns::AWSSNS; @@ -288,6 +289,9 @@ pub async fn build_da_client(da_params: &DaValidatedArgs) -> Box { Box::new(EthereumDaClient::new_with_args(ethereum_da_params).await) } + DaValidatedArgs::Starknet(starknet_da_params) => { + Box::new(StarknetDaClient::new_with_args(starknet_da_params).await) + } } } diff --git a/crates/orchestrator/src/jobs/snos_job/fact_info.rs b/crates/orchestrator/src/jobs/snos_job/fact_info.rs index 147c6797..60464c48 100644 --- a/crates/orchestrator/src/jobs/snos_job/fact_info.rs +++ b/crates/orchestrator/src/jobs/snos_job/fact_info.rs @@ -2,6 +2,9 @@ //! //! Port of https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/bootloaders/generate_fact.py +use std::ops::Add; +use std::str::FromStr; + use alloy::primitives::{keccak256, B256}; use cairo_vm::program_hash::compute_program_hash_chain; use cairo_vm::types::builtin_name::BuiltinName; @@ -9,6 +12,7 @@ use cairo_vm::types::relocatable::MaybeRelocatable; use cairo_vm::vm::runners::cairo_pie::CairoPie; use cairo_vm::Felt252; use starknet::core::types::Felt; +use starknet_os::crypto::poseidon::poseidon_hash_many_bytes; // use starknet::core::types::FieldElement; use super::error::FactError; @@ -26,6 +30,84 @@ pub struct FactInfo { pub fact: B256, } +#[derive(Debug)] +pub struct OnChainData { + pub on_chain_data_hash: B256, + pub on_chain_data_size: usize, +} + +pub struct BootLoaderOutput { + pub one_felt: Felt, + pub program_output_len_add_2: Felt, + pub program_hash: Felt, + pub program_output: Vec, +} + +impl BootLoaderOutput { + pub fn to_byte_nested_vec(&self) -> Vec> { + let mut result = Vec::new(); + result.push(self.one_felt.to_bytes_be().to_vec()); + result.push(self.program_output_len_add_2.to_bytes_be().to_vec()); + result.push(self.program_hash.to_bytes_be().to_vec()); + for felt in &self.program_output { + result.push(felt.to_bytes_be().to_vec()); + } + result + } +} + +pub fn get_fact_l2(cairo_pie: &CairoPie, program_hash: Option) -> color_eyre::Result { + let program_hash = match program_hash { + Some(hash) => hash, + None => Felt::from_bytes_be( + &compute_program_hash_chain(&cairo_pie.metadata.program, BOOTLOADER_VERSION) + .map_err(|e| { + tracing::error!( + log_type = "FactInfo", + category = "fact_info", + function_type = "get_fact_info", + "Failed to compute program hash: {}", + e + ); + FactError::ProgramHashCompute(e.to_string()) + })? + .to_bytes_be(), + ), + }; + let program_output = get_program_output(cairo_pie)?; + let boot_loader_output: BootLoaderOutput = BootLoaderOutput { + one_felt: Felt::ONE, + program_output_len_add_2: Felt::from(program_output.len()).add(2), + program_hash, + program_output, + }; + let boot_loader_output_slice_vec = boot_loader_output.to_byte_nested_vec(); + let boot_loader_output_hash_vec = + poseidon_hash_many_bytes(&boot_loader_output_slice_vec.iter().map(|v| v.as_slice()).collect::>())? + .to_vec(); + let boot_loader_output_hash = boot_loader_output_hash_vec.as_slice(); + + // Bootloader Program Hash : 0x5ab580b04e3532b6b18f81cfa654a05e29dd8e2352d88df1e765a84072db07 + // taken from the code sent by integrity team. + let boot_loader_program_hash_bytes = + Felt::from_str("0x5ab580b04e3532b6b18f81cfa654a05e29dd8e2352d88df1e765a84072db07")?.to_bytes_be(); + let boot_loader_program_hash = boot_loader_program_hash_bytes.as_slice(); + + let fact_hash = poseidon_hash_many_bytes(vec![boot_loader_program_hash, boot_loader_output_hash].as_slice())?; + + Ok(B256::from_slice(fact_hash.to_vec().as_slice())) +} + +pub fn build_on_chain_data(cairo_pie: &CairoPie) -> color_eyre::Result { + let program_output = get_program_output(cairo_pie)?; + let fact_topology = get_fact_topology(cairo_pie, program_output.len())?; + let fact_root = generate_merkle_root(&program_output, &fact_topology)?; + + let da_child = fact_root.children.last().expect("fact_root is empty"); + + Ok(OnChainData { on_chain_data_hash: da_child.node_hash, on_chain_data_size: da_child.page_size }) +} + pub fn get_fact_info(cairo_pie: &CairoPie, program_hash: Option) -> Result { tracing::debug!( log_type = "FactInfo", diff --git a/crates/orchestrator/src/jobs/snos_job/mod.rs b/crates/orchestrator/src/jobs/snos_job/mod.rs index c82d48c3..5a9892bf 100644 --- a/crates/orchestrator/src/jobs/snos_job/mod.rs +++ b/crates/orchestrator/src/jobs/snos_job/mod.rs @@ -106,11 +106,18 @@ impl Job for SnosJob { let snos_url = config.snos_config().rpc_for_snos.to_string(); let snos_url = snos_url.trim_end_matches('/'); tracing::debug!(job_id = %job.internal_id, "Calling prove_block function"); - let (cairo_pie, snos_output) = - prove_block(COMPILED_OS, block_number, snos_url, LayoutName::all_cairo, false).await.map_err(|e| { - tracing::error!(job_id = %job.internal_id, error = %e, "SNOS execution failed"); - SnosError::SnosExecutionError { internal_id: job.internal_id.clone(), message: e.to_string() } - })?; + let (cairo_pie, snos_output) = prove_block( + COMPILED_OS, + block_number, + snos_url, + LayoutName::all_cairo, + config.snos_config().snos_full_output, + ) + .await + .map_err(|e| { + tracing::error!(job_id = %job.internal_id, error = %e, "SNOS execution failed"); + SnosError::SnosExecutionError { internal_id: job.internal_id.clone(), message: e.to_string() } + })?; tracing::debug!(job_id = %job.internal_id, "prove_block function completed successfully"); let fact_info = get_fact_info(&cairo_pie, None)?; diff --git a/crates/orchestrator/src/tests/config.rs b/crates/orchestrator/src/tests/config.rs index e46fdc23..d4fe9495 100644 --- a/crates/orchestrator/src/tests/config.rs +++ b/crates/orchestrator/src/tests/config.rs @@ -534,6 +534,7 @@ fn get_env_params() -> EnvParams { }); let snos_config = SNOSParams { + snos_full_output: false, rpc_for_snos: Url::parse(&get_env_var_or_panic("MADARA_ORCHESTRATOR_RPC_FOR_SNOS")) .expect("Failed to parse MADARA_ORCHESTRATOR_RPC_FOR_SNOS"), }; diff --git a/crates/orchestrator/src/tests/jobs/snos_job/mod.rs b/crates/orchestrator/src/tests/jobs/snos_job/mod.rs index 8a48f86e..3ffff3d3 100644 --- a/crates/orchestrator/src/tests/jobs/snos_job/mod.rs +++ b/crates/orchestrator/src/tests/jobs/snos_job/mod.rs @@ -1,8 +1,10 @@ use std::collections::HashMap; use std::sync::Arc; +use cairo_vm::types::layout_name::LayoutName; use cairo_vm::vm::runners::cairo_pie::CairoPie; use chrono::{SubsecRound, Utc}; +use prove_block::prove_block; use rstest::*; use starknet_os::io::output::StarknetOsOutput; use url::Url; @@ -10,6 +12,7 @@ use uuid::Uuid; use crate::constants::{CAIRO_PIE_FILE_NAME, SNOS_OUTPUT_FILE_NAME}; use crate::jobs::constants::JOB_METADATA_SNOS_BLOCK; +use crate::jobs::snos_job::fact_info::get_fact_l2; use crate::jobs::snos_job::SnosJob; use crate::jobs::types::{JobItem, JobStatus, JobType, JobVerificationStatus}; use crate::jobs::Job; @@ -97,3 +100,34 @@ async fn test_process_job() -> color_eyre::Result<()> { Ok(()) } + +pub const COMPILED_OS: &[u8] = include_bytes!("../../../../../../build/os_latest.json"); + +#[rstest] +#[tokio::test(flavor = "multi_thread")] +// This test case is formed as follows : +// - We took the block from starknet sepolia +// - We calculated the expected_fact by calling the integrity l2 fact hash calculation endpoint +#[case(30000, "0x04e1bc5781a9577bf181a01fdc6ec270d99cd38edea9521cf9464bff94010531")] +async fn test_prove_block_for_l3_output( + #[case] block_number: u64, + #[case] expected_fact: &str, +) -> color_eyre::Result<()> { + let pathfinder_url: String = match std::env::var(SNOS_PATHFINDER_RPC_URL_ENV) { + Ok(url) => url, + Err(_) => { + println!("Ignoring test: {} environment variable is not set", SNOS_PATHFINDER_RPC_URL_ENV); + return Ok(()); + } + }; + let pathfinder_url = pathfinder_url.trim_end_matches('/'); + + let (cairo_pie, _snos_output) = prove_block(COMPILED_OS, block_number, pathfinder_url, LayoutName::all_cairo, true) + .await + .expect("Unable to run snos"); + + let fact_info = get_fact_l2(&cairo_pie, None)?; + assert_eq!(fact_info.to_string(), expected_fact); + + Ok(()) +} diff --git a/e2e-tests/tests.rs b/e2e-tests/tests.rs index 9430109d..4adac029 100644 --- a/e2e-tests/tests.rs +++ b/e2e-tests/tests.rs @@ -237,6 +237,13 @@ async fn test_orchestrator_workflow(#[case] l2_block_number: String) { assert!(test_result.is_ok(), "After Update State Job state DB state assertion failed."); } +#[rstest] +// This is a starknet sepolia block. Block description : +// - This block is already proved on starknet sepolia +#[case("30000".to_string())] +#[tokio::test] +async fn test_orchestration_workflow_l3(#[case] _l3_block_number: String) {} + /// Function that adds rules to tests for localstack /// This can be removed after https://github.com/localstack/localstack/issues/9861 is closed async fn create_event_bridge_rule(trigger_rule_name: &String, target_queue_name: &String) -> color_eyre::Result<()> {