2026-01-29 22:20:42 +03:00
|
|
|
use std::{net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2025-08-01 18:32:30 +03:00
|
|
|
use actix_web::dev::ServerHandle;
|
2026-01-29 22:20:42 +03:00
|
|
|
use anyhow::{Context as _, Result};
|
2024-11-28 22:05:14 +02:00
|
|
|
use clap::Parser;
|
2025-04-16 16:17:53 +03:00
|
|
|
use common::rpc_primitives::RpcConfig;
|
2026-01-29 22:20:42 +03:00
|
|
|
use futures::{FutureExt as _, never::Never};
|
|
|
|
|
use log::{error, info, warn};
|
|
|
|
|
use sequencer_core::{
|
|
|
|
|
SequencerCore, block_settlement_client::BlockSettlementClientTrait as _,
|
|
|
|
|
config::SequencerConfig,
|
|
|
|
|
};
|
2024-09-30 05:49:46 +03:00
|
|
|
use sequencer_rpc::new_http_server;
|
2025-08-01 18:32:30 +03:00
|
|
|
use tokio::{sync::Mutex, task::JoinHandle};
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2025-04-18 08:14:31 -04:00
|
|
|
pub const RUST_LOG: &str = "RUST_LOG";
|
|
|
|
|
|
2024-11-28 22:05:14 +02:00
|
|
|
#[derive(Parser, Debug)]
|
|
|
|
|
#[clap(version)]
|
|
|
|
|
struct Args {
|
|
|
|
|
/// Path to configs
|
|
|
|
|
home_dir: PathBuf,
|
|
|
|
|
}
|
2024-09-30 05:49:46 +03:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
/// Handle to manage the sequencer and its tasks.
|
|
|
|
|
///
|
|
|
|
|
/// Implements `Drop` to ensure all tasks are aborted and the HTTP server is stopped when dropped.
|
|
|
|
|
pub struct SequencerHandle {
|
|
|
|
|
addr: SocketAddr,
|
|
|
|
|
http_server_handle: ServerHandle,
|
|
|
|
|
main_loop_handle: JoinHandle<Result<Never>>,
|
|
|
|
|
retry_pending_blocks_loop_handle: JoinHandle<Result<Never>>,
|
|
|
|
|
listen_for_bedrock_blocks_loop_handle: JoinHandle<Result<Never>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SequencerHandle {
|
|
|
|
|
/// Runs the sequencer indefinitely, monitoring its tasks.
|
|
|
|
|
///
|
|
|
|
|
/// If no error occurs, this function will never return.
|
|
|
|
|
pub async fn run_forever(&mut self) -> Result<Never> {
|
|
|
|
|
let Self {
|
|
|
|
|
addr: _,
|
|
|
|
|
http_server_handle: _,
|
|
|
|
|
main_loop_handle,
|
|
|
|
|
retry_pending_blocks_loop_handle,
|
|
|
|
|
listen_for_bedrock_blocks_loop_handle,
|
|
|
|
|
} = self;
|
|
|
|
|
|
|
|
|
|
tokio::select! {
|
|
|
|
|
res = main_loop_handle => {
|
|
|
|
|
res
|
|
|
|
|
.context("Main loop task panicked")?
|
|
|
|
|
.context("Main loop exited unexpectedly")
|
|
|
|
|
}
|
|
|
|
|
res = retry_pending_blocks_loop_handle => {
|
|
|
|
|
res
|
|
|
|
|
.context("Retry pending blocks loop task panicked")?
|
|
|
|
|
.context("Retry pending blocks loop exited unexpectedly")
|
|
|
|
|
}
|
|
|
|
|
res = listen_for_bedrock_blocks_loop_handle => {
|
|
|
|
|
res
|
|
|
|
|
.context("Listen for bedrock blocks loop task panicked")?
|
|
|
|
|
.context("Listen for bedrock blocks loop exited unexpectedly")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_finished(&self) -> bool {
|
|
|
|
|
self.main_loop_handle.is_finished()
|
|
|
|
|
|| self.retry_pending_blocks_loop_handle.is_finished()
|
|
|
|
|
|| self.listen_for_bedrock_blocks_loop_handle.is_finished()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn addr(&self) -> SocketAddr {
|
|
|
|
|
self.addr
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for SequencerHandle {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
let Self {
|
|
|
|
|
addr: _,
|
|
|
|
|
http_server_handle,
|
|
|
|
|
main_loop_handle,
|
|
|
|
|
retry_pending_blocks_loop_handle,
|
|
|
|
|
listen_for_bedrock_blocks_loop_handle,
|
|
|
|
|
} = self;
|
|
|
|
|
|
|
|
|
|
main_loop_handle.abort();
|
|
|
|
|
retry_pending_blocks_loop_handle.abort();
|
|
|
|
|
listen_for_bedrock_blocks_loop_handle.abort();
|
|
|
|
|
|
|
|
|
|
// Can't wait here as Drop can't be async, but anyway stop signal should be sent
|
|
|
|
|
http_server_handle.stop(true).now_or_never();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn startup_sequencer(app_config: SequencerConfig) -> Result<SequencerHandle> {
|
|
|
|
|
let block_timeout = Duration::from_millis(app_config.block_create_timeout_millis);
|
|
|
|
|
let retry_pending_blocks_timeout =
|
|
|
|
|
Duration::from_millis(app_config.retry_pending_blocks_timeout_millis);
|
2024-12-29 14:11:47 +02:00
|
|
|
let port = app_config.port;
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
let (sequencer_core, mempool_handle) = SequencerCore::start_from_config(app_config).await;
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2025-08-01 18:32:30 +03:00
|
|
|
info!("Sequencer core set up");
|
2024-11-28 22:05:14 +02:00
|
|
|
|
|
|
|
|
let seq_core_wrapped = Arc::new(Mutex::new(sequencer_core));
|
|
|
|
|
|
2025-12-31 04:02:25 +03:00
|
|
|
let (http_server, addr) = new_http_server(
|
2025-11-18 19:31:03 +03:00
|
|
|
RpcConfig::with_port(port),
|
|
|
|
|
Arc::clone(&seq_core_wrapped),
|
|
|
|
|
mempool_handle,
|
|
|
|
|
)?;
|
2024-09-30 05:49:46 +03:00
|
|
|
info!("HTTP server started");
|
2025-08-01 18:32:30 +03:00
|
|
|
let http_server_handle = http_server.handle();
|
2024-09-30 05:49:46 +03:00
|
|
|
tokio::spawn(http_server);
|
|
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
info!("Starting main sequencer loop");
|
|
|
|
|
let main_loop_handle = tokio::spawn(main_loop(Arc::clone(&seq_core_wrapped), block_timeout));
|
|
|
|
|
|
2026-01-27 13:27:52 -03:00
|
|
|
info!("Starting pending block retry loop");
|
2026-01-29 22:20:42 +03:00
|
|
|
let retry_pending_blocks_loop_handle = tokio::spawn(retry_pending_blocks_loop(
|
|
|
|
|
Arc::clone(&seq_core_wrapped),
|
|
|
|
|
retry_pending_blocks_timeout,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
info!("Starting bedrock block listening loop");
|
|
|
|
|
let listen_for_bedrock_blocks_loop_handle =
|
|
|
|
|
tokio::spawn(listen_for_bedrock_blocks_loop(seq_core_wrapped));
|
|
|
|
|
|
|
|
|
|
Ok(SequencerHandle {
|
|
|
|
|
addr,
|
|
|
|
|
http_server_handle,
|
|
|
|
|
main_loop_handle,
|
|
|
|
|
retry_pending_blocks_loop_handle,
|
|
|
|
|
listen_for_bedrock_blocks_loop_handle,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn main_loop(seq_core: Arc<Mutex<SequencerCore>>, block_timeout: Duration) -> Result<Never> {
|
|
|
|
|
loop {
|
|
|
|
|
tokio::time::sleep(block_timeout).await;
|
|
|
|
|
|
|
|
|
|
info!("Collecting transactions from mempool, block creation");
|
|
|
|
|
|
|
|
|
|
let id = {
|
|
|
|
|
let mut state = seq_core.lock().await;
|
|
|
|
|
|
|
|
|
|
state
|
|
|
|
|
.produce_new_block_and_post_to_settlement_layer()
|
|
|
|
|
.await?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
info!("Block with id {id} created");
|
|
|
|
|
|
|
|
|
|
info!("Waiting for new transactions");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn retry_pending_blocks_loop(
|
|
|
|
|
seq_core: Arc<Mutex<SequencerCore>>,
|
|
|
|
|
retry_pending_blocks_timeout: Duration,
|
|
|
|
|
) -> Result<Never> {
|
|
|
|
|
loop {
|
|
|
|
|
tokio::time::sleep(retry_pending_blocks_timeout).await;
|
|
|
|
|
|
|
|
|
|
let (pending_blocks, block_settlement_client) = {
|
|
|
|
|
let sequencer_core = seq_core.lock().await;
|
|
|
|
|
let client = sequencer_core.block_settlement_client();
|
|
|
|
|
let pending_blocks = sequencer_core
|
|
|
|
|
.get_pending_blocks()
|
|
|
|
|
.expect("Sequencer should be able to retrieve pending blocks");
|
|
|
|
|
(pending_blocks, client)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
info!("Resubmitting {} pending blocks", pending_blocks.len());
|
|
|
|
|
for block in &pending_blocks {
|
2026-02-12 00:01:00 +03:00
|
|
|
// TODO: We could cache the inscribe tx for each pending block to avoid re-creating it
|
|
|
|
|
// on every retry.
|
|
|
|
|
let (tx, _msg_id) = block_settlement_client
|
|
|
|
|
.create_inscribe_tx(block)
|
|
|
|
|
.context("Failed to create inscribe tx for pending block")?;
|
|
|
|
|
|
|
|
|
|
if let Err(e) = block_settlement_client
|
|
|
|
|
.submit_inscribe_tx_to_bedrock(tx)
|
|
|
|
|
.await
|
|
|
|
|
{
|
2026-01-29 22:20:42 +03:00
|
|
|
warn!(
|
2026-02-12 00:01:00 +03:00
|
|
|
"Failed to resubmit block with id {} with error {e:#}",
|
|
|
|
|
block.header.block_id
|
2026-01-29 22:20:42 +03:00
|
|
|
);
|
2026-01-27 13:27:52 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-29 22:20:42 +03:00
|
|
|
}
|
|
|
|
|
}
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
async fn listen_for_bedrock_blocks_loop(seq_core: Arc<Mutex<SequencerCore>>) -> Result<Never> {
|
|
|
|
|
use indexer_service_rpc::RpcClient as _;
|
|
|
|
|
|
|
|
|
|
let indexer_client = seq_core.lock().await.indexer_client();
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
let retry_delay = Duration::from_secs(5);
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
loop {
|
|
|
|
|
// TODO: Subscribe from the first pending block ID?
|
|
|
|
|
let mut subscription = indexer_client
|
|
|
|
|
.subscribe_to_finalized_blocks()
|
|
|
|
|
.await
|
|
|
|
|
.context("Failed to subscribe to finalized blocks")?;
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
while let Some(block_id) = subscription.next().await {
|
|
|
|
|
let block_id = block_id.context("Failed to get next block from subscription")?;
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
info!("Received new L2 block with ID {block_id}");
|
2024-11-28 22:05:14 +02:00
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
seq_core
|
|
|
|
|
.lock()
|
|
|
|
|
.await
|
|
|
|
|
.clean_finalized_blocks_from_db(block_id)
|
|
|
|
|
.with_context(|| {
|
|
|
|
|
format!("Failed to clean finalized blocks from DB for block ID {block_id}")
|
|
|
|
|
})?;
|
2025-08-01 18:32:30 +03:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
warn!(
|
|
|
|
|
"Block subscription closed unexpectedly, reason: {:?}, retrying after {retry_delay:?}",
|
|
|
|
|
subscription.close_reason()
|
|
|
|
|
);
|
|
|
|
|
tokio::time::sleep(retry_delay).await;
|
|
|
|
|
}
|
2025-08-01 18:32:30 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn main_runner() -> Result<()> {
|
|
|
|
|
env_logger::init();
|
|
|
|
|
|
|
|
|
|
let args = Args::parse();
|
|
|
|
|
let Args { home_dir } = args;
|
|
|
|
|
|
2025-12-31 04:02:25 +03:00
|
|
|
let app_config = SequencerConfig::from_path(&home_dir.join("sequencer_config.json"))?;
|
2025-08-01 18:32:30 +03:00
|
|
|
|
|
|
|
|
if let Some(ref rust_log) = app_config.override_rust_log {
|
|
|
|
|
info!("RUST_LOG env var set to {rust_log:?}");
|
|
|
|
|
|
2025-09-04 14:38:41 +03:00
|
|
|
unsafe {
|
|
|
|
|
std::env::set_var(RUST_LOG, rust_log);
|
|
|
|
|
}
|
2024-09-30 05:49:46 +03:00
|
|
|
}
|
2025-08-01 18:32:30 +03:00
|
|
|
|
2025-11-26 00:27:20 +03:00
|
|
|
// ToDo: Add restart on failures
|
2026-01-29 22:20:42 +03:00
|
|
|
let mut sequencer_handle = startup_sequencer(app_config).await?;
|
2026-01-27 13:27:52 -03:00
|
|
|
|
|
|
|
|
info!("Sequencer running. Monitoring concurrent tasks...");
|
|
|
|
|
|
2026-01-29 22:20:42 +03:00
|
|
|
let Err(err) = sequencer_handle.run_forever().await;
|
2026-02-10 00:19:37 +03:00
|
|
|
error!("Sequencer failed: {err:#}");
|
2025-09-03 14:41:01 -03:00
|
|
|
|
2026-01-27 13:27:52 -03:00
|
|
|
info!("Shutting down sequencer...");
|
2025-08-01 18:32:30 +03:00
|
|
|
|
|
|
|
|
Ok(())
|
2024-09-30 05:49:46 +03:00
|
|
|
}
|