Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0ea7a37
fix(supervisor/core): derivation update failure
dhyaniarun1993 Jul 4, 2025
66d92e4
main merged + conflict resolved
dhyaniarun1993 Jul 7, 2025
dac9db9
derivation storage idempotancy
dhyaniarun1993 Jul 7, 2025
34339a2
docs updated
dhyaniarun1993 Jul 7, 2025
457b19d
lint and test fix
dhyaniarun1993 Jul 7, 2025
1df0198
chore(supervisor): remove l1 cache
dhyaniarun1993 Jul 7, 2025
fc34dd3
lintfix
dhyaniarun1993 Jul 7, 2025
6dbd3dd
Merge branch 'chore/remove-current-l1' into feat/preinter-db-support
dhyaniarun1993 Jul 8, 2025
42fa9db
feat(supervisor): preinterop db support
dhyaniarun1993 Jul 8, 2025
db55aff
reset pre-interop api integrated
dhyaniarun1993 Jul 8, 2025
9164926
pluggedin db initialisation error
dhyaniarun1993 Jul 8, 2025
0f45bb5
Merge branch 'feat/preinter-db-support' into feat/preinterop-node-sup…
dhyaniarun1993 Jul 8, 2025
743d186
resetter refactored
dhyaniarun1993 Jul 8, 2025
c593bd8
refactor
dhyaniarun1993 Jul 8, 2025
a7d9dca
Merge branch 'feat/preinter-db-support' into feat/preinterop-node-sup…
dhyaniarun1993 Jul 8, 2025
8483982
pre-interop support added
dhyaniarun1993 Jul 8, 2025
7851f48
interop offset updated
dhyaniarun1993 Jul 9, 2025
8a6338c
main merged + conflict resolved
dhyaniarun1993 Jul 9, 2025
dfb77ff
fix metrics
dhyaniarun1993 Jul 9, 2025
de52623
log storage idempotancy
dhyaniarun1993 Jul 9, 2025
5e46799
lintfix
dhyaniarun1993 Jul 9, 2025
04fe5da
linfix
dhyaniarun1993 Jul 9, 2025
b4bd877
feat/preinter-db-support merged
dhyaniarun1993 Jul 9, 2025
9af9d1a
minor improvements
dhyaniarun1993 Jul 14, 2025
6f9b05c
main merged + conflict resolved
dhyaniarun1993 Jul 14, 2025
290cd7c
test cases fixes
dhyaniarun1993 Jul 14, 2025
7897656
op-node image updated
dhyaniarun1993 Jul 14, 2025
9742f09
added test cases
dhyaniarun1993 Jul 14, 2025
2ad4707
corner cases + bugfixes
dhyaniarun1993 Jul 14, 2025
98f22bc
managed node spec changes added
dhyaniarun1993 Jul 15, 2025
6302763
revert reset on FutureErr to let finalized head sync
dhyaniarun1993 Jul 15, 2025
eec5897
lintfix
dhyaniarun1993 Jul 15, 2025
a638346
Merge branch 'main' into feat/preinterop-node-support
dhyaniarun1993 Jul 15, 2025
aaaba11
review fixes
dhyaniarun1993 Jul 15, 2025
7471cf2
added more test cases
dhyaniarun1993 Jul 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
384 changes: 354 additions & 30 deletions crates/supervisor/core/src/chain_processor/task.rs

Large diffs are not rendered by default.

46 changes: 41 additions & 5 deletions crates/supervisor/core/src/config/rollup_config_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
}
}

/// Returns the genesis anchor as a [`DerivedRefPair`].
pub const fn get_anchor(&self) -> DerivedRefPair {
/// Returns the genesis as a [`DerivedRefPair`].
pub const fn get_derived_pair(&self) -> DerivedRefPair {

Check warning on line 33 in crates/supervisor/core/src/config/rollup_config_set.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/config/rollup_config_set.rs#L33

Added line #L33 was not covered by tests
DerivedRefPair { derived: self.l2, source: self.l1 }
}
}
Expand Down Expand Up @@ -73,15 +73,36 @@
})
}

/// Returns `true` if the timestamp is at or after the interop activation time.
///
/// Interop activates at [`interop_time`](Self::interop_time). This function checks whether the
/// provided timestamp is before or after interop timestamp.
///
/// Returns `false` if `interop_time` is not configured.
pub fn is_interop(&self, timestamp: u64) -> bool {
self.interop_time.is_some_and(|t| timestamp >= t)
}

/// Returns `true` if the timestamp is strictly after the interop activation block.
///
/// Interop activates at [`interop_time`](Self::interop_time). This function checks whether the
/// current block timestamp is *after* that activation, skipping the activation block
/// provided timestamp is *after* that activation, skipping the activation block
/// itself.
///
/// Returns `false` if `interop_time` is not configured.
pub fn is_post_interop(&self, timestamp: u64) -> bool {
self.interop_time.is_some_and(|t| timestamp.saturating_sub(self.block_time) >= t)
self.is_interop(timestamp.saturating_sub(self.block_time))
}

/// Returns `true` if given block is the interop activation block.
///
/// An interop activation block is defined as the block that is right after the
/// interop activation time.
///
/// Returns `false` if `interop_time` is not configured.
pub fn is_interop_activation_block(&self, block: BlockInfo) -> bool {
self.is_interop(block.timestamp) &&
!self.is_interop(block.timestamp.saturating_sub(self.block_time))
}
}

Expand Down Expand Up @@ -115,10 +136,15 @@
Ok(())
}

/// returns whether interop is enabled for a chain at given timestamp
/// Returns `true` if interop is enabled for the chain at given timestamp.
pub fn is_interop_enabled(&self, chain_id: ChainId, timestamp: u64) -> bool {
self.get(chain_id).map(|cfg| cfg.is_post_interop(timestamp)).unwrap_or(false) // if config not found, return false
}

/// Returns `true` if given block is the interop activation block for the specified chain.
pub fn is_interop_activation_block(&self, chain_id: ChainId, block: BlockInfo) -> bool {
self.get(chain_id).map(|cfg| cfg.is_interop_activation_block(block)).unwrap_or(false)
}

Check warning on line 147 in crates/supervisor/core/src/config/rollup_config_set.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/config/rollup_config_set.rs#L145-L147

Added lines #L145 - L147 were not covered by tests
}

#[cfg(test)]
Expand Down Expand Up @@ -152,4 +178,14 @@
// Unknown chain_id returns false
assert!(!set.is_interop_enabled(ChainId::from(999u64), 200));
}

#[test]
fn test_rollup_config_is_interop_interop_time_zero() {
// Interop time is 100, block_time is 10
let rollup_config =
RollupConfig::new(Genesis::new(dummy_blockinfo(0), dummy_blockinfo(0)), 2, Some(0));

assert!(rollup_config.is_interop(0));
assert!(rollup_config.is_interop(1000));
}
}
2 changes: 1 addition & 1 deletion crates/supervisor/core/src/logindexer/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ mod tests {
let mut mock_provider = MockBlockProvider::new();
mock_provider.expect_fetch_receipts().withf(move |hash| *hash == block_hash).returning(
|_| {
Err(ManagedNodeError::Client(ClientError::Authentication(
Err(ManagedNodeError::ClientError(ClientError::Authentication(
AuthenticationError::InvalidHeader,
)))
},
Expand Down
27 changes: 22 additions & 5 deletions crates/supervisor/core/src/safety_checker/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use alloy_primitives::ChainId;
use derive_more::Constructor;
use kona_protocol::BlockInfo;
use kona_supervisor_storage::CrossChainSafetyProvider;
use kona_supervisor_storage::{CrossChainSafetyProvider, StorageError};
use std::{sync::Arc, time::Duration};
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
Expand Down Expand Up @@ -114,10 +114,27 @@

// Finds the next block that is eligible for promotion at the configured target level.
fn find_next_promotable_block(&self) -> Result<BlockInfo, CrossSafetyError> {
let current_head =
self.provider.get_safety_head_ref(self.chain_id, self.promoter.target_level())?;
let upper_head =
self.provider.get_safety_head_ref(self.chain_id, self.promoter.lower_bound_level())?;
let current_head = self
.provider
.get_safety_head_ref(self.chain_id, self.promoter.target_level())
.map_err(|err| {
if matches!(err, StorageError::FutureData) {
CrossSafetyError::NoBlockToPromote

Check warning on line 122 in crates/supervisor/core/src/safety_checker/task.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/safety_checker/task.rs#L121-L122

Added lines #L121 - L122 were not covered by tests
} else {
err.into()

Check warning on line 124 in crates/supervisor/core/src/safety_checker/task.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/safety_checker/task.rs#L124

Added line #L124 was not covered by tests
}
})?;

let upper_head = self
.provider
.get_safety_head_ref(self.chain_id, self.promoter.lower_bound_level())
.map_err(|err| {
if matches!(err, StorageError::FutureData) {
CrossSafetyError::NoBlockToPromote

Check warning on line 133 in crates/supervisor/core/src/safety_checker/task.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/safety_checker/task.rs#L132-L133

Added lines #L132 - L133 were not covered by tests
} else {
err.into()

Check warning on line 135 in crates/supervisor/core/src/safety_checker/task.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/safety_checker/task.rs#L135

Added line #L135 was not covered by tests
}
})?;

if current_head.number >= upper_head.number {
return Err(CrossSafetyError::NoBlockToPromote);
Expand Down
10 changes: 7 additions & 3 deletions crates/supervisor/core/src/supervisor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,13 @@
for (chain_id, config) in self.config.rollup_config_set.rollups.iter() {
// Initialise the database for each chain.
let db = self.database_factory.get_or_create_db(*chain_id)?;
let anchor = config.genesis.get_anchor();
db.initialise_log_storage(anchor.derived)?;
db.initialise_derivation_storage(anchor)?;
let interop_time = config.interop_time;
let derived_pair = config.genesis.get_derived_pair();
if config.is_interop(derived_pair.derived.timestamp) {
Comment thread
itschaindev marked this conversation as resolved.
info!(target: "supervisor_service", chain_id, interop_time, %derived_pair, "Initialising database for interop activation block");
db.initialise_log_storage(derived_pair.derived)?;
db.initialise_derivation_storage(derived_pair)?;
}

Check warning on line 152 in crates/supervisor/core/src/supervisor.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/supervisor.rs#L146-L152

Added lines #L146 - L152 were not covered by tests
info!(target: "supervisor_service", chain_id, "Database initialized successfully");
}
Ok(())
Expand Down
20 changes: 19 additions & 1 deletion crates/supervisor/core/src/syncnode/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
/// Fetches the [`BlockInfo`] by block number.
async fn block_ref_by_number(&self, block_number: u64) -> Result<BlockInfo, ClientError>;

/// Resets the managed node to the pre-interop state.
async fn reset_pre_interop(&self) -> Result<(), ClientError>;

/// Resets the node state with the provided block IDs.
async fn reset(
&self,
Expand Down Expand Up @@ -302,14 +305,29 @@
Metrics::MANAGED_NODE_RPC_REQUEST_DURATION_SECONDS,
"block_ref_by_number",
async {
ManagedModeApiClient::block_ref_by_number(client.as_ref(), block_number).await
ManagedModeApiClient::l2_block_ref_by_number(client.as_ref(), block_number).await

Check warning on line 308 in crates/supervisor/core/src/syncnode/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/syncnode/client.rs#L308

Added line #L308 was not covered by tests
},
"node" => self.config.url.clone()
)?;

Ok(block_info)
}

async fn reset_pre_interop(&self) -> Result<(), ClientError> {
let client = self.get_ws_client().await?;
observe_metrics_for_result_async!(
Metrics::MANAGED_NODE_RPC_REQUESTS_SUCCESS_TOTAL,
Metrics::MANAGED_NODE_RPC_REQUESTS_ERROR_TOTAL,
Metrics::MANAGED_NODE_RPC_REQUEST_DURATION_SECONDS,
"reset_pre_interop",
async {
ManagedModeApiClient::reset_pre_interop(client.as_ref()).await
},
"node" => self.config.url.clone()
)?;
Ok(())
}

Check warning on line 329 in crates/supervisor/core/src/syncnode/client.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/syncnode/client.rs#L316-L329

Added lines #L316 - L329 were not covered by tests

async fn reset(
&self,
unsafe_id: BlockNumHash,
Expand Down
2 changes: 1 addition & 1 deletion crates/supervisor/core/src/syncnode/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use thiserror::Error;
pub enum ManagedNodeError {
/// Represents an error that occurred while starting the managed node.
#[error(transparent)]
Client(#[from] ClientError),
ClientError(#[from] ClientError),

/// Represents an error that occurred while subscribing to the managed node.
#[error("subscription error: {0}")]
Expand Down
18 changes: 16 additions & 2 deletions crates/supervisor/core/src/syncnode/resetter.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{ManagedNodeClient, ManagedNodeError};
use alloy_eips::BlockNumHash;
use kona_supervisor_storage::{DerivationStorageReader, HeadRefStorageReader};
use kona_supervisor_storage::{DerivationStorageReader, HeadRefStorageReader, StorageError};
use kona_supervisor_types::SuperHead;
use std::sync::Arc;
use tokio::sync::Mutex;
Expand Down Expand Up @@ -32,6 +32,11 @@
let SuperHead { local_unsafe, cross_unsafe, local_safe, cross_safe, finalized, .. } =
match self.get_latest_valid_super_head().await {
Ok(block) => block,
// todo: require refactor and corner case handling
Comment thread
itschaindev marked this conversation as resolved.
Err(ManagedNodeError::StorageError(StorageError::DatabaseNotInitialised)) => {
self.reset_pre_interop().await?;
return Ok(());

Check warning on line 38 in crates/supervisor/core/src/syncnode/resetter.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/syncnode/resetter.rs#L37-L38

Added lines #L37 - L38 were not covered by tests
}
Err(err) => {
error!(target: "resetter", %err, "Failed to get latest valid derived block");
return Err(ManagedNodeError::ResetFailed);
Expand Down Expand Up @@ -59,7 +64,15 @@
.inspect_err(|err| {
error!(target: "resetter", %err, "Failed to reset managed node");
})?;
Ok(())
}

async fn reset_pre_interop(&self) -> Result<(), ManagedNodeError> {
info!(target: "resetter", "Resetting the node to pre-interop state");

Check warning on line 71 in crates/supervisor/core/src/syncnode/resetter.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/syncnode/resetter.rs#L70-L71

Added lines #L70 - L71 were not covered by tests

self.client.reset_pre_interop().await.inspect_err(|err| {
error!(target: "resetter", %err, "Failed to reset managed node to pre-interop state");
})?;

Check warning on line 75 in crates/supervisor/core/src/syncnode/resetter.rs

View check run for this annotation

Codecov / codecov/patch

crates/supervisor/core/src/syncnode/resetter.rs#L73-L75

Added lines #L73 - L75 were not covered by tests
Ok(())
}

Expand Down Expand Up @@ -174,6 +187,7 @@
async fn pending_output_v0_at_timestamp(&self, timestamp: u64) -> Result<OutputV0, ClientError>;
async fn l2_block_ref_by_timestamp(&self, timestamp: u64) -> Result<BlockInfo, ClientError>;
async fn block_ref_by_number(&self, block_number: u64) -> Result<BlockInfo, ClientError>;
async fn reset_pre_interop(&self) -> Result<(), ClientError>;
async fn reset(&self, unsafe_id: BlockNumHash, cross_unsafe_id: BlockNumHash, local_safe_id: BlockNumHash, cross_safe_id: BlockNumHash, finalised_id: BlockNumHash) -> Result<(), ClientError>;
async fn provide_l1(&self, block_info: BlockInfo) -> Result<(), ClientError>;
async fn update_finalized(&self, finalized_block_id: BlockNumHash) -> Result<(), ClientError>;
Expand Down Expand Up @@ -214,7 +228,7 @@
#[tokio::test]
async fn test_reset_db_error() {
let mut db = MockDb::new();
db.expect_get_super_head().returning(|| Err(StorageError::DatabaseNotInitialised));
db.expect_get_super_head().returning(|| Err(StorageError::LockPoisoned));

let client = MockClient::new();

Expand Down
1 change: 1 addition & 0 deletions crates/supervisor/core/src/syncnode/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ mod tests {
async fn pending_output_v0_at_timestamp(&self, timestamp: u64) -> Result<OutputV0, ClientError>;
async fn l2_block_ref_by_timestamp(&self, timestamp: u64) -> Result<BlockInfo, ClientError>;
async fn block_ref_by_number(&self, block_number: u64) -> Result<BlockInfo, ClientError>;
async fn reset_pre_interop(&self) -> Result<(), ClientError>;
async fn reset(&self, unsafe_id: BlockNumHash, cross_unsafe_id: BlockNumHash, local_safe_id: BlockNumHash, cross_safe_id: BlockNumHash, finalised_id: BlockNumHash) -> Result<(), ClientError>;
async fn provide_l1(&self, block_info: BlockInfo) -> Result<(), ClientError>;
async fn update_finalized(&self, finalized_block_id: BlockNumHash) -> Result<(), ClientError>;
Expand Down
8 changes: 6 additions & 2 deletions crates/supervisor/rpc/src/jsonrpsee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ pub trait ManagedModeApi {
#[method(name = "anchorPoint")]
async fn anchor_point(&self) -> RpcResult<DerivedRefPair>;

/// Reset the managed node to the pre-interop state
#[method(name = "resetPreInterop")]
async fn reset_pre_interop(&self) -> RpcResult<()>;

/// Reset the managed node to the specified block heads
#[method(name = "reset")]
async fn reset(
Expand All @@ -189,8 +193,8 @@ pub trait ManagedModeApi {
async fn fetch_receipts(&self, block_hash: BlockHash) -> RpcResult<Receipts>;

/// Get block infor for a given block number
#[method(name = "blockRefByNumber")]
async fn block_ref_by_number(&self, number: u64) -> RpcResult<BlockInfo>;
#[method(name = "l2BlockRefByNumber")]
async fn l2_block_ref_by_number(&self, number: u64) -> RpcResult<BlockInfo>;

/// Get the chain id
#[method(name = "chainID")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ where
"Failed to seek head reference"
);
})?;
let block_ref = result.ok_or_else(|| {
warn!(target: "supervisor_storage", %safety_level, "No head reference found");
StorageError::EntryNotFound("no head reference found".to_string())
})?;
let block_ref = result.ok_or_else(|| StorageError::FutureData)?;
Ok(block_ref.into())
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/devnets/simple-supervisor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ optimism_package:
type: op-geth
cl:
type: op-node
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.3
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:develop
log_level: debug
network_params:
network: "kurtosis"
Expand All @@ -32,7 +32,7 @@ optimism_package:
type: op-geth
cl:
type: op-node
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.3
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:develop
log_level: debug
network_params:
network: "kurtosis"
Expand Down