use super::EngineTaskExt;
use crate::{
EngineClient, EngineState, EngineSyncStateUpdate, EngineTask, EngineTaskError,
EngineTaskErrorSeverity, Metrics, SynchronizeTask, SynchronizeTaskError,
task_queue::EngineTaskErrors,
};
use alloy_provider::Provider;
use alloy_rpc_types_eth::Transaction;
use kona_genesis::{RollupConfig, SystemConfig};
use kona_protocol::{BlockInfo, L2BlockInfo, OpBlockConversionError, to_system_config};
use kona_sources::{SyncStartError, find_starting_forkchoice};
use op_alloy_consensus::OpTxEnvelope;
use std::{collections::BinaryHeap, sync::Arc};
use thiserror::Error;
use tokio::sync::watch::Sender;
#[derive(Debug)]
pub struct Engine {
state: EngineState,
state_sender: Sender<EngineState>,
task_queue_length: Sender<usize>,
tasks: BinaryHeap<EngineTask>,
}
impl Engine {
pub fn new(
initial_state: EngineState,
state_sender: Sender<EngineState>,
task_queue_length: Sender<usize>,
) -> Self {
Self { state: initial_state, state_sender, task_queue_length, tasks: BinaryHeap::default() }
}
pub const fn state(&self) -> &EngineState {
&self.state
}
pub fn state_subscribe(&self) -> tokio::sync::watch::Receiver<EngineState> {
self.state_sender.subscribe()
}
pub fn queue_length_subscribe(&self) -> tokio::sync::watch::Receiver<usize> {
self.task_queue_length.subscribe()
}
pub fn enqueue(&mut self, task: EngineTask) {
self.tasks.push(task);
self.task_queue_length.send_replace(self.tasks.len());
}
pub async fn reset(
&mut self,
client: Arc<EngineClient>,
config: Arc<RollupConfig>,
) -> Result<(L2BlockInfo, BlockInfo, SystemConfig), EngineResetError> {
self.clear();
let start =
find_starting_forkchoice(&config, client.l1_provider(), client.l2_engine()).await?;
while let Err(err) = SynchronizeTask::new(
client.clone(),
config.clone(),
EngineSyncStateUpdate {
unsafe_head: Some(start.un_safe),
cross_unsafe_head: Some(start.un_safe),
local_safe_head: Some(start.safe),
safe_head: Some(start.safe),
finalized_head: Some(start.finalized),
},
)
.execute(&mut self.state)
.await
{
match err.severity() {
EngineTaskErrorSeverity::Temporary |
EngineTaskErrorSeverity::Flush |
EngineTaskErrorSeverity::Reset => {
debug!(target: "engine", ?err, "Forkchoice update failed during reset. Trying again...");
}
EngineTaskErrorSeverity::Critical => {
return Err(EngineResetError::Forkchoice(err));
}
}
}
let origin_block = start
.safe
.l1_origin
.number
.saturating_sub(config.channel_timeout(start.safe.block_info.timestamp));
let l1_origin_info: BlockInfo = client
.l1_provider()
.get_block(origin_block.into())
.await
.map_err(SyncStartError::RpcError)?
.ok_or(SyncStartError::BlockNotFound(origin_block.into()))?
.into_consensus()
.into();
let l2_safe_block = client
.l2_engine()
.get_block(start.safe.block_info.hash.into())
.full()
.await
.map_err(SyncStartError::RpcError)?
.ok_or(SyncStartError::BlockNotFound(origin_block.into()))?
.into_consensus()
.map_transactions(|t| <Transaction<OpTxEnvelope> as Clone>::clone(&t).into_inner());
let system_config = to_system_config(&l2_safe_block, &config)?;
kona_macros::inc!(counter, Metrics::ENGINE_RESET_COUNT);
Ok((start.safe, l1_origin_info, system_config))
}
pub fn clear(&mut self) {
self.tasks.clear();
}
pub async fn drain(&mut self) -> Result<(), EngineTaskErrors> {
while let Some(task) = self.tasks.peek() {
task.execute(&mut self.state).await?;
self.state_sender.send_replace(self.state);
self.tasks.pop();
self.task_queue_length.send_replace(self.tasks.len());
}
Ok(())
}
}
#[derive(Debug, Error)]
pub enum EngineResetError {
#[error(transparent)]
Forkchoice(#[from] SynchronizeTaskError),
#[error(transparent)]
SyncStart(#[from] SyncStartError),
#[error(transparent)]
SystemConfigConversion(#[from] OpBlockConversionError),
}