Skip to content

Commit aea3c93

Browse files
authored
feat: chain orchestrator (#185)
* feat: chain orchestrator * lint * lint * docs lint * fix: clone the in-memory chain instead of taking it * update chain consolidation and l1 message validation * add missing network block error * update fork sync / reconcilliation to use network as opossed to database * fix merege * refactor and add test cases * cleanup * rename chain orchestrator crate * cleanup * remove expect for sequencer in rnm * sync test * sync test * sync test * add error handling for missing paylaod id * remove networking for L1 consolidation sync test * improve test coverage and fix bugs * lint * fix cli NetworkArgs * add block gap to reorg integration test * make test more robust * merge upstream * refactor * address comments * address comments * merge changes * add migration script
1 parent 143a60f commit aea3c93

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3618
-1478
lines changed

.codespellrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[codespell]
2-
skip = .git,target,Cargo.toml,Cargo.lock
2+
skip = .git,target,Cargo.toml,Cargo.lock,docker-compose
33
ignore-words-list = crate

.github/workflows/lint.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ jobs:
128128
- type: wasm
129129
target: wasm32-unknown-unknown
130130
exclude: |
131-
scroll-engine,scroll-wire,rollup-node,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-indexer,scroll-codec,scroll-derivation-pipeline,rollup-node-providers,rollup-node-sequencer,rollup-node-signer,tests
131+
scroll-engine,scroll-wire,rollup-node,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-chain-orchestrator,scroll-codec,scroll-derivation-pipeline,rollup-node-providers,rollup-node-sequencer,rollup-node-signer,tests
132132
- type: riscv
133133
target: riscv32imac-unknown-none-elf
134134
exclude: |
135-
scroll-engine,scroll-wire,rollup-node,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-indexer,scroll-codec,scroll-derivation-pipeline,rollup-node-providers,rollup-node-sequencer,rollup-node-signer,tests
135+
scroll-engine,scroll-wire,rollup-node,scroll-network,rollup-node-manager,rollup-node-watcher,scroll-db,scroll-migration,rollup-node-chain-orchestrator,scroll-codec,scroll-derivation-pipeline,rollup-node-providers,rollup-node-sequencer,rollup-node-signer,tests
136136
steps:
137137
- uses: actions/checkout@v5
138138
- uses: rui314/setup-mold@v1

Cargo.lock

Lines changed: 24 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ members = [
1212
"crates/database/migration",
1313
"crates/derivation-pipeline",
1414
"crates/engine",
15-
"crates/indexer",
15+
"crates/chain-orchestrator",
1616
"crates/l1",
1717
"crates/manager",
1818
"crates/network",
@@ -155,6 +155,7 @@ reth-primitives-traits = { git = "https://github.com/scroll-tech/reth.git", defa
155155
reth-provider = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
156156
reth-rpc-builder = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
157157
reth-rpc-server-types = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
158+
reth-storage-api = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
158159
reth-tasks = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
159160
reth-tokio-util = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
160161
reth-tracing = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
@@ -168,7 +169,7 @@ reth-scroll-primitives = { git = "https://github.com/scroll-tech/reth.git", defa
168169

169170
# rollup node
170171
rollup-node = { path = "crates/node" }
171-
rollup-node-indexer = { path = "crates/indexer" }
172+
rollup-node-chain-orchestrator = { path = "crates/chain-orchestrator" }
172173
rollup-node-manager = { path = "crates/manager" }
173174
rollup-node-primitives = { path = "crates/primitives" }
174175
rollup-node-providers = { path = "crates/providers" }

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This repository is a modular Rust workspace for the Scroll rollup node. It is de
2424
│ │ └── migration/
2525
│ ├── derivation-pipeline/
2626
│ ├── engine/
27-
│ ├── indexer/
27+
│ ├── chain-orchestrator/
2828
│ ├── l1/
2929
│ ├── network/
3030
│ ├── node/
@@ -46,7 +46,7 @@ This repository is a modular Rust workspace for the Scroll rollup node. It is de
4646
- **crates/database/migration/**: Database schema migrations using SeaORM.
4747
- **crates/derivation-pipeline/**: Stateless pipeline for transforming batches into block-building payloads.
4848
- **crates/engine/**: Core engine logic for block execution, fork choice, and payload management.
49-
- **crates/indexer/**: Indexes L1 and L2 data for efficient querying and notification.
49+
- **crates/chain-orchestrator/**: Responsible for orchestrating the L2 chain based on events from L1 and data gossiped over the P2P network.
5050
- **crates/l1/**: Primitives and ABI bindings for L1 contracts and messages.
5151
- **crates/network/**: P2P networking stack for node communication.
5252
- **crates/node/**: Node manager and orchestration logic.

crates/indexer/Cargo.toml renamed to crates/chain-orchestrator/Cargo.toml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "rollup-node-indexer"
2+
name = "rollup-node-chain-orchestrator"
33
version.workspace = true
44
edition.workspace = true
55
rust-version.workspace = true
@@ -11,19 +11,30 @@ workspace = true
1111

1212
[dependencies]
1313
# alloy
14+
alloy-consensus = { workspace = true }
15+
alloy-eips = { workspace = true }
16+
alloy-json-rpc.workspace = true
1417
alloy-primitives.workspace = true
18+
alloy-provider.workspace = true
19+
alloy-transport.workspace = true
1520

1621
# rollup-node
1722
scroll-db.workspace = true
1823
rollup-node-primitives.workspace = true
1924
rollup-node-watcher.workspace = true
2025

2126
# scroll
27+
reth-scroll-primitives.workspace = true
2228
scroll-alloy-consensus.workspace = true
2329
scroll-alloy-hardforks.workspace = true
30+
scroll-alloy-network.workspace = true
31+
scroll-network.workspace = true
2432

2533
# reth
2634
reth-chainspec.workspace = true
35+
reth-network-p2p = { git = "https://github.com/scroll-tech/reth.git", default-features = false }
36+
reth-network-peers.workspace = true
37+
reth-primitives-traits.workspace = true
2738

2839
# misc
2940
futures.workspace = true
@@ -36,7 +47,10 @@ tokio-stream.workspace = true
3647
tracing.workspace = true
3748

3849
[dev-dependencies]
50+
alloy-consensus = { workspace = true, features = ["arbitrary"] }
3951
alloy-primitives = { workspace = true, features = ["arbitrary"] }
52+
alloy-rpc-client.workspace = true
53+
alloy-transport.workspace = true
4054

4155
# rollup-node
4256
scroll-db = { workspace = true, features = ["test-utils"] }
@@ -46,8 +60,15 @@ rollup-node-primitives = { workspace = true, features = ["arbitrary"] }
4660
reth-scroll-chainspec.workspace = true
4761
reth-scroll-forks.workspace = true
4862

63+
# reth
64+
reth-eth-wire-types.workspace = true
65+
reth-network-peers.workspace = true
66+
4967
# misc
5068
arbitrary.workspace = true
5169
futures.workspace = true
70+
parking_lot.workspace = true
5271
rand.workspace = true
72+
reqwest.workspace = true
73+
serde_json = { version = "1.0" }
5374
tokio.workspace = true
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use super::{ChainOrchestratorError, ChainOrchestratorEvent};
2+
use std::{
3+
fmt,
4+
future::Future,
5+
pin::Pin,
6+
task::{Context, Poll},
7+
};
8+
9+
/// A future that resolves to a `Result<ChainOrchestratorEvent, ChainOrchestratorError>`.
10+
pub(super) type PendingChainOrchestratorFuture = Pin<
11+
Box<dyn Future<Output = Result<Option<ChainOrchestratorEvent>, ChainOrchestratorError>> + Send>,
12+
>;
13+
14+
/// A type that represents a future that is being executed by the chain orchestrator.
15+
pub(super) enum ChainOrchestratorFuture {
16+
HandleReorg(PendingChainOrchestratorFuture),
17+
HandleFinalized(PendingChainOrchestratorFuture),
18+
HandleBatchCommit(PendingChainOrchestratorFuture),
19+
HandleBatchFinalization(PendingChainOrchestratorFuture),
20+
HandleL1Message(PendingChainOrchestratorFuture),
21+
HandleDerivedBlock(PendingChainOrchestratorFuture),
22+
HandleL2Block(PendingChainOrchestratorFuture),
23+
}
24+
25+
impl ChainOrchestratorFuture {
26+
/// Polls the future to completion.
27+
pub(super) fn poll(
28+
&mut self,
29+
cx: &mut Context<'_>,
30+
) -> Poll<Result<Option<ChainOrchestratorEvent>, ChainOrchestratorError>> {
31+
match self {
32+
Self::HandleReorg(fut) |
33+
Self::HandleFinalized(fut) |
34+
Self::HandleBatchCommit(fut) |
35+
Self::HandleBatchFinalization(fut) |
36+
Self::HandleL1Message(fut) |
37+
Self::HandleDerivedBlock(fut) |
38+
Self::HandleL2Block(fut) => fut.as_mut().poll(cx),
39+
}
40+
}
41+
}
42+
43+
// We implement the Debug trait for ChainOrchestratorFuture to provide a human-readable
44+
// representation of the enum variants.
45+
impl fmt::Debug for ChainOrchestratorFuture {
46+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47+
match self {
48+
Self::HandleReorg(_) => write!(f, "HandleReorg"),
49+
Self::HandleFinalized(_) => write!(f, "HandleFinalized"),
50+
Self::HandleBatchCommit(_) => write!(f, "HandleBatchCommit"),
51+
Self::HandleBatchFinalization(_) => write!(f, "HandleBatchFinalization"),
52+
Self::HandleL1Message(_) => write!(f, "HandleL1Message"),
53+
Self::HandleDerivedBlock(_) => write!(f, "HandleDerivedBlock"),
54+
Self::HandleL2Block(_) => write!(f, "HandleL2Block"),
55+
}
56+
}
57+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use alloy_json_rpc::RpcError;
2+
use alloy_primitives::B256;
3+
use alloy_transport::TransportErrorKind;
4+
use scroll_db::{DatabaseError, L1MessageStart};
5+
6+
/// A type that represents an error that occurred in the chain orchestrator.
7+
#[derive(Debug, thiserror::Error)]
8+
pub enum ChainOrchestratorError {
9+
/// An error occurred while interacting with the database.
10+
#[error("database error occurred: {0}")]
11+
DatabaseError(#[from] DatabaseError),
12+
/// An error occurred while trying to fetch the L2 block from the database.
13+
#[error("L2 block not found - block number: {0}")]
14+
L2BlockNotFoundInDatabase(u64),
15+
/// An error occurred while trying to fetch the L2 block from the L2 client.
16+
#[error("L2 block not found in L2 client - block number: {0}")]
17+
L2BlockNotFoundInL2Client(u64),
18+
/// A fork was received from the peer that is associated with a reorg of the safe chain.
19+
#[error("L2 safe block reorg detected")]
20+
L2SafeBlockReorgDetected,
21+
/// A block contains invalid L1 messages.
22+
#[error("Block contains invalid L1 message. Expected: {expected:?}, Actual: {actual:?}")]
23+
L1MessageMismatch {
24+
/// The expected L1 messages hash.
25+
expected: B256,
26+
/// The actual L1 messages hash.
27+
actual: B256,
28+
},
29+
/// An L1 message was not found in the database.
30+
#[error("L1 message not found at {0}")]
31+
L1MessageNotFound(L1MessageStart),
32+
/// An inconsistency was detected when trying to consolidate the chain.
33+
#[error("Chain inconsistency detected")]
34+
ChainInconsistency,
35+
/// The peer did not provide the requested block header.
36+
#[error("A peer did not provide the requested block header")]
37+
MissingBlockHeader {
38+
/// The hash of the block header that was requested.
39+
hash: B256,
40+
},
41+
/// An error occurred while making a network request.
42+
#[error("Network request error: {0}")]
43+
NetworkRequestError(#[from] reth_network_p2p::error::RequestError),
44+
/// An error occurred while making a JSON-RPC request to the Execution Node (EN).
45+
#[error("An error occurred while making a JSON-RPC request to the EN: {0}")]
46+
RpcError(#[from] RpcError<TransportErrorKind>),
47+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use alloy_consensus::Header;
2+
use alloy_primitives::{Signature, B256};
3+
use reth_network_peers::PeerId;
4+
use reth_scroll_primitives::ScrollBlock;
5+
use rollup_node_primitives::{BatchInfo, BlockInfo, ChainImport, L2BlockInfoWithL1Messages};
6+
7+
/// An event emitted by the `ChainOrchestrator`.
8+
#[derive(Debug, Clone, PartialEq, Eq)]
9+
pub enum ChainOrchestratorEvent {
10+
/// A new block has been received from the network but we have insufficient data to process it
11+
/// due to being in optimistic mode.
12+
InsufficientDataForReceivedBlock(B256),
13+
/// The block that we have received is already known.
14+
BlockAlreadyKnown(B256, PeerId),
15+
/// A fork of the chain that is older than the current chain has been received.
16+
OldForkReceived {
17+
/// The headers of the old fork.
18+
headers: Vec<Header>,
19+
/// The peer that provided the old fork.
20+
peer_id: PeerId,
21+
/// The signature of the old fork.
22+
signature: Signature,
23+
},
24+
/// The chain should be optimistically synced to the provided block.
25+
OptimisticSync(ScrollBlock),
26+
/// The chain has been extended, returning the new blocks.
27+
ChainExtended(ChainImport),
28+
/// The chain has reorged, returning the new chain and the peer that provided them.
29+
ChainReorged(ChainImport),
30+
/// A `BatchCommit` event has been indexed returning the batch info and L1 block number at
31+
/// which the event was emitted. If this event is associated with a batch revert then the
32+
/// `safe_head` will also be populated with the `BlockInfo` that represents the new L2 head.
33+
BatchCommitIndexed {
34+
/// The batch info.
35+
batch_info: BatchInfo,
36+
/// The L1 block number in which the batch was committed.
37+
l1_block_number: u64,
38+
/// The safe L2 block info.
39+
safe_head: Option<BlockInfo>,
40+
},
41+
/// A batch has been finalized returning the batch hash and new an optional finalized
42+
/// L2 block.
43+
BatchFinalized(B256, Option<BlockInfo>),
44+
/// An L1 block has been finalized returning the L1 block number and an optional
45+
/// finalized L2 block.
46+
L1BlockFinalized(u64, Option<BlockInfo>),
47+
/// A `L1Message` event has been committed returning the message queue index.
48+
L1MessageCommitted(u64),
49+
/// A reorg has occurred on L1, returning the L1 block number of the new L1 head,
50+
/// the L1 message queue index of the new L1 head, and optionally the L2 head and safe block
51+
/// info if the reorg resulted in a new L2 head or safe block.
52+
L1Reorg {
53+
/// The L1 block number of the new L1 head.
54+
l1_block_number: u64,
55+
/// The L1 message queue index of the new L1 head.
56+
queue_index: Option<u64>,
57+
/// The L2 head block info.
58+
l2_head_block_info: Option<BlockInfo>,
59+
/// The L2 safe block info.
60+
l2_safe_block_info: Option<BlockInfo>,
61+
},
62+
/// An L2 block has been committed returning the [`L2BlockInfoWithL1Messages`] and an
63+
/// optional [`BatchInfo`] if the block is associated with a committed batch.
64+
L2ChainCommitted(L2BlockInfoWithL1Messages, Option<BatchInfo>, bool),
65+
/// An L2 consolidated block has been committed returning the [`L2BlockInfoWithL1Messages`].
66+
L2ConsolidatedBlockCommitted(L2BlockInfoWithL1Messages),
67+
}

0 commit comments

Comments
 (0)