kona_node_service/actors/engine/
finalizer.rs

1//! The [`L2Finalizer`].
2
3use kona_engine::{EngineTask, FinalizeTask};
4use kona_protocol::{BlockInfo, OpAttributesWithParent};
5use std::collections::BTreeMap;
6use tokio::sync::watch;
7
8use crate::actors::engine::actor::EngineActorState;
9
10/// An internal type alias for L1 block numbers.
11type L1BlockNumber = u64;
12
13/// An internal type alias for L2 block numbers.
14type L2BlockNumber = u64;
15
16/// The [`L2Finalizer`] is responsible for finalizing L2 blocks derived from finalized L1 blocks.
17/// It maintains a queue of derived L2 blocks that are awaiting finalization, and finalizes them
18/// as new finalized L1 blocks are received.
19#[derive(Debug)]
20pub struct L2Finalizer {
21    /// A channel that receives new finalized L1 blocks intermittently.
22    finalized_l1_block_rx: watch::Receiver<Option<BlockInfo>>,
23    /// A map of `L1 block number -> highest derived L2 block number` within the L1 epoch, used to
24    /// track derived [`OpAttributesWithParent`] awaiting finalization. When a new finalized L1
25    /// block is received, the highest L2 block whose inputs are contained within the finalized
26    /// L1 chain is finalized.
27    awaiting_finalization: BTreeMap<L1BlockNumber, L2BlockNumber>,
28}
29
30impl L2Finalizer {
31    /// Creates a new [`L2Finalizer`] with the given channel receiver for finalized L1 blocks.
32    pub const fn new(finalized_l1_block_rx: watch::Receiver<Option<BlockInfo>>) -> Self {
33        Self { finalized_l1_block_rx, awaiting_finalization: BTreeMap::new() }
34    }
35
36    /// Enqueues a derived [`OpAttributesWithParent`] for finalization. When a new finalized L1
37    /// block is observed that is `>=` the height of [`OpAttributesWithParent::derived_from`], the
38    /// L2 block associated with the payload attributes will be finalized.
39    pub fn enqueue_for_finalization(&mut self, attributes: &OpAttributesWithParent) {
40        self.awaiting_finalization
41            .entry(
42                attributes.derived_from.map(|b| b.number).expect(
43                    "Fatal: Cannot enqueue attributes for finalization that weren't derived",
44                ),
45            )
46            .and_modify(|n| *n = (*n).max(attributes.block_number()))
47            .or_insert(attributes.block_number());
48    }
49
50    /// Clears the finalization queue.
51    pub fn clear(&mut self) {
52        self.awaiting_finalization.clear();
53    }
54
55    /// Receives a new finalized L1 block from the channel.
56    pub async fn new_finalized_block(&mut self) -> Result<(), watch::error::RecvError> {
57        self.finalized_l1_block_rx.changed().await
58    }
59
60    /// Attempts to finalize any L2 blocks that the finalizer knows about and are contained within
61    /// the new finalized L1 chain.
62    pub(super) async fn try_finalize_next(&mut self, engine_state: &mut EngineActorState) {
63        // If there is no finalized L1 block available in the watch channel, do nothing.
64        let Some(new_finalized_l1) = *self.finalized_l1_block_rx.borrow() else {
65            return;
66        };
67
68        // Find the highest safe L2 block that is contained within the finalized chain,
69        // that the finalizer is aware of.
70        let highest_safe = self.awaiting_finalization.range(..=new_finalized_l1.number).next_back();
71
72        // If the highest safe block is found, enqueue a finalization task and drain the
73        // queue of all L1 blocks not contained in the finalized L1 chain.
74        if let Some((_, highest_safe_number)) = highest_safe {
75            let task = EngineTask::Finalize(FinalizeTask::new(
76                engine_state.client.clone(),
77                engine_state.rollup.clone(),
78                *highest_safe_number,
79            ));
80            engine_state.engine.enqueue(task);
81
82            self.awaiting_finalization.retain(|&number, _| number > new_finalized_l1.number);
83        }
84    }
85}