diff --git a/crates/full-node/sov-sequencer/src/preferred/initialization.rs b/crates/full-node/sov-sequencer/src/preferred/initialization.rs index e94d098757..e55082c7fe 100644 --- a/crates/full-node/sov-sequencer/src/preferred/initialization.rs +++ b/crates/full-node/sov-sequencer/src/preferred/initialization.rs @@ -4,6 +4,7 @@ use crate::preferred::db::SequencerRole; use anyhow::Context; use anyhow::Result; use sov_db::ledger_db::LedgerDb; +use sov_modules_api::capabilities::SequencerRemuneration; use sov_modules_api::rest::StateUpdateReceiver; use std::net::SocketAddr; use std::path::Path; @@ -64,6 +65,10 @@ where .await .context("Sequencer must have DaService configured with submit support")?; + // Early check: verify that this node's DA signer matches the preferred + // sequencer registered in the runtime. + Self::check_runtime_address_match(&latest_state_update, da_address)?; + debug!( ?latest_state_update, %da_address, @@ -277,6 +282,35 @@ where Ok((seq, handles)) } + fn check_runtime_address_match( + latest_state_update: &StateUpdateInfo<::Storage>, + da_address: <::Da as DaSpec>::Address, + ) -> anyhow::Result<()> { + let mut runtime: Rt = Default::default(); + let mut checkpoint = + StateCheckpoint::new(latest_state_update.storage.clone(), &runtime.kernel(), None); + let registry_preferred = runtime + .sequencer_remuneration() + .preferred_sequencer(&mut checkpoint); + match registry_preferred { + Some(ref expected) if *expected == da_address => Ok(()), + Some(expected) => { + anyhow::bail!( + "DA address mismatch: this node's DaService signer address is {da_address}, \ + but the preferred sequencer DA address in the sequencer registry is {expected}. \ + Check your DA service configuration." + ); + } + None => { + anyhow::bail!( + "No preferred sequencer is registered in the sequencer registry, \ + but this node is configured as a preferred sequencer. \ + Check the sequencer registry genesis configuration." + ); + } + } + } + fn api_state( storage: S::Storage, ) -> ( @@ -285,9 +319,9 @@ where ) { let mut runtime: Rt = Default::default(); assert!( - accepts_preferred_batches(runtime.blob_selector()), - "Attempting to use preferred sequencer with an incompatible rollup. Set your sequencer config to `standard` in your rollup's config.toml file or change your kernel to be compatible with soft confirmations." - ); + accepts_preferred_batches(runtime.blob_selector()), + "Attempting to use preferred sequencer with an incompatible rollup. Set your sequencer config to `standard` in your rollup's config.toml file or change your kernel to be compatible with soft confirmations." + ); let checkpoint = StateCheckpoint::new(storage, &runtime.kernel(), None); // Preferred sequencer deliberately treats the latest available slot as finalized // when initializing API state (soft-confirmation semantics). diff --git a/crates/full-node/sov-sequencer/tests/integration/preferred_end_to_end.rs b/crates/full-node/sov-sequencer/tests/integration/preferred_end_to_end.rs index 57c8e0e9ca..9a58ee11bb 100644 --- a/crates/full-node/sov-sequencer/tests/integration/preferred_end_to_end.rs +++ b/crates/full-node/sov-sequencer/tests/integration/preferred_end_to_end.rs @@ -33,7 +33,9 @@ use sov_rollup_interface::node::da::DaService; use sov_rollup_interface::node::ledger_api::IncludeChildren; use sov_sequencer::{SequencerKindConfig, StateUpdateNotification}; use sov_test_modules::hooks_count::HooksCount; -use sov_test_utils::runtime::genesis::optimistic::HighLevelOptimisticGenesisConfig; +use sov_test_utils::runtime::genesis::optimistic::{ + HighLevelOptimisticGenesisConfig, MinimalOptimisticGenesisConfig, +}; use sov_test_utils::test_rollup::FullNodeBlueprint; use sov_test_utils::test_rollup::StoragePath; use sov_test_utils::test_rollup::{GenesisSource, RollupBuilder, RollupProverConfig, TestRollup}; @@ -3725,6 +3727,74 @@ fn tx_assert_state_root( encode_call::>(key, nonce, &msg) } +#[tokio::test(flavor = "multi_thread")] +#[should_panic(expected = "DA address mismatch")] +async fn preferred_sequencer_fails_on_da_address_mismatch() { + let genesis_config = HighLevelOptimisticGenesisConfig::generate(); + let genesis_params = GenesisParams { + runtime: as Runtime>::GenesisConfig::from_minimal_config( + genesis_config.into(), + ValueSetterConfig { + admin: sov_modules_api::Address::from([0; 28]), + }, + (), + PaymasterConfig::default(), + (), + (), + ), + }; + + RollupBuilder::::new( + GenesisSource::CustomParams(genesis_params), + DEFAULT_BLOCK_PRODUCING_CONFIG, + 0, + ) + .set_config(|conf| { + conf.sequencer_config = SequencerKindConfig::Preferred(Default::default()); + }) + .start() + .await + .unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +#[should_panic(expected = "No preferred sequencer is registered in the sequencer registry")] +async fn preferred_sequencer_fails_when_no_preferred_sequencer_in_registry() { + let genesis_config = HighLevelOptimisticGenesisConfig::generate(); + let seq_da_address = HighLevelOptimisticGenesisConfig::::sequencer_da_addr(); + let mut minimal: MinimalOptimisticGenesisConfig = genesis_config.into(); + minimal + .config + .sequencer_registry + .sequencer_config + .is_preferred_sequencer = false; + let genesis_params = GenesisParams { + runtime: as Runtime>::GenesisConfig::from_minimal_config( + minimal, + ValueSetterConfig { + admin: sov_modules_api::Address::from([0; 28]), + }, + (), + PaymasterConfig::default(), + (), + (), + ), + }; + + RollupBuilder::::new( + GenesisSource::CustomParams(genesis_params), + DEFAULT_BLOCK_PRODUCING_CONFIG, + 0, + ) + .set_config(|conf| { + conf.sequencer_config = SequencerKindConfig::Preferred(Default::default()); + }) + .set_da_config(|c| c.sender_address = seq_da_address) + .start() + .await + .unwrap(); +} + mod tests_with_basic_kernel { use sov_modules_stf_blueprint::GenesisParams; use sov_test_utils::test_rollup::{GenesisSource, RollupBuilder}; @@ -3745,6 +3815,7 @@ mod tests_with_basic_kernel { )] async fn preferred_sequencer_panics_with_basic_kernel() { let genesis_config = HighLevelOptimisticGenesisConfig::generate(); + let seq_da_address = HighLevelOptimisticGenesisConfig::::sequencer_da_addr(); let genesis_params = GenesisParams { runtime: GenesisConfig::from_minimal_config(genesis_config.into()), }; @@ -3758,6 +3829,7 @@ mod tests_with_basic_kernel { conf.sequencer_config = sov_sequencer::SequencerKindConfig::Preferred(Default::default()); }) + .set_da_config(|c| c.sender_address = seq_da_address) .start() .await .unwrap();