From dbd400c1813404baa2e46a21e6cfa517a167c978 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:39:10 +0800 Subject: [PATCH 01/10] add initial sequencer migration script and readme --- sequencer-migration/README.md | 35 +++ sequencer-migration/local.env | 2 + sequencer-migration/migrate-sequencer.sh | 355 +++++++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 sequencer-migration/README.md create mode 100644 sequencer-migration/local.env create mode 100755 sequencer-migration/migrate-sequencer.sh diff --git a/sequencer-migration/README.md b/sequencer-migration/README.md new file mode 100644 index 00000000..51648758 --- /dev/null +++ b/sequencer-migration/README.md @@ -0,0 +1,35 @@ +# Scroll Sequencer Migration + +This module contains documentation and scripts for Scroll's sequencer migration from `l2geth` to rollup node (RN) aka `l2reth`. + +### Risks +We want to minimize risks and minimize service disruption. For this we need to consider following risks: +- invalid L2 blocks produced +- L2 reorg (e.g. different blocks issued at same L2 block height) +- L1 messages skipped/reverted +- general service interruption + +## Migration Procedure + +To instill confidence we will do many repeated transitions from `l2geth` -> `l2reth` -> `l2geth` with the time that `l2reth` sequences increasing. + +The high-level flow of the transition will look like this: +1. `l2geth` is sequencing currently +2. Turn off `l2geth` sequencing +3. Get block height of `l2geth` +4. Wait until `l2reth` has same block height +5. Turn on `l2reth` sequencing +6. Wait until `l2reth` has sequenced until block X or for some time +7. Turn off `l2reth` sequencing +8. Wait until `l2geth` has same block height +9. Turn on `l2geth` sequencingx + +### Testing locally +```bash +# this test runs for ~60 seconds and starts with l2geth sequencing and expects all nodes to reach the same block at block number 120. +RUST_LOG=info,docker-compose=off cargo test --package tests --test migrate_sequencer -- docker_test_migrate_sequencer --exact --show-output + +source local.env +./migrate-sequencer.sh +``` + diff --git a/sequencer-migration/local.env b/sequencer-migration/local.env new file mode 100644 index 00000000..1e2fd7d3 --- /dev/null +++ b/sequencer-migration/local.env @@ -0,0 +1,2 @@ +export L2GETH_RPC_URL=http://localhost:8547 +export L2RETH_RPC_URL=http://localhost:8545 \ No newline at end of file diff --git a/sequencer-migration/migrate-sequencer.sh b/sequencer-migration/migrate-sequencer.sh new file mode 100755 index 00000000..ca03cb51 --- /dev/null +++ b/sequencer-migration/migrate-sequencer.sh @@ -0,0 +1,355 @@ +#!/bin/bash + +# Sequencer Migration Script +# Migrates sequencing from l2geth -> l2reth -> l2geth +# Usage: ./migrate-sequencer.sh + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +SYNC_TIMEOUT=5 # 5 seconds timeout for sync operations +POLL_INTERVAL=0.1 # Poll every 0.1 seconds + +# Global variables to track state +START_TIME=$(date +%s) +INITIAL_L2GETH_BLOCK="" +INITIAL_L2RETH_BLOCK="" +L2GETH_STOP_BLOCK="" +L2RETH_FINAL_BLOCK="" + +# Helper function to print colored output +log() { + echo -e "${2:-$NC}[$(date '+%H:%M:%S')] $1${NC}" +} + +log_info() { log "$1" "$BLUE"; } +log_success() { log "$1" "$GREEN"; } +log_warning() { log "$1" "$YELLOW"; } +log_error() { log "$1" "$RED"; } + +# Check if required environment variables are set +check_env_vars() { + log_info "Checking environment variables..." + + if [[ -z "${L2GETH_RPC_URL:-}" ]]; then + log_error "L2GETH_RPC_URL environment variable is required" + exit 1 + fi + + if [[ -z "${L2RETH_RPC_URL:-}" ]]; then + log_error "L2RETH_RPC_URL environment variable is required" + exit 1 + fi + + log_success "Environment variables configured" + log_info "L2GETH_RPC_URL: $L2GETH_RPC_URL" + log_info "L2RETH_RPC_URL: $L2RETH_RPC_URL" +} + +# Get block number and hash for a given RPC URL +get_block_info() { + local rpc_url="$1" + local temp_file=$(mktemp) + + if ! cast block latest --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then + rm -f "$temp_file" + return 1 + fi + + local block_number=$(grep "^number" "$temp_file" | awk '{print $2}') + local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}') + + rm -f "$temp_file" + echo "$block_number $block_hash" +} + +# Get only block number for a given RPC URL +get_block_number() { + local rpc_url="$1" + cast block latest --rpc-url "$rpc_url" 2>/dev/null | grep "^number" | awk '{print $2}' +} + +is_l2geth_mining() { + local result=$(cast rpc eth_mining --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | tr -d '"') + [[ "$result" == "true" ]] +} + +start_l2geth_mining() { + log_info "Starting l2geth mining..." + if cast rpc miner_start --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2GETH mining started" + return 0 + else + log_error "Failed to start l2geth mining" + return 1 + fi +} + +stop_l2geth_mining() { + log_info "Stopping l2geth mining..." + if cast rpc miner_stop --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2GETH mining stopped" + return 0 + else + log_error "Failed to stop l2geth mining" + return 1 + fi +} + +enable_l2reth_sequencing() { + log_info "Enabling L2RETH automatic sequencing..." + if cast rpc rollupNode_enableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2RETH automatic sequencing enabled" + return 0 + else + log_error "Failed to enable L2RETH automatic sequencing" + return 1 + fi +} + +disable_l2reth_sequencing() { + log_info "Disabling L2RETH automatic sequencing..." + if cast rpc rollupNode_disableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2RETH automatic sequencing disabled" + return 0 + else + log_error "Failed to disable L2RETH automatic sequencing" + return 1 + fi +} + +wait_for_block() { + local rpc_url="$1" + local target_block="$2" + local node_name="$3" + local target_hash="$4" + + log_info "Waiting for $node_name to reach block #$target_block (hash: $target_hash)..." + + local start_time=$(date +%s) + while true; do + local current_time=$(date +%s) + local elapsed=$((current_time - start_time)) + + if [[ $elapsed -gt $SYNC_TIMEOUT ]]; then + log_error "Timeout waiting for $node_name to reach block #$target_block" + return 1 + fi + + local block_info + if block_info=$(get_block_info "$rpc_url"); then + local current_block=$(echo "$block_info" | awk '{print $1}') + local current_hash=$(echo "$block_info" | awk '{print $2}') + + if [[ "$current_block" -ge "$target_block" ]]; then + if [[ "$current_block" -eq "$target_block" && "$current_hash" == "$target_hash" ]]; then + log_success "$node_name reached target block #$target_block (hash: $target_hash)" + return 0 + elif [[ "$current_block" -gt "$target_block" ]]; then + log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)" + return 0 + else + log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash" + fi + fi + fi + + sleep $POLL_INTERVAL + done +} + +check_rpc_connectivity() { + log_info "Checking RPC connectivity..." + + if ! get_block_info "$L2GETH_RPC_URL" >/dev/null; then + log_error "Cannot connect to L2GETH at $L2GETH_RPC_URL" + exit 1 + fi + + if ! get_block_info "$L2RETH_RPC_URL" >/dev/null; then + log_error "Cannot connect to L2RETH at $L2RETH_RPC_URL" + exit 1 + fi + + log_success "Both nodes are accessible" +} + +pre_flight_checks() { + log_info "=== PRE-FLIGHT CHECKS ===" + + check_rpc_connectivity + + # Get initial block states + local l2geth_info=$(get_block_info "$L2GETH_RPC_URL") + local l2reth_info=$(get_block_info "$L2RETH_RPC_URL") + + INITIAL_L2GETH_BLOCK=$(echo "$l2geth_info" | awk '{print $1}') + local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') + INITIAL_L2RETH_BLOCK=$(echo "$l2reth_info" | awk '{print $1}') + local l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}') + + log_info "L2GETH current block: #$INITIAL_L2GETH_BLOCK (hash: $l2geth_hash)" + log_info "L2RETH current block: #$INITIAL_L2RETH_BLOCK (hash: $l2reth_hash)" + + # Check if l2geth is mining + if ! is_l2geth_mining; then + log_error "L2GETH is not currently mining. Please start mining first." + exit 1 + fi + log_success "L2GETH is currently mining" + + # Verify nodes are on the same chain by comparing a recent block + local compare_block=$((INITIAL_L2RETH_BLOCK < INITIAL_L2GETH_BLOCK ? INITIAL_L2RETH_BLOCK : INITIAL_L2GETH_BLOCK)) + if [[ $compare_block -gt 0 ]]; then + local l2geth_compare_hash=$(cast block "$compare_block" --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | grep "^hash" | awk '{print $2}') + local l2reth_compare_hash=$(cast block "$compare_block" --rpc-url "$L2RETH_RPC_URL" 2>/dev/null | grep "^hash" | awk '{print $2}') + + if [[ "$l2geth_compare_hash" != "$l2reth_compare_hash" ]]; then + log_error "Nodes are on different chains! Block #$compare_block hashes differ:" + log_error " L2GETH: $l2geth_compare_hash" + log_error " L2RETH: $l2reth_compare_hash" + exit 1 + fi + log_success "Nodes are on the same chain (verified at block #$compare_block)" + fi + + log_success "Pre-flight checks completed" +} + +print_summary() { + local end_time=$(date +%s) + local total_time=$((end_time - START_TIME)) + + log_info "=== MIGRATION SUMMARY ===" + log_info "Migration completed in ${total_time}s" + log_info "Initial L2GETH block: #$INITIAL_L2GETH_BLOCK" + log_info "Initial L2RETH block: #$INITIAL_L2RETH_BLOCK" + log_info "L2GETH stopped at block: #$L2GETH_STOP_BLOCK" + log_info "L2RETH final block: #$L2RETH_FINAL_BLOCK" + + local final_l2geth_info=$(get_block_info "$L2GETH_RPC_URL") + local final_l2geth_block=$(echo "$final_l2geth_info" | awk '{print $1}') + local final_l2geth_hash=$(echo "$final_l2geth_info" | awk '{print $2}') + log_info "Final L2GETH block: #$final_l2geth_block (hash: $final_l2geth_hash)" + + log_success "Sequencer migration completed successfully!" +} + +main() { + # Check arguments + if [[ $# -ne 1 ]]; then + echo "Usage: $0 " + echo " blocks_to_produce: Number of blocks for L2RETH to produce during migration" + exit 1 + fi + + local blocks_to_produce="$1" + + # Validate blocks_to_produce is a positive integer + if ! [[ "$blocks_to_produce" =~ ^[1-9][0-9]*$ ]]; then + log_error "blocks_to_produce must be a positive integer, got: $blocks_to_produce" + exit 1 + fi + + log_info "Starting sequencer migration: L2GETH -> L2RETH -> L2GETH" + log_info "L2RETH will produce $blocks_to_produce blocks" + + check_env_vars + pre_flight_checks + + # Double check if user wants to proceed + read -p "Proceed with migration? (y/N): " confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + log_warning "Migration aborted by user" + exit 0 + fi + + # Phase 1: Stop L2GETH sequencing + log_info "=== PHASE 1: STOPPING L2GETH SEQUENCING ===" + stop_l2geth_mining + + # Record where L2GETH stopped + local stop_info=$(get_block_info "$L2GETH_RPC_URL") + L2GETH_STOP_BLOCK=$(echo "$stop_info" | awk '{print $1}') + local stop_hash=$(echo "$stop_info" | awk '{print $2}') + log_success "L2GETH sequencing stopped at block #$L2GETH_STOP_BLOCK (hash: $stop_hash)" + + # Phase 2: Wait for L2RETH to sync + log_info "=== PHASE 2: WAITING FOR L2RETH SYNC ===" + wait_for_block "$L2RETH_RPC_URL" "$L2GETH_STOP_BLOCK" "L2RETH" "$stop_hash" + + # Phase 3: Enable L2RETH sequencing and wait for blocks + log_info "=== PHASE 3: L2RETH SEQUENCING ($blocks_to_produce blocks) ===" + enable_l2reth_sequencing + + local target_block=$((L2GETH_STOP_BLOCK + blocks_to_produce)) + log_info "Waiting for L2RETH to produce $blocks_to_produce blocks (target: #$target_block)..." + + # Monitor block production + local current_block=$L2GETH_STOP_BLOCK + while [[ $current_block -lt $target_block ]]; do + sleep $POLL_INTERVAL + local new_block=$(get_block_number "$L2RETH_RPC_URL") + if [[ $new_block -gt $current_block ]]; then + local block_info=$(get_block_info "$L2RETH_RPC_URL") + local block_hash=$(echo "$block_info" | awk '{print $2}') + log_success "L2RETH produced block #$new_block (hash: $block_hash)" + current_block=$new_block + fi + done + + # Phase 4: Stop L2RETH sequencing + log_info "=== PHASE 4: STOPPING L2RETH SEQUENCING ===" + disable_l2reth_sequencing + + # Record final L2RETH block + local final_info=$(get_block_info "$L2RETH_RPC_URL") + L2RETH_FINAL_BLOCK=$(echo "$final_info" | awk '{print $1}') + local final_hash=$(echo "$final_info" | awk '{print $2}') + log_success "L2RETH sequencing stopped at block #$L2RETH_FINAL_BLOCK (hash: $final_hash)" + + # Phase 5: Wait for L2GETH to sync + log_info "=== PHASE 5: WAITING FOR L2GETH SYNC ===" + wait_for_block "$L2GETH_RPC_URL" "$L2RETH_FINAL_BLOCK" "L2GETH" "$final_hash" + + # Phase 6: Resume L2GETH sequencing + log_info "=== PHASE 6: RESUMING L2GETH SEQUENCING ===" + start_l2geth_mining + + # TODO: this could be done with wait for function? + # Wait for at least one new block to confirm + log_info "Waiting for L2GETH to produce at least one new block..." + local confirmation_target=$((L2RETH_FINAL_BLOCK + 1)) + local start_time=$(date +%s) + while true; do + local current_time=$(date +%s) + local elapsed=$((current_time - start_time)) + + if [[ $elapsed -gt 60 ]]; then # 1 minute timeout for first block + log_error "Timeout waiting for L2GETH to produce new block" + exit 1 + fi + + local current_block=$(get_block_number "$L2GETH_RPC_URL") + if [[ $current_block -ge $confirmation_target ]]; then + local confirm_info=$(get_block_info "$L2GETH_RPC_URL") + local confirm_hash=$(echo "$confirm_info" | awk '{print $2}') + log_success "L2GETH sequencing resumed, produced block #$current_block (hash: $confirm_hash)" + break + fi + + sleep $POLL_INTERVAL + done + + print_summary +} + +# Run main function +main "$@" \ No newline at end of file From c49c14264b583790c3689a2aea6e14058f5d2366 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:51:11 +0800 Subject: [PATCH 02/10] add test for sequencer migration script --- tests/tests/migrate_sequencer.rs | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/tests/migrate_sequencer.rs diff --git a/tests/tests/migrate_sequencer.rs b/tests/tests/migrate_sequencer.rs new file mode 100644 index 00000000..f2a0c6f8 --- /dev/null +++ b/tests/tests/migrate_sequencer.rs @@ -0,0 +1,91 @@ +use eyre::Result; +use tests::*; + +/// +/// This integration test validates that blocks can be successfully propagated between +/// different Ethereum client implementations (l2geth and rollup-node) in various +/// network topologies. The test exercises: +/// +/// 1. **Isolated Network Segments**: Initially runs l2geth nodes in isolation, verifying they can +/// produce and sync blocks independently +/// - Topology: `l2geth_follower -> l2geth_sequencer` +/// - l2geth_sequencer produces blocks, l2geth_follower syncs +/// - Rollup nodes remain disconnected at block 0 +/// +/// 2. **Cross-Client Synchronization**: Connects rollup nodes to the l2geth network, ensuring they +/// can catch up to the current chain state +/// - Topology: `[rn_follower, rn_sequencer, l2geth_follower] -> l2geth_sequencer` +/// - All nodes connect to l2geth_sequencer as the single source of truth +/// - Rollup nodes sync from block 0 to current height +/// +/// 3. **Sequencer Handoff**: Transitions block production from l2geth to rollup-node, testing that +/// all nodes stay synchronized during the transition +/// - Topology remains: `[rn_follower, rn_sequencer, l2geth_follower] -> l2geth_sequencer` +/// - Block production switches from l2geth_sequencer to rn_sequencer +/// - All nodes receive new blocks from rn_sequencer via l2geth_sequencer relay +/// +/// 4. **Network Partition Recovery**: Disconnects l2geth nodes, continues production on rollup +/// nodes, then reconnects to verify successful resynchronization +/// - Initial partition: `rn_follower -> rn_sequencer` (isolated rollup network) +/// - l2geth nodes disconnected, fall behind in block height +/// - Reconnection topology: `[l2geth_follower, l2geth_sequencer] -> rn_sequencer` +/// - l2geth nodes catch up by syncing from rn_sequencer +/// +/// 5. **Bidirectional Compatibility**: Returns block production to l2geth after rollup nodes have +/// extended the chain, ensuring backward compatibility +/// - Final topology: `[rn_follower, l2geth_follower, l2geth_sequencer] -> rn_sequencer` +/// - Block production returns to l2geth_sequencer +/// - Validates that l2geth can continue the chain after rollup node blocks +/// +/// The test validates that both client implementations maintain consensus despite +/// network topology changes, sequencer transitions, and temporary network partitions. +/// Each topology change tests different aspects of peer discovery, block gossip, +/// and chain synchronization across heterogeneous client implementations. +#[tokio::test] +async fn docker_test_migrate_sequencer() -> Result<()> { + reth_tracing::init_test_tracing(); + + tracing::info!("=== STARTING docker_test_migrate_sequencer ==="); + let env = DockerComposeEnv::new("docker_test_migrate_sequencer").await?; + + let rn_sequencer = env.get_rn_sequencer_provider().await?; + let rn_follower = env.get_rn_follower_provider().await?; + let l2geth_sequencer = env.get_l2geth_sequencer_provider().await?; + let l2geth_follower = env.get_l2geth_follower_provider().await?; + + let nodes = [&rn_sequencer, &rn_follower, &l2geth_sequencer, &l2geth_follower]; + + // Connect all nodes to each other. + // topology: + // l2geth_follower -> l2geth_sequencer + // l2geth_follower -> rn_sequencer + // rn_follower -> l2geth_sequencer + // rn_follower -> rn_sequencer + // rn_sequencer -> l2geth_sequencer + utils::admin_add_peer(&l2geth_follower, &env.l2geth_sequencer_enode()?).await?; + utils::admin_add_peer(&l2geth_follower, &env.rn_sequencer_enode()?).await?; + utils::admin_add_peer(&rn_follower, &env.l2geth_sequencer_enode()?).await?; + utils::admin_add_peer(&rn_follower, &env.rn_sequencer_enode()?).await?; + utils::admin_add_peer(&rn_sequencer, &env.l2geth_sequencer_enode()?).await?; + + // Enable block production on l2geth sequencer + utils::miner_start(&l2geth_sequencer).await?; + + // Wait for at least 60 blocks to be produced + let target_block = 30; + utils::wait_for_block(&[&l2geth_sequencer], target_block).await?; + + let target_block = 60; + utils::wait_for_block(&nodes, target_block).await?; + utils::assert_blocks_match(&nodes, target_block).await?; + + let target_block = 90; + utils::wait_for_block(&nodes, target_block).await?; + utils::assert_blocks_match(&nodes, target_block).await?; + + let target_block = 120; + utils::wait_for_block(&nodes, target_block).await?; + utils::assert_blocks_match(&nodes, target_block).await?; + + Ok(()) +} From 2e0410a55cf18c7f7c4ca69e952898902f7a24cd Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 23 Sep 2025 15:52:01 +0800 Subject: [PATCH 03/10] add l2geth revert script --- sequencer-migration/common-functions.sh | 203 ++++++++++++++++++ sequencer-migration/migrate-sequencer.sh | 191 ++-------------- sequencer-migration/revert-l2geth-to-block.sh | 203 ++++++++++++++++++ 3 files changed, 419 insertions(+), 178 deletions(-) create mode 100644 sequencer-migration/common-functions.sh create mode 100755 sequencer-migration/revert-l2geth-to-block.sh diff --git a/sequencer-migration/common-functions.sh b/sequencer-migration/common-functions.sh new file mode 100644 index 00000000..c4f5a590 --- /dev/null +++ b/sequencer-migration/common-functions.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +# Common Functions for Sequencer Migration Scripts +# This file contains shared functionality used by multiple scripts + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +SYNC_TIMEOUT=5 # 5 seconds timeout for sync operations +POLL_INTERVAL=0.1 # Poll every 0.1 seconds + +# Helper function to print colored output +log() { + echo -e "${2:-$NC}[$(date '+%H:%M:%S')] $1${NC}" +} + +log_info() { log "$1" "$BLUE"; } +log_success() { log "$1" "$GREEN"; } +log_warning() { log "$1" "$YELLOW"; } +log_error() { log "$1" "$RED"; } + +# Check if required environment variables are set +check_env_vars() { + log_info "Checking environment variables..." + + if [[ -z "${L2GETH_RPC_URL:-}" ]]; then + log_error "L2GETH_RPC_URL environment variable is required" + exit 1 + fi + + if [[ -z "${L2RETH_RPC_URL:-}" ]]; then + log_error "L2RETH_RPC_URL environment variable is required" + exit 1 + fi + + log_success "Environment variables configured" + log_info "L2GETH_RPC_URL: $L2GETH_RPC_URL" + log_info "L2RETH_RPC_URL: $L2RETH_RPC_URL" +} + +# Get block number and hash for a given RPC URL +get_latest_block_info() { + local rpc_url="$1" + local temp_file=$(mktemp) + + if ! cast block latest --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then + rm -f "$temp_file" + return 1 + fi + + local block_number=$(grep "^number" "$temp_file" | awk '{print $2}') + local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}') + + rm -f "$temp_file" + echo "$block_number $block_hash" +} + +# Get block info for a specific block number +get_block_info() { + local rpc_url="$1" + local block_number="$2" + local temp_file=$(mktemp) + + if ! cast block "$block_number" --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then + rm -f "$temp_file" + return 1 + fi + + local block_num=$(grep "^number" "$temp_file" | awk '{print $2}') + local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}') + + rm -f "$temp_file" + echo "$block_num $block_hash" +} + +# Get only block number for a given RPC URL +get_block_number() { + local rpc_url="$1" + cast block latest --rpc-url "$rpc_url" 2>/dev/null | grep "^number" | awk '{print $2}' +} + +# Check if l2geth is mining +is_l2geth_mining() { + local result=$(cast rpc eth_mining --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | tr -d '"') + [[ "$result" == "true" ]] +} + +# Start l2geth mining +start_l2geth_mining() { + log_info "Starting l2geth mining..." + if cast rpc miner_start --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2GETH mining started" + return 0 + else + log_error "Failed to start l2geth mining" + return 1 + fi +} + +# Stop l2geth mining +stop_l2geth_mining() { + log_info "Stopping l2geth mining..." + if cast rpc miner_stop --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2GETH mining stopped" + return 0 + else + log_error "Failed to stop l2geth mining" + return 1 + fi +} + +# Enable l2reth automatic sequencing +enable_l2reth_sequencing() { + log_info "Enabling L2RETH automatic sequencing..." + if cast rpc rollupNode_enableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2RETH automatic sequencing enabled" + return 0 + else + log_error "Failed to enable L2RETH automatic sequencing" + return 1 + fi +} + +# Disable l2reth automatic sequencing +disable_l2reth_sequencing() { + log_info "Disabling L2RETH automatic sequencing..." + if cast rpc rollupNode_disableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then + log_success "L2RETH automatic sequencing disabled" + return 0 + else + log_error "Failed to disable L2RETH automatic sequencing" + return 1 + fi +} + +# Wait for a specific block to be reached +wait_for_block() { + local node_name="$1" + local rpc_url="$2" + local target_block="$3" + local target_hash="$4" + + log_info "Waiting for $node_name to reach block #$target_block (hash: $target_hash)..." + + local start_time=$(date +%s) + while true; do + local current_time=$(date +%s) + local elapsed=$((current_time - start_time)) + local block_info + + if [[ $elapsed -gt $SYNC_TIMEOUT ]]; then + log_error "Timeout waiting for $node_name to reach block #$target_block" + + local current_block=$(echo "$block_info" | awk '{print $1}') + local current_hash=$(echo "$block_info" | awk '{print $2}') + log_error "$node_name is at block $current_block (hash: $current_hash)" + + return 1 + fi + + + if block_info=$(get_latest_block_info "$rpc_url"); then + local current_block=$(echo "$block_info" | awk '{print $1}') + local current_hash=$(echo "$block_info" | awk '{print $2}') + + if [[ "$current_block" -ge "$target_block" ]]; then + if [[ "$current_block" -eq "$target_block" && "$current_hash" == "$target_hash" ]]; then + log_success "$node_name reached target block #$target_block (hash: $target_hash)" + return 0 + elif [[ "$current_block" -gt "$target_block" ]]; then + log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)" + return 0 + else + log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash" + fi + fi + fi + + sleep $POLL_INTERVAL + done +} + +# Check RPC connectivity for both nodes +check_rpc_connectivity() { + log_info "Checking RPC connectivity..." + + if ! get_latest_block_info "$L2GETH_RPC_URL" >/dev/null; then + log_error "Cannot connect to L2GETH at $L2GETH_RPC_URL" + exit 1 + fi + + if ! get_latest_block_info "$L2RETH_RPC_URL" >/dev/null; then + log_error "Cannot connect to L2RETH at $L2RETH_RPC_URL" + exit 1 + fi + + log_success "Both nodes are accessible" +} \ No newline at end of file diff --git a/sequencer-migration/migrate-sequencer.sh b/sequencer-migration/migrate-sequencer.sh index ca03cb51..e36b7714 100755 --- a/sequencer-migration/migrate-sequencer.sh +++ b/sequencer-migration/migrate-sequencer.sh @@ -6,16 +6,8 @@ set -euo pipefail -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Configuration -SYNC_TIMEOUT=5 # 5 seconds timeout for sync operations -POLL_INTERVAL=0.1 # Poll every 0.1 seconds +# Source common functions +source "$(dirname "$0")/common-functions.sh" # Global variables to track state START_TIME=$(date +%s) @@ -24,171 +16,14 @@ INITIAL_L2RETH_BLOCK="" L2GETH_STOP_BLOCK="" L2RETH_FINAL_BLOCK="" -# Helper function to print colored output -log() { - echo -e "${2:-$NC}[$(date '+%H:%M:%S')] $1${NC}" -} - -log_info() { log "$1" "$BLUE"; } -log_success() { log "$1" "$GREEN"; } -log_warning() { log "$1" "$YELLOW"; } -log_error() { log "$1" "$RED"; } - -# Check if required environment variables are set -check_env_vars() { - log_info "Checking environment variables..." - - if [[ -z "${L2GETH_RPC_URL:-}" ]]; then - log_error "L2GETH_RPC_URL environment variable is required" - exit 1 - fi - - if [[ -z "${L2RETH_RPC_URL:-}" ]]; then - log_error "L2RETH_RPC_URL environment variable is required" - exit 1 - fi - - log_success "Environment variables configured" - log_info "L2GETH_RPC_URL: $L2GETH_RPC_URL" - log_info "L2RETH_RPC_URL: $L2RETH_RPC_URL" -} - -# Get block number and hash for a given RPC URL -get_block_info() { - local rpc_url="$1" - local temp_file=$(mktemp) - - if ! cast block latest --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then - rm -f "$temp_file" - return 1 - fi - - local block_number=$(grep "^number" "$temp_file" | awk '{print $2}') - local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}') - - rm -f "$temp_file" - echo "$block_number $block_hash" -} - -# Get only block number for a given RPC URL -get_block_number() { - local rpc_url="$1" - cast block latest --rpc-url "$rpc_url" 2>/dev/null | grep "^number" | awk '{print $2}' -} - -is_l2geth_mining() { - local result=$(cast rpc eth_mining --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | tr -d '"') - [[ "$result" == "true" ]] -} - -start_l2geth_mining() { - log_info "Starting l2geth mining..." - if cast rpc miner_start --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then - log_success "L2GETH mining started" - return 0 - else - log_error "Failed to start l2geth mining" - return 1 - fi -} - -stop_l2geth_mining() { - log_info "Stopping l2geth mining..." - if cast rpc miner_stop --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then - log_success "L2GETH mining stopped" - return 0 - else - log_error "Failed to stop l2geth mining" - return 1 - fi -} - -enable_l2reth_sequencing() { - log_info "Enabling L2RETH automatic sequencing..." - if cast rpc rollupNode_enableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then - log_success "L2RETH automatic sequencing enabled" - return 0 - else - log_error "Failed to enable L2RETH automatic sequencing" - return 1 - fi -} - -disable_l2reth_sequencing() { - log_info "Disabling L2RETH automatic sequencing..." - if cast rpc rollupNode_disableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then - log_success "L2RETH automatic sequencing disabled" - return 0 - else - log_error "Failed to disable L2RETH automatic sequencing" - return 1 - fi -} - -wait_for_block() { - local rpc_url="$1" - local target_block="$2" - local node_name="$3" - local target_hash="$4" - - log_info "Waiting for $node_name to reach block #$target_block (hash: $target_hash)..." - - local start_time=$(date +%s) - while true; do - local current_time=$(date +%s) - local elapsed=$((current_time - start_time)) - - if [[ $elapsed -gt $SYNC_TIMEOUT ]]; then - log_error "Timeout waiting for $node_name to reach block #$target_block" - return 1 - fi - - local block_info - if block_info=$(get_block_info "$rpc_url"); then - local current_block=$(echo "$block_info" | awk '{print $1}') - local current_hash=$(echo "$block_info" | awk '{print $2}') - - if [[ "$current_block" -ge "$target_block" ]]; then - if [[ "$current_block" -eq "$target_block" && "$current_hash" == "$target_hash" ]]; then - log_success "$node_name reached target block #$target_block (hash: $target_hash)" - return 0 - elif [[ "$current_block" -gt "$target_block" ]]; then - log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)" - return 0 - else - log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash" - fi - fi - fi - - sleep $POLL_INTERVAL - done -} - -check_rpc_connectivity() { - log_info "Checking RPC connectivity..." - - if ! get_block_info "$L2GETH_RPC_URL" >/dev/null; then - log_error "Cannot connect to L2GETH at $L2GETH_RPC_URL" - exit 1 - fi - - if ! get_block_info "$L2RETH_RPC_URL" >/dev/null; then - log_error "Cannot connect to L2RETH at $L2RETH_RPC_URL" - exit 1 - fi - - log_success "Both nodes are accessible" -} - pre_flight_checks() { log_info "=== PRE-FLIGHT CHECKS ===" check_rpc_connectivity # Get initial block states - local l2geth_info=$(get_block_info "$L2GETH_RPC_URL") - local l2reth_info=$(get_block_info "$L2RETH_RPC_URL") + local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local l2reth_info=$(get_latest_block_info "$L2RETH_RPC_URL") INITIAL_L2GETH_BLOCK=$(echo "$l2geth_info" | awk '{print $1}') local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') @@ -234,7 +69,7 @@ print_summary() { log_info "L2GETH stopped at block: #$L2GETH_STOP_BLOCK" log_info "L2RETH final block: #$L2RETH_FINAL_BLOCK" - local final_l2geth_info=$(get_block_info "$L2GETH_RPC_URL") + local final_l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") local final_l2geth_block=$(echo "$final_l2geth_info" | awk '{print $1}') local final_l2geth_hash=$(echo "$final_l2geth_info" | awk '{print $2}') log_info "Final L2GETH block: #$final_l2geth_block (hash: $final_l2geth_hash)" @@ -276,14 +111,14 @@ main() { stop_l2geth_mining # Record where L2GETH stopped - local stop_info=$(get_block_info "$L2GETH_RPC_URL") + local stop_info=$(get_latest_block_info "$L2GETH_RPC_URL") L2GETH_STOP_BLOCK=$(echo "$stop_info" | awk '{print $1}') local stop_hash=$(echo "$stop_info" | awk '{print $2}') log_success "L2GETH sequencing stopped at block #$L2GETH_STOP_BLOCK (hash: $stop_hash)" # Phase 2: Wait for L2RETH to sync log_info "=== PHASE 2: WAITING FOR L2RETH SYNC ===" - wait_for_block "$L2RETH_RPC_URL" "$L2GETH_STOP_BLOCK" "L2RETH" "$stop_hash" + wait_for_block "L2RETH" "$L2RETH_RPC_URL" "$L2GETH_STOP_BLOCK" "$stop_hash" # Phase 3: Enable L2RETH sequencing and wait for blocks log_info "=== PHASE 3: L2RETH SEQUENCING ($blocks_to_produce blocks) ===" @@ -298,7 +133,7 @@ main() { sleep $POLL_INTERVAL local new_block=$(get_block_number "$L2RETH_RPC_URL") if [[ $new_block -gt $current_block ]]; then - local block_info=$(get_block_info "$L2RETH_RPC_URL") + local block_info=$(get_latest_block_info "$L2RETH_RPC_URL") local block_hash=$(echo "$block_info" | awk '{print $2}') log_success "L2RETH produced block #$new_block (hash: $block_hash)" current_block=$new_block @@ -310,20 +145,20 @@ main() { disable_l2reth_sequencing # Record final L2RETH block - local final_info=$(get_block_info "$L2RETH_RPC_URL") + local final_info=$(get_latest_block_info "$L2RETH_RPC_URL") L2RETH_FINAL_BLOCK=$(echo "$final_info" | awk '{print $1}') local final_hash=$(echo "$final_info" | awk '{print $2}') log_success "L2RETH sequencing stopped at block #$L2RETH_FINAL_BLOCK (hash: $final_hash)" # Phase 5: Wait for L2GETH to sync log_info "=== PHASE 5: WAITING FOR L2GETH SYNC ===" - wait_for_block "$L2GETH_RPC_URL" "$L2RETH_FINAL_BLOCK" "L2GETH" "$final_hash" + wait_for_block "L2GETH" "$L2GETH_RPC_URL" "$L2RETH_FINAL_BLOCK" "$final_hash" # Phase 6: Resume L2GETH sequencing log_info "=== PHASE 6: RESUMING L2GETH SEQUENCING ===" start_l2geth_mining - # TODO: this could be done with wait for function? + # TODO: change wait for method so that this can be done as well. # Wait for at least one new block to confirm log_info "Waiting for L2GETH to produce at least one new block..." local confirmation_target=$((L2RETH_FINAL_BLOCK + 1)) @@ -332,14 +167,14 @@ main() { local current_time=$(date +%s) local elapsed=$((current_time - start_time)) - if [[ $elapsed -gt 60 ]]; then # 1 minute timeout for first block + if [[ $elapsed -gt 5 ]]; then # 5 seconds timeout for first block log_error "Timeout waiting for L2GETH to produce new block" exit 1 fi local current_block=$(get_block_number "$L2GETH_RPC_URL") if [[ $current_block -ge $confirmation_target ]]; then - local confirm_info=$(get_block_info "$L2GETH_RPC_URL") + local confirm_info=$(get_latest_block_info "$L2GETH_RPC_URL") local confirm_hash=$(echo "$confirm_info" | awk '{print $2}') log_success "L2GETH sequencing resumed, produced block #$current_block (hash: $confirm_hash)" break diff --git a/sequencer-migration/revert-l2geth-to-block.sh b/sequencer-migration/revert-l2geth-to-block.sh new file mode 100755 index 00000000..9dd9fbef --- /dev/null +++ b/sequencer-migration/revert-l2geth-to-block.sh @@ -0,0 +1,203 @@ +#!/bin/bash + +# L2GETH Block Revert Script +# Reverts l2geth to a specific block number using debug_setHead RPC +# Usage: ./revert-l2geth-to-block.sh + +set -euo pipefail + +# Source common functions +source "$(dirname "$0")/common-functions.sh" + +# Script-specific configuration +BLOCK_PRODUCTION_TIMEOUT=60 # 60 seconds timeout for new block production + +# Global variables to track state +START_TIME=$(date +%s) +TARGET_BLOCK="" +TARGET_HASH="" + +# Reset l2geth to a specific block using debug_setHead +reset_l2geth_to_block() { + local block_number="$1" + + log_info "Resetting l2geth to block #$block_number using debug_setHead..." + + # Convert block number to hex format for debug_setHead + local block_hex=$(printf "0x%x" "$block_number") + + if cast rpc debug_setHead --rpc-url "$L2GETH_RPC_URL" "$block_hex" >/dev/null 2>&1; then + log_success "L2GETH reset to block #$block_number" + return 0 + else + log_error "Failed to reset l2geth to block #$block_number" + return 1 + fi +} + +# Wait for new block production after reset +wait_for_new_block_production() { + local starting_block="$1" + local expected_next_block=$((starting_block + 1)) + + log_info "Waiting for l2geth to produce new blocks starting from #$expected_next_block..." + + local start_time=$(date +%s) + while true; do + local current_time=$(date +%s) + local elapsed=$((current_time - start_time)) + + if [[ $elapsed -gt $BLOCK_PRODUCTION_TIMEOUT ]]; then + log_error "Timeout waiting for l2geth to produce new block (waited ${BLOCK_PRODUCTION_TIMEOUT}s)" + return 1 + fi + + local current_block=$(get_block_number "$L2GETH_RPC_URL") + if [[ $current_block -ge $expected_next_block ]]; then + local block_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local block_hash=$(echo "$block_info" | awk '{print $2}') + log_success "L2GETH produced new block #$current_block (hash: $block_hash)" + return 0 + fi + + sleep $POLL_INTERVAL + done +} + + +pre_flight_checks() { + log_info "=== PRE-FLIGHT CHECKS ===" + + check_rpc_connectivity + + # Get current block states + local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local l2reth_info=$(get_latest_block_info "$L2RETH_RPC_URL") + + local current_l2geth_block=$(echo "$l2geth_info" | awk '{print $1}') + local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') + local current_l2reth_block=$(echo "$l2reth_info" | awk '{print $1}') + local l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}') + + log_info "L2GETH current block: #$current_l2geth_block (hash: $l2geth_hash)" + log_info "L2RETH current block: #$current_l2reth_block (hash: $l2reth_hash)" + + # Validate target block exists and is reachable + if [[ $TARGET_BLOCK -gt $current_l2geth_block ]]; then + log_error "Target block #$TARGET_BLOCK is greater than current l2geth block #$current_l2geth_block" + log_error "Can only revert to an existing block" + exit 1 + fi + + # Get target block info to verify it exists + local target_info + if ! target_info=$(get_block_info "$L2GETH_RPC_URL" "$TARGET_BLOCK"); then + log_error "Target block #$TARGET_BLOCK not found in l2geth" + exit 1 + fi + + TARGET_HASH=$(echo "$target_info" | awk '{print $2}') + log_info "Target block #$TARGET_BLOCK exists (hash: $TARGET_HASH)" + + log_success "Pre-flight checks completed" +} + +print_summary() { + local end_time=$(date +%s) + local total_time=$((end_time - START_TIME)) + + log_info "=== REVERT SUMMARY ===" + log_info "Revert completed in ${total_time}s" + log_info "Target block: #$TARGET_BLOCK (hash: $TARGET_HASH)" + + local final_l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local final_l2geth_block=$(echo "$final_l2geth_info" | awk '{print $1}') + local final_l2geth_hash=$(echo "$final_l2geth_info" | awk '{print $2}') + log_info "Final L2GETH block: #$final_l2geth_block (hash: $final_l2geth_hash)" + + log_success "L2GETH revert completed successfully!" +} + +main() { + # Check arguments + if [[ $# -ne 1 ]]; then + echo "Usage: $0 " + echo " block_number: Block number to revert l2geth to" + exit 1 + fi + + TARGET_BLOCK="$1" + + # Validate target block is a non-negative integer + if ! [[ "$TARGET_BLOCK" =~ ^[0-9]+$ ]]; then + log_error "block_number must be a non-negative integer, got: $TARGET_BLOCK" + exit 1 + fi + + log_info "Starting l2geth revert to block #$TARGET_BLOCK" + + check_env_vars + pre_flight_checks + + # Phase 1: Disable sequencing on both nodes + log_info "=== PHASE 1: DISABLING SEQUENCING ===" + + # Disable l2reth sequencing first (safety measure) + disable_l2reth_sequencing + + # Disable l2geth mining + stop_l2geth_mining + + # Phase 2: Show current state and get confirmation + log_info "=== PHASE 2: CONFIRMATION ===" + + local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local l2reth_info=$(get_latest_block_info "$L2RETH_RPC_URL") + + local current_l2geth_block=$(echo "$l2geth_info" | awk '{print $1}') + local current_l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') + local current_l2reth_block=$(echo "$l2reth_info" | awk '{print $1}') + local current_l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}') + + log_info "Current L2GETH block: #$current_l2geth_block (hash: $current_l2geth_hash)" + log_info "Current L2RETH block: #$current_l2reth_block (hash: $current_l2reth_hash)" + log_warning "Will revert L2GETH to block #$TARGET_BLOCK (hash: $TARGET_HASH)" + + read -p "Continue with revert? (y/N): " confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + log_warning "Revert aborted by user" + # Re-enable mining before exit + start_l2geth_mining + exit 0 + fi + + # Phase 3: Reset l2geth to target block + log_info "=== PHASE 3: RESETTING L2GETH ===" + reset_l2geth_to_block "$TARGET_BLOCK" + + # Verify the reset was successful + local reset_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local reset_block=$(echo "$reset_info" | awk '{print $1}') + local reset_hash=$(echo "$reset_info" | awk '{print $2}') + + if [[ $reset_block -eq $TARGET_BLOCK ]]; then + log_success "L2GETH successfully reset to block #$reset_block (hash: $reset_hash)" + else + log_error "Reset verification failed: expected block #$TARGET_BLOCK, got #$reset_block" + exit 1 + fi + + # Phase 4: Re-enable l2geth sequencing + log_info "=== PHASE 4: ENABLING L2GETH SEQUENCING ===" + start_l2geth_mining + + # Phase 5: Wait for new block production + log_info "=== PHASE 5: MONITORING NEW BLOCK PRODUCTION ===" + # TODO: combine with wait_for_block ? + wait_for_new_block_production "$TARGET_BLOCK" + + print_summary +} + +# Run main function +main "$@" \ No newline at end of file From 9d7bd3c5c2ff0c9d48b9975e97e805321c3b55eb Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 23 Sep 2025 15:52:57 +0800 Subject: [PATCH 04/10] add more documentation --- sequencer-migration/README.md | 13 +++++++++++-- tests/src/utils.rs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sequencer-migration/README.md b/sequencer-migration/README.md index 51648758..e7dcad59 100644 --- a/sequencer-migration/README.md +++ b/sequencer-migration/README.md @@ -1,5 +1,4 @@ # Scroll Sequencer Migration - This module contains documentation and scripts for Scroll's sequencer migration from `l2geth` to rollup node (RN) aka `l2reth`. ### Risks @@ -25,11 +24,21 @@ The high-level flow of the transition will look like this: 9. Turn on `l2geth` sequencingx ### Testing locally +To test locally run the test `docker_test_migrate_sequencer` and execute the `migrate-sequencer.sh` script. ```bash # this test runs for ~60 seconds and starts with l2geth sequencing and expects all nodes to reach the same block at block number 120. RUST_LOG=info,docker-compose=off cargo test --package tests --test migrate_sequencer -- docker_test_migrate_sequencer --exact --show-output source local.env -./migrate-sequencer.sh +./migrate-sequencer.sh + +# if necessary, reset l2geth block height and start sequencing on l2geth. +./revert-l2geth-to-block.sh ``` +**Simulating failure case**: +- To simulate the case where `l2reth` produces invalid blocks we can adjust to `--builder.gaslimit=40000000` in `launch_rollup_node_sequencer.bash`. This will produce a block with a too big jump of the gas limit and `l2geth` will reject it. In a simulation we can then "revert" `l2geth` to its latest block and start sequencing on `l2geth` again. +- Continuing on the above case we can fabricate a L2 reorg by simply resetting to any previous block. For all `l2geth` nodes the reorg will be shallow (ie before the invalid `l2reth` blocks) and for `l2reth` it will be deeper (ie all `l2reth` produced blocks + reset to previous block). + +TODO: how to run with Docker + diff --git a/tests/src/utils.rs b/tests/src/utils.rs index fc20bc0c..06ae420a 100644 --- a/tests/src/utils.rs +++ b/tests/src/utils.rs @@ -48,7 +48,7 @@ pub async fn miner_stop(provider: &NamedProvider) -> Result<()> { /// * `Ok(())` if all nodes reach the target block within the timeout /// * `Err` if timeout is reached or any provider call fails pub async fn wait_for_block(nodes: &[&NamedProvider], target_block: u64) -> Result<()> { - let timeout_duration = Duration::from_secs(30); + let timeout_duration = Duration::from_secs(60); let timeout_secs = timeout_duration.as_secs(); tracing::info!( From 08b0f0f52c42b77eb57f1f7f2b543452a68836ae Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:59:49 +0800 Subject: [PATCH 05/10] simplify scripts and remove duplicate code --- sequencer-migration/common-functions.sh | 40 ++++++++++++++----- sequencer-migration/migrate-sequencer.sh | 29 +++----------- sequencer-migration/revert-l2geth-to-block.sh | 38 +++--------------- tests/tests/migrate_sequencer.rs | 40 ------------------- 4 files changed, 40 insertions(+), 107 deletions(-) diff --git a/sequencer-migration/common-functions.sh b/sequencer-migration/common-functions.sh index c4f5a590..d12da7ee 100644 --- a/sequencer-migration/common-functions.sh +++ b/sequencer-migration/common-functions.sh @@ -139,13 +139,22 @@ disable_l2reth_sequencing() { } # Wait for a specific block to be reached +# Parameters: +# $1: node_name - Human readable name for logging +# $2: rpc_url - RPC URL to query +# $3: target_block - Block number to wait for +# $4: target_hash (optional) - If provided, will verify exact hash match wait_for_block() { local node_name="$1" local rpc_url="$2" local target_block="$3" local target_hash="$4" - log_info "Waiting for $node_name to reach block #$target_block (hash: $target_hash)..." + if [[ -n "$target_hash" ]]; then + log_info "Waiting for $node_name to reach block #$target_block (hash: $target_hash)..." + else + log_info "Waiting for $node_name to reach block #$target_block or higher..." + fi local start_time=$(date +%s) while true; do @@ -156,9 +165,11 @@ wait_for_block() { if [[ $elapsed -gt $SYNC_TIMEOUT ]]; then log_error "Timeout waiting for $node_name to reach block #$target_block" - local current_block=$(echo "$block_info" | awk '{print $1}') - local current_hash=$(echo "$block_info" | awk '{print $2}') - log_error "$node_name is at block $current_block (hash: $current_hash)" + if block_info=$(get_latest_block_info "$rpc_url" 2>/dev/null); then + local current_block=$(echo "$block_info" | awk '{print $1}') + local current_hash=$(echo "$block_info" | awk '{print $2}') + log_error "$node_name is at block $current_block (hash: $current_hash)" + fi return 1 fi @@ -169,14 +180,21 @@ wait_for_block() { local current_hash=$(echo "$block_info" | awk '{print $2}') if [[ "$current_block" -ge "$target_block" ]]; then - if [[ "$current_block" -eq "$target_block" && "$current_hash" == "$target_hash" ]]; then - log_success "$node_name reached target block #$target_block (hash: $target_hash)" - return 0 - elif [[ "$current_block" -gt "$target_block" ]]; then - log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)" - return 0 + if [[ -n "$target_hash" ]]; then + # Hash verification mode + if [[ "$current_block" -eq "$target_block" && "$current_hash" == "$target_hash" ]]; then + log_success "$node_name reached target block #$target_block (hash: $target_hash)" + return 0 + elif [[ "$current_block" -gt "$target_block" ]]; then + log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)" + return 0 + else + log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash" + fi else - log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash" + # Block number only mode + log_success "$node_name reached block #$current_block (hash: $current_hash)" + return 0 fi fi fi diff --git a/sequencer-migration/migrate-sequencer.sh b/sequencer-migration/migrate-sequencer.sh index e36b7714..15987ec0 100755 --- a/sequencer-migration/migrate-sequencer.sh +++ b/sequencer-migration/migrate-sequencer.sh @@ -158,30 +158,13 @@ main() { log_info "=== PHASE 6: RESUMING L2GETH SEQUENCING ===" start_l2geth_mining - # TODO: change wait for method so that this can be done as well. - # Wait for at least one new block to confirm - log_info "Waiting for L2GETH to produce at least one new block..." + # Wait for at least one new block to confirm sequencing resumed local confirmation_target=$((L2RETH_FINAL_BLOCK + 1)) - local start_time=$(date +%s) - while true; do - local current_time=$(date +%s) - local elapsed=$((current_time - start_time)) - - if [[ $elapsed -gt 5 ]]; then # 5 seconds timeout for first block - log_error "Timeout waiting for L2GETH to produce new block" - exit 1 - fi - - local current_block=$(get_block_number "$L2GETH_RPC_URL") - if [[ $current_block -ge $confirmation_target ]]; then - local confirm_info=$(get_latest_block_info "$L2GETH_RPC_URL") - local confirm_hash=$(echo "$confirm_info" | awk '{print $2}') - log_success "L2GETH sequencing resumed, produced block #$current_block (hash: $confirm_hash)" - break - fi - - sleep $POLL_INTERVAL - done + if ! wait_for_block "L2GETH" "$L2GETH_RPC_URL" "$confirmation_target" ""; then + log_error "L2GETH failed to produce new block after resuming sequencing" + exit 1 + fi + log_success "L2GETH sequencing resumed successfully" print_summary } diff --git a/sequencer-migration/revert-l2geth-to-block.sh b/sequencer-migration/revert-l2geth-to-block.sh index 9dd9fbef..b22e39f9 100755 --- a/sequencer-migration/revert-l2geth-to-block.sh +++ b/sequencer-migration/revert-l2geth-to-block.sh @@ -9,9 +9,6 @@ set -euo pipefail # Source common functions source "$(dirname "$0")/common-functions.sh" -# Script-specific configuration -BLOCK_PRODUCTION_TIMEOUT=60 # 60 seconds timeout for new block production - # Global variables to track state START_TIME=$(date +%s) TARGET_BLOCK="" @@ -35,34 +32,6 @@ reset_l2geth_to_block() { fi } -# Wait for new block production after reset -wait_for_new_block_production() { - local starting_block="$1" - local expected_next_block=$((starting_block + 1)) - - log_info "Waiting for l2geth to produce new blocks starting from #$expected_next_block..." - - local start_time=$(date +%s) - while true; do - local current_time=$(date +%s) - local elapsed=$((current_time - start_time)) - - if [[ $elapsed -gt $BLOCK_PRODUCTION_TIMEOUT ]]; then - log_error "Timeout waiting for l2geth to produce new block (waited ${BLOCK_PRODUCTION_TIMEOUT}s)" - return 1 - fi - - local current_block=$(get_block_number "$L2GETH_RPC_URL") - if [[ $current_block -ge $expected_next_block ]]; then - local block_info=$(get_latest_block_info "$L2GETH_RPC_URL") - local block_hash=$(echo "$block_info" | awk '{print $2}') - log_success "L2GETH produced new block #$current_block (hash: $block_hash)" - return 0 - fi - - sleep $POLL_INTERVAL - done -} pre_flight_checks() { @@ -193,8 +162,11 @@ main() { # Phase 5: Wait for new block production log_info "=== PHASE 5: MONITORING NEW BLOCK PRODUCTION ===" - # TODO: combine with wait_for_block ? - wait_for_new_block_production "$TARGET_BLOCK" + local expected_next_block=$((TARGET_BLOCK + 1)) + if ! wait_for_block "L2GETH" "$L2GETH_RPC_URL" "$expected_next_block" ""; then + log_error "L2GETH failed to produce new block after reset" + exit 1 + fi print_summary } diff --git a/tests/tests/migrate_sequencer.rs b/tests/tests/migrate_sequencer.rs index f2a0c6f8..ae679d84 100644 --- a/tests/tests/migrate_sequencer.rs +++ b/tests/tests/migrate_sequencer.rs @@ -1,46 +1,6 @@ use eyre::Result; use tests::*; -/// -/// This integration test validates that blocks can be successfully propagated between -/// different Ethereum client implementations (l2geth and rollup-node) in various -/// network topologies. The test exercises: -/// -/// 1. **Isolated Network Segments**: Initially runs l2geth nodes in isolation, verifying they can -/// produce and sync blocks independently -/// - Topology: `l2geth_follower -> l2geth_sequencer` -/// - l2geth_sequencer produces blocks, l2geth_follower syncs -/// - Rollup nodes remain disconnected at block 0 -/// -/// 2. **Cross-Client Synchronization**: Connects rollup nodes to the l2geth network, ensuring they -/// can catch up to the current chain state -/// - Topology: `[rn_follower, rn_sequencer, l2geth_follower] -> l2geth_sequencer` -/// - All nodes connect to l2geth_sequencer as the single source of truth -/// - Rollup nodes sync from block 0 to current height -/// -/// 3. **Sequencer Handoff**: Transitions block production from l2geth to rollup-node, testing that -/// all nodes stay synchronized during the transition -/// - Topology remains: `[rn_follower, rn_sequencer, l2geth_follower] -> l2geth_sequencer` -/// - Block production switches from l2geth_sequencer to rn_sequencer -/// - All nodes receive new blocks from rn_sequencer via l2geth_sequencer relay -/// -/// 4. **Network Partition Recovery**: Disconnects l2geth nodes, continues production on rollup -/// nodes, then reconnects to verify successful resynchronization -/// - Initial partition: `rn_follower -> rn_sequencer` (isolated rollup network) -/// - l2geth nodes disconnected, fall behind in block height -/// - Reconnection topology: `[l2geth_follower, l2geth_sequencer] -> rn_sequencer` -/// - l2geth nodes catch up by syncing from rn_sequencer -/// -/// 5. **Bidirectional Compatibility**: Returns block production to l2geth after rollup nodes have -/// extended the chain, ensuring backward compatibility -/// - Final topology: `[rn_follower, l2geth_follower, l2geth_sequencer] -> rn_sequencer` -/// - Block production returns to l2geth_sequencer -/// - Validates that l2geth can continue the chain after rollup node blocks -/// -/// The test validates that both client implementations maintain consensus despite -/// network topology changes, sequencer transitions, and temporary network partitions. -/// Each topology change tests different aspects of peer discovery, block gossip, -/// and chain synchronization across heterogeneous client implementations. #[tokio::test] async fn docker_test_migrate_sequencer() -> Result<()> { reth_tracing::init_test_tracing(); From 218e953e56a44c396d1b4b245b9d30173cb1a403 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 24 Sep 2025 08:27:35 +0800 Subject: [PATCH 06/10] address review comments --- sequencer-migration/common-functions.sh | 73 ++++++++++++++++-------- sequencer-migration/migrate-sequencer.sh | 33 +++++++++-- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/sequencer-migration/common-functions.sh b/sequencer-migration/common-functions.sh index d12da7ee..98260e37 100644 --- a/sequencer-migration/common-functions.sh +++ b/sequencer-migration/common-functions.sh @@ -43,45 +43,59 @@ check_env_vars() { log_info "L2RETH_RPC_URL: $L2RETH_RPC_URL" } -# Get block number and hash for a given RPC URL -get_latest_block_info() { +# Get block number and hash for a given RPC URL and block identifier +# Parameters: +# $1: rpc_url - The RPC endpoint URL +# $2: block_identifier - Block number or "latest" (default: "latest") +get_block_info() { local rpc_url="$1" - local temp_file=$(mktemp) + local block_identifier="${2:-latest}" + local block_data - if ! cast block latest --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then - rm -f "$temp_file" + if ! block_data=$(cast block "$block_identifier" --json --rpc-url "$rpc_url" 2>/dev/null); then return 1 fi - local block_number=$(grep "^number" "$temp_file" | awk '{print $2}') - local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}') + local block_number=$(echo "$block_data" | jq -r '.number // empty') + local block_hash=$(echo "$block_data" | jq -r '.hash // empty') + + if [[ -z "$block_number" || -z "$block_hash" || "$block_number" == "null" || "$block_hash" == "null" ]]; then + return 1 + fi - rm -f "$temp_file" echo "$block_number $block_hash" } -# Get block info for a specific block number -get_block_info() { +# Get latest block info (convenience function) +get_latest_block_info() { + get_block_info "$1" "latest" +} + +# Get only block number for a given RPC URL +get_block_number() { local rpc_url="$1" - local block_number="$2" - local temp_file=$(mktemp) + local block_data - if ! cast block "$block_number" --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then - rm -f "$temp_file" + if ! block_data=$(cast block latest --json --rpc-url "$rpc_url" 2>/dev/null); then return 1 fi - local block_num=$(grep "^number" "$temp_file" | awk '{print $2}') - local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}') + local block_number=$(echo "$block_data" | jq -r '.number // empty') - rm -f "$temp_file" - echo "$block_num $block_hash" + if [[ -z "$block_number" || "$block_number" == "null" ]]; then + return 1 + fi + + echo "$block_number" } -# Get only block number for a given RPC URL -get_block_number() { +# Get chain ID for a given RPC URL +get_chain_id() { local rpc_url="$1" - cast block latest --rpc-url "$rpc_url" 2>/dev/null | grep "^number" | awk '{print $2}' + + if ! cast chain-id --rpc-url "$rpc_url" 2>/dev/null; then + return 1 + fi } # Check if l2geth is mining @@ -186,8 +200,21 @@ wait_for_block() { log_success "$node_name reached target block #$target_block (hash: $target_hash)" return 0 elif [[ "$current_block" -gt "$target_block" ]]; then - log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)" - return 0 + # Verify the target block hash even though we surpassed it + local target_block_info=$(get_block_info "$rpc_url" "$target_block") + if [[ -n "$target_block_info" ]]; then + local actual_target_hash=$(echo "$target_block_info" | awk '{print $2}') + if [[ "$actual_target_hash" == "$target_hash" ]]; then + log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash), target block verified" + return 0 + else + log_error "$node_name surpassed target but target block #$target_block hash mismatch: expected $target_hash, got $actual_target_hash" + return 1 + fi + else + log_error "$node_name surpassed target but failed to verify target block #$target_block" + return 1 + fi else log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash" fi diff --git a/sequencer-migration/migrate-sequencer.sh b/sequencer-migration/migrate-sequencer.sh index 15987ec0..90e4c2fd 100755 --- a/sequencer-migration/migrate-sequencer.sh +++ b/sequencer-migration/migrate-sequencer.sh @@ -40,11 +40,36 @@ pre_flight_checks() { fi log_success "L2GETH is currently mining" - # Verify nodes are on the same chain by comparing a recent block + # Verify nodes are on the same chain by comparing chain IDs + local l2geth_chain_id=$(get_chain_id "$L2GETH_RPC_URL") + local l2reth_chain_id=$(get_chain_id "$L2RETH_RPC_URL") + + if [[ -z "$l2geth_chain_id" || -z "$l2reth_chain_id" ]]; then + log_error "Failed to retrieve chain IDs from one or both nodes" + exit 1 + fi + + if [[ "$l2geth_chain_id" != "$l2reth_chain_id" ]]; then + log_error "Nodes are on different chains! Chain IDs differ:" + log_error " L2GETH: $l2geth_chain_id" + log_error " L2RETH: $l2reth_chain_id" + exit 1 + fi + log_success "Nodes are on the same chain (Chain ID: $l2geth_chain_id)" + + # Verify nodes are on the same chain by comparing a recent block hash local compare_block=$((INITIAL_L2RETH_BLOCK < INITIAL_L2GETH_BLOCK ? INITIAL_L2RETH_BLOCK : INITIAL_L2GETH_BLOCK)) if [[ $compare_block -gt 0 ]]; then - local l2geth_compare_hash=$(cast block "$compare_block" --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | grep "^hash" | awk '{print $2}') - local l2reth_compare_hash=$(cast block "$compare_block" --rpc-url "$L2RETH_RPC_URL" 2>/dev/null | grep "^hash" | awk '{print $2}') + local l2geth_compare_info=$(get_block_info "$L2GETH_RPC_URL" "$compare_block") + local l2reth_compare_info=$(get_block_info "$L2RETH_RPC_URL" "$compare_block") + + if [[ -z "$l2geth_compare_info" || -z "$l2reth_compare_info" ]]; then + log_error "Failed to retrieve block #$compare_block from one or both nodes" + exit 1 + fi + + local l2geth_compare_hash=$(echo "$l2geth_compare_info" | awk '{print $2}') + local l2reth_compare_hash=$(echo "$l2reth_compare_info" | awk '{print $2}') if [[ "$l2geth_compare_hash" != "$l2reth_compare_hash" ]]; then log_error "Nodes are on different chains! Block #$compare_block hashes differ:" @@ -52,7 +77,7 @@ pre_flight_checks() { log_error " L2RETH: $l2reth_compare_hash" exit 1 fi - log_success "Nodes are on the same chain (verified at block #$compare_block)" + log_success "Block hash verification passed at block #$compare_block" fi log_success "Pre-flight checks completed" From 0301e40803651c5973804df2ed07be0e342c2618 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 24 Sep 2025 08:55:18 +0800 Subject: [PATCH 07/10] simplify more --- sequencer-migration/common-functions.sh | 58 ++++++++++++---- sequencer-migration/migrate-sequencer.sh | 66 +------------------ sequencer-migration/revert-l2geth-to-block.sh | 20 ++---- 3 files changed, 54 insertions(+), 90 deletions(-) diff --git a/sequencer-migration/common-functions.sh b/sequencer-migration/common-functions.sh index 98260e37..67af1c08 100644 --- a/sequencer-migration/common-functions.sh +++ b/sequencer-migration/common-functions.sh @@ -56,13 +56,16 @@ get_block_info() { return 1 fi - local block_number=$(echo "$block_data" | jq -r '.number // empty') + local block_number_hex=$(echo "$block_data" | jq -r '.number // empty') local block_hash=$(echo "$block_data" | jq -r '.hash // empty') - if [[ -z "$block_number" || -z "$block_hash" || "$block_number" == "null" || "$block_hash" == "null" ]]; then + if [[ -z "$block_number_hex" || -z "$block_hash" || "$block_number_hex" == "null" || "$block_hash" == "null" ]]; then return 1 fi + # Convert hex to decimal + local block_number=$(echo "$block_number_hex" | xargs cast to-dec) + echo "$block_number $block_hash" } @@ -74,19 +77,14 @@ get_latest_block_info() { # Get only block number for a given RPC URL get_block_number() { local rpc_url="$1" - local block_data - - if ! block_data=$(cast block latest --json --rpc-url "$rpc_url" 2>/dev/null); then - return 1 - fi + local block_info - local block_number=$(echo "$block_data" | jq -r '.number // empty') - - if [[ -z "$block_number" || "$block_number" == "null" ]]; then + if ! block_info=$(get_block_info "$rpc_url" "latest"); then return 1 fi - echo "$block_number" + # Extract just the block number (first field) + echo "$block_info" | awk '{print $1}' } # Get chain ID for a given RPC URL @@ -245,4 +243,42 @@ check_rpc_connectivity() { fi log_success "Both nodes are accessible" +} + +# Common pre-flight checks for sequencer migration scripts +perform_pre_flight_checks() { + log_info "=== PRE-FLIGHT CHECKS ===" + + check_rpc_connectivity + + # Get current block states + local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local l2reth_info=$(get_latest_block_info "$L2RETH_RPC_URL") + + local current_l2geth_block=$(echo "$l2geth_info" | awk '{print $1}') + local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') + local current_l2reth_block=$(echo "$l2reth_info" | awk '{print $1}') + local l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}') + + log_info "L2GETH current block: #$current_l2geth_block (hash: $l2geth_hash)" + log_info "L2RETH current block: #$current_l2reth_block (hash: $l2reth_hash)" + + # Verify nodes are on the same chain by comparing chain IDs + local l2geth_chain_id=$(get_chain_id "$L2GETH_RPC_URL") + local l2reth_chain_id=$(get_chain_id "$L2RETH_RPC_URL") + + if [[ -z "$l2geth_chain_id" || -z "$l2reth_chain_id" ]]; then + log_error "Failed to retrieve chain IDs from one or both nodes" + exit 1 + fi + + if [[ "$l2geth_chain_id" != "$l2reth_chain_id" ]]; then + log_error "Nodes are on different chains! Chain IDs differ:" + log_error " L2GETH: $l2geth_chain_id" + log_error " L2RETH: $l2reth_chain_id" + exit 1 + fi + log_success "Nodes are on the same chain (Chain ID: $l2geth_chain_id)" + + log_success "Pre-flight checks completed" } \ No newline at end of file diff --git a/sequencer-migration/migrate-sequencer.sh b/sequencer-migration/migrate-sequencer.sh index 90e4c2fd..4d5e92e8 100755 --- a/sequencer-migration/migrate-sequencer.sh +++ b/sequencer-migration/migrate-sequencer.sh @@ -11,27 +11,11 @@ source "$(dirname "$0")/common-functions.sh" # Global variables to track state START_TIME=$(date +%s) -INITIAL_L2GETH_BLOCK="" -INITIAL_L2RETH_BLOCK="" L2GETH_STOP_BLOCK="" L2RETH_FINAL_BLOCK="" -pre_flight_checks() { - log_info "=== PRE-FLIGHT CHECKS ===" - - check_rpc_connectivity - - # Get initial block states - local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") - local l2reth_info=$(get_latest_block_info "$L2RETH_RPC_URL") - - INITIAL_L2GETH_BLOCK=$(echo "$l2geth_info" | awk '{print $1}') - local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') - INITIAL_L2RETH_BLOCK=$(echo "$l2reth_info" | awk '{print $1}') - local l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}') - - log_info "L2GETH current block: #$INITIAL_L2GETH_BLOCK (hash: $l2geth_hash)" - log_info "L2RETH current block: #$INITIAL_L2RETH_BLOCK (hash: $l2reth_hash)" +migrate_pre_flight_checks() { + perform_pre_flight_checks # Check if l2geth is mining if ! is_l2geth_mining; then @@ -39,48 +23,6 @@ pre_flight_checks() { exit 1 fi log_success "L2GETH is currently mining" - - # Verify nodes are on the same chain by comparing chain IDs - local l2geth_chain_id=$(get_chain_id "$L2GETH_RPC_URL") - local l2reth_chain_id=$(get_chain_id "$L2RETH_RPC_URL") - - if [[ -z "$l2geth_chain_id" || -z "$l2reth_chain_id" ]]; then - log_error "Failed to retrieve chain IDs from one or both nodes" - exit 1 - fi - - if [[ "$l2geth_chain_id" != "$l2reth_chain_id" ]]; then - log_error "Nodes are on different chains! Chain IDs differ:" - log_error " L2GETH: $l2geth_chain_id" - log_error " L2RETH: $l2reth_chain_id" - exit 1 - fi - log_success "Nodes are on the same chain (Chain ID: $l2geth_chain_id)" - - # Verify nodes are on the same chain by comparing a recent block hash - local compare_block=$((INITIAL_L2RETH_BLOCK < INITIAL_L2GETH_BLOCK ? INITIAL_L2RETH_BLOCK : INITIAL_L2GETH_BLOCK)) - if [[ $compare_block -gt 0 ]]; then - local l2geth_compare_info=$(get_block_info "$L2GETH_RPC_URL" "$compare_block") - local l2reth_compare_info=$(get_block_info "$L2RETH_RPC_URL" "$compare_block") - - if [[ -z "$l2geth_compare_info" || -z "$l2reth_compare_info" ]]; then - log_error "Failed to retrieve block #$compare_block from one or both nodes" - exit 1 - fi - - local l2geth_compare_hash=$(echo "$l2geth_compare_info" | awk '{print $2}') - local l2reth_compare_hash=$(echo "$l2reth_compare_info" | awk '{print $2}') - - if [[ "$l2geth_compare_hash" != "$l2reth_compare_hash" ]]; then - log_error "Nodes are on different chains! Block #$compare_block hashes differ:" - log_error " L2GETH: $l2geth_compare_hash" - log_error " L2RETH: $l2reth_compare_hash" - exit 1 - fi - log_success "Block hash verification passed at block #$compare_block" - fi - - log_success "Pre-flight checks completed" } print_summary() { @@ -89,8 +31,6 @@ print_summary() { log_info "=== MIGRATION SUMMARY ===" log_info "Migration completed in ${total_time}s" - log_info "Initial L2GETH block: #$INITIAL_L2GETH_BLOCK" - log_info "Initial L2RETH block: #$INITIAL_L2RETH_BLOCK" log_info "L2GETH stopped at block: #$L2GETH_STOP_BLOCK" log_info "L2RETH final block: #$L2RETH_FINAL_BLOCK" @@ -122,7 +62,7 @@ main() { log_info "L2RETH will produce $blocks_to_produce blocks" check_env_vars - pre_flight_checks + migrate_pre_flight_checks # Double check if user wants to proceed read -p "Proceed with migration? (y/N): " confirm diff --git a/sequencer-migration/revert-l2geth-to-block.sh b/sequencer-migration/revert-l2geth-to-block.sh index b22e39f9..57b46fba 100755 --- a/sequencer-migration/revert-l2geth-to-block.sh +++ b/sequencer-migration/revert-l2geth-to-block.sh @@ -34,22 +34,12 @@ reset_l2geth_to_block() { -pre_flight_checks() { - log_info "=== PRE-FLIGHT CHECKS ===" +revert_pre_flight_checks() { + perform_pre_flight_checks - check_rpc_connectivity - - # Get current block states + # Get current l2geth block for validation local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") - local l2reth_info=$(get_latest_block_info "$L2RETH_RPC_URL") - local current_l2geth_block=$(echo "$l2geth_info" | awk '{print $1}') - local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}') - local current_l2reth_block=$(echo "$l2reth_info" | awk '{print $1}') - local l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}') - - log_info "L2GETH current block: #$current_l2geth_block (hash: $l2geth_hash)" - log_info "L2RETH current block: #$current_l2reth_block (hash: $l2reth_hash)" # Validate target block exists and is reachable if [[ $TARGET_BLOCK -gt $current_l2geth_block ]]; then @@ -67,8 +57,6 @@ pre_flight_checks() { TARGET_HASH=$(echo "$target_info" | awk '{print $2}') log_info "Target block #$TARGET_BLOCK exists (hash: $TARGET_HASH)" - - log_success "Pre-flight checks completed" } print_summary() { @@ -106,7 +94,7 @@ main() { log_info "Starting l2geth revert to block #$TARGET_BLOCK" check_env_vars - pre_flight_checks + revert_pre_flight_checks # Phase 1: Disable sequencing on both nodes log_info "=== PHASE 1: DISABLING SEQUENCING ===" From 0a0aea93fb6a55930c52d08810bff7371ab8dada Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:05:37 +0800 Subject: [PATCH 08/10] add simple switch to l2geth script --- sequencer-migration/switch-to-l2geth.sh | 60 +++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 sequencer-migration/switch-to-l2geth.sh diff --git a/sequencer-migration/switch-to-l2geth.sh b/sequencer-migration/switch-to-l2geth.sh new file mode 100755 index 00000000..85c98a7e --- /dev/null +++ b/sequencer-migration/switch-to-l2geth.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Switch Sequencing to L2GETH Script +# Disables L2RETH sequencing and enables L2GETH sequencing +# Usage: ./switch-to-l2geth.sh + +set -euo pipefail + +# Source common functions +source "$(dirname "$0")/common-functions.sh" + +# Global variables to track state +START_TIME=$(date +%s) + +perform_sequencing_switch() { + log_info "=== SWITCHING SEQUENCING TO L2GETH ===" + + # Phase 1: Disable L2RETH sequencing + log_info "--- Phase 1: Disabling L2RETH sequencing ---" + disable_l2reth_sequencing + + # Phase 2: Enable L2GETH sequencing + log_info "--- Phase 2: Enabling L2GETH sequencing ---" + start_l2geth_mining + + # Phase 3: Verify L2GETH is producing blocks + log_info "--- Phase 3: Verifying L2GETH block production ---" + + # Get current block and wait for next block + local current_block=$(get_block_number "$L2GETH_RPC_URL") + local target_block=$((current_block + 1)) + + log_info "Current L2GETH block: #$current_block, waiting for block #$target_block..." + + if wait_for_block "L2GETH" "$L2GETH_RPC_URL" "$target_block" ""; then + log_success "L2GETH is successfully producing blocks" + else + log_error "L2GETH failed to produce new blocks after sequencing was enabled" + exit 1 + fi +} + +main() { + log_info "Starting sequencer switch: L2RETH -> L2GETH" + + check_env_vars + perform_pre_flight_checks + + # Final confirmation + read -p "Proceed with switching sequencing to L2GETH? (y/N): " confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + log_warning "Sequencing switch cancelled by user" + exit 0 + fi + + perform_sequencing_switch +} + +# Run main function +main "$@" \ No newline at end of file From 6362c57d131056048f7c81ac6ded411286c429b9 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 25 Sep 2025 09:59:27 +0800 Subject: [PATCH 09/10] add Dockerfile and add more usage documentation --- sequencer-migration/Dockerfile | 24 ++++++++++++++++++++++++ sequencer-migration/README.md | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 sequencer-migration/Dockerfile diff --git a/sequencer-migration/Dockerfile b/sequencer-migration/Dockerfile new file mode 100644 index 00000000..331fcc2d --- /dev/null +++ b/sequencer-migration/Dockerfile @@ -0,0 +1,24 @@ +FROM ghcr.io/foundry-rs/foundry:stable + +USER root +RUN apt-get update && apt-get install -y \ + jq + +WORKDIR /migration +COPY . . + +RUN chmod +x migrate-sequencer.sh \ + revert-l2geth-to-block.sh \ + switch-to-l2geth.sh + +# Set default environment variables (can be overridden at runtime) +ENV L2GETH_RPC_URL=http://host.docker.internal:8547 +ENV L2RETH_RPC_URL=http://host.docker.internal:8545 + +USER foundry + +# Auto-source common functions when bash starts +RUN echo 'source /migration/common-functions.sh' >> /home/foundry/.bashrc + +# Default to interactive bash shell with RC file loaded +CMD ["/bin/bash", "-l"] \ No newline at end of file diff --git a/sequencer-migration/README.md b/sequencer-migration/README.md index e7dcad59..f4b7e122 100644 --- a/sequencer-migration/README.md +++ b/sequencer-migration/README.md @@ -21,7 +21,23 @@ The high-level flow of the transition will look like this: 6. Wait until `l2reth` has sequenced until block X or for some time 7. Turn off `l2reth` sequencing 8. Wait until `l2geth` has same block height -9. Turn on `l2geth` sequencingx +9. Turn on `l2geth` sequencing + +## Usage +Make sure the `L2RETH_RPC_URL` and `L2GETH_RPC_URL` env variables are properly configured. Simply run the script and follow the instructions. + +```bash +./migrate-sequencer.sh +./revert-l2geth-to-block.sh +./switch-to-l2geth.sh + +# make common functions available on bash +source common-functions.sh + +start_l2geth_mining +get_block_info $L2GETH_RPC_URL +[...] +``` ### Testing locally To test locally run the test `docker_test_migrate_sequencer` and execute the `migrate-sequencer.sh` script. @@ -40,5 +56,17 @@ source local.env - To simulate the case where `l2reth` produces invalid blocks we can adjust to `--builder.gaslimit=40000000` in `launch_rollup_node_sequencer.bash`. This will produce a block with a too big jump of the gas limit and `l2geth` will reject it. In a simulation we can then "revert" `l2geth` to its latest block and start sequencing on `l2geth` again. - Continuing on the above case we can fabricate a L2 reorg by simply resetting to any previous block. For all `l2geth` nodes the reorg will be shallow (ie before the invalid `l2reth` blocks) and for `l2reth` it will be deeper (ie all `l2reth` produced blocks + reset to previous block). -TODO: how to run with Docker +### Running with Docker +```bash +docker run -it --rm sequencer-migration:latest + +# then just use the scripts as before +./migrate-sequencer.sh + +# or call any of the common functions +start_l2geth_mining +get_block_info $L2GETH_RPC_URL +[...] +``` +If running on Linux you might need to specify `-e L2GETH_RPC_URL=http://your-l2geth:8547 -e L2RETH_RPC_URL=http://your-l2reth:8545` as the default URLs might not work. \ No newline at end of file From 087e58857c869419503a3492d8c5c522d6a78e2b Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:13:09 +0800 Subject: [PATCH 10/10] push to Dockerhub --- .../workflows/sequencer-migration-docker.yml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/sequencer-migration-docker.yml diff --git a/.github/workflows/sequencer-migration-docker.yml b/.github/workflows/sequencer-migration-docker.yml new file mode 100644 index 00000000..cecf1816 --- /dev/null +++ b/.github/workflows/sequencer-migration-docker.yml @@ -0,0 +1,46 @@ +name: Sequencer Migration Docker + +on: + push: + tags: + - '*' + release: + types: [published] + +jobs: + build-and-push: + runs-on: + group: scroll-reth-runner-group + permissions: {} + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Extract docker metadata + id: meta + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + with: + images: scrolltech/sequencer-migration + tags: | + type=ref,event=tag,enable=${{ github.event_name == 'push' }} + type=raw,value=latest,enable=${{ github.event_name == 'release' }} + flavor: | + latest=false + + - name: Login to Docker Hub + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 #v3.5.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + context: sequencer-migration + file: sequencer-migration/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }}