Skip to content

feat: chain orchestrator #185

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open

feat: chain orchestrator #185

wants to merge 36 commits into from

Conversation

frisitano
Copy link
Collaborator

@frisitano frisitano commented Jul 2, 2025

closes: #182
closes scroll-tech/reth#243

@frisitano frisitano marked this pull request as ready for review July 16, 2025 14:10
@frisitano frisitano requested a review from Copilot July 16, 2025 14:13
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a new ChainOrchestrator to replace the previous indexer, integrates it throughout the node, watcher, network, and engine, and updates tests and database migrations accordingly.

  • Introduces ChainOrchestrator in place of Indexer, refactors RollupNodeManager to consume orchestrator events instead of indexer events.
  • Adds Synced notifications to L1Watcher and updates engine driver to handle optimistic sync via ChainOrchestrator.
  • Refactors configuration (ScrollRollupNodeConfig), network manager, and database migrations; adjusts tests to cover the new orchestrator flows.

Reviewed Changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/indexer/src/lib.rs Rename Indexer to ChainOrchestrator and overhaul API flows
crates/manager/src/manager/mod.rs Replace indexer usage with ChainOrchestrator in node manager
crates/node/src/args.rs Instantiate ChainOrchestrator in ScrollRollupNodeConfig
crates/watcher/src/lib.rs Add Synced variant and is_synced flag to L1Watcher
crates/scroll-wire/src/protocol/proto.rs Adjust doc comment for NewBlock::new
crates/node/tests/e2e.rs Add/revise reorg and sync end-to-end tests
crates/watcher/tests/reorg.rs Update tests to skip Synced notifications
crates/database/db/src/operations.rs Extend DB ops with L1MessageStart and block-and-batch queries
crates/database/migration/src/migration_info.rs Add genesis_hash() to migrations and insert genesis blocks
crates/network/src/manager.rs Wire up eth-wire listener and dispatch chain-orchestrator events
crates/engine/src/driver.rs Support ChainImport and OptimisticSync futures in engine driver
Comments suppressed due to low confidence (2)

crates/scroll-wire/src/protocol/proto.rs:33

  • The doc comment uses "blocks" (plural) but the constructor takes a single block; change to "block" for accuracy.
    /// Returns a [`NewBlock`] instance with the provided signature and blocks.

crates/node/tests/e2e.rs:95

  • The follower_can_reorg test has no assertions; either add meaningful checks or remove the empty test to maintain coverage.
async fn follower_can_reorg() -> eyre::Result<()> {

@frisitano frisitano requested a review from greged93 July 17, 2025 17:14
@greged93 greged93 mentioned this pull request Jul 22, 2025
Copy link
Collaborator

@greged93 greged93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of comments and some small nits and leftover code to clean.

Comment on lines -215 to +233
if self.is_synced() {
if self.is_synced {
tokio::time::sleep(SLOW_SYNC_INTERVAL).await;
} else if self.current_block_number == self.l1_state.head {
// if we have synced to the head of the L1, notify the channel and set the
// `is_synced`` flag.
if let Err(L1WatcherError::SendError(_)) = self.notify(L1Notification::Synced).await
{
tracing::warn!(target: "scroll::watcher", "L1 watcher channel closed, stopping the watcher");
break;
}
self.is_synced = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the current logic suggests the watcher can never transition from is_synced = true -> false. Is this expected?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. In the context of the RN, Synced should mean that we have synced all L1 messages required to validate messages included in unsafe L2 blocks. Given that we only include L1 messages after the corresponding L1 block has been finalized I think this should be fine provided the watcher doesn't start to lag > 2 epochs behind the safe tip then the Synced status should still remain valid. What do you think about this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm but so if we lose a provider for 12 minutes we might enter an edge case we can't exit from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point and given that we have had recent experiences of the L1 provider being down for longer than 12 minutes I think we should cover this case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do this in another PR?

Copy link
Collaborator

@greged93 greged93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple of extra comments and questions. Also I see we have a lot more unwraps in the code, are all these safe to keep?

Comment on lines +329 to +330
// Reverse the new chain headers to have them in the correct order.
received_chain_headers.reverse();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally think we would gain in code clarity if received_chain_headers and current_chain_headers were ordered in the same way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would tend to agree but I think we should attempt to merge as is for now and then refactor this at a later date.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me, should we raise an issue with all improvements listed?

Comment on lines +136 to +137
// Purge all pending block imports.
self.chain_imports.clear();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we purge all pending block imports?

Copy link
Collaborator Author

@frisitano frisitano Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the optimistic sync supercedes any previous chain imports. Does this seem reasonable to you?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also clear the pending futures then?

@@ -599,7 +598,7 @@ async fn can_build_blocks_and_exit_at_gas_limit() {
let chain_spec = SCROLL_DEV.clone();
const MIN_TRANSACTION_GAS_COST: u64 = 21_000;
const BLOCK_BUILDING_DURATION: Duration = Duration::from_millis(250);
const BLOCK_GAP_TRIGGER: u64 = 100;
// const BLOCK_GAP_TRIGGER: u64 = 100;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

Copy link
Collaborator

@greged93 greged93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewing l2geth code, I noticed we might be missing checks on L1 messages.

Comment on lines +888 to +891
async fn validate_l1_messages(
blocks: &[ScrollBlock],
database: Arc<Database>,
) -> Result<(), ChainOrchestratorError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might be missing a couple of checks in this function:
https://github.com/scroll-tech/go-ethereum/blob/develop/core/block_validator.go#L104-L109

  • Post Euclid V2: check indexes are continuous, at the start of the block and start at the last seen L1 message index.
  • Pre Euclid V2: queue index can't decrease, check skipped L1 messages are in DB.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can omit the check on queue indexes not being decreasing for now, as we will (most likely?) never sync pre euclid v2 blocks via live sync. I will open an issue to track this and we can implement it in the future to support pre euclid v2 sync.

Regarding the start index, how about we introduce an l1_message_index: Arc<Mutex<IndexType> in memory which is used to determine the cursor start. I think continuity is already covered due to the fact that we assert consistency with the db? Regarding the position in the block I will implement a check for that as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or are you suggesting we do this in the consensus on reth (as I believe you have already in the open PR)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or are you suggesting we do this in the consensus on reth (as I believe you have already in the open PR)?

I'm actually not sure if we should have these checks in Reth and/or Rollup node. What do you think?

Regarding the start index, how about we introduce an l1_message_index: Arc<Mutex in memory which is used to determine the cursor start.

Won't we have an issue with L1 reorgs with this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually not sure if we should have these checks in Reth and/or Rollup node. What do you think?

I think it makes sense to have the L1 message checks in the rollup node.

Won't we have an issue with L1 reorgs with this?

I wouldn't have thought so because L1 messages are only included when they are finalized, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to have the L1 message checks in the rollup node.

Works for me. I think I will still leave the current ones in Reth, for historical sync in order to still have a minimal check wdyt?

I wouldn't have thought so because L1 messages are only included when they are finalized, right?

As long as the sequencer uses the L1MessageInclusionMode::Finalized I think that's fine but if we switch to BlockDepth we might start to have issues.

Copy link
Collaborator

@greged93 greged93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just need to resolve the pending comments and open 1 issues related to L1 watcher sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Indexer] Refactor Indexer to ChainOrchestrator L1 message validation
2 participants