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 }} 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 new file mode 100644 index 00000000..f4b7e122 --- /dev/null +++ b/sequencer-migration/README.md @@ -0,0 +1,72 @@ +# 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` 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. +```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 + +# 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). + +### 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 diff --git a/sequencer-migration/common-functions.sh b/sequencer-migration/common-functions.sh new file mode 100644 index 00000000..67af1c08 --- /dev/null +++ b/sequencer-migration/common-functions.sh @@ -0,0 +1,284 @@ +#!/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 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 block_identifier="${2:-latest}" + local block_data + + if ! block_data=$(cast block "$block_identifier" --json --rpc-url "$rpc_url" 2>/dev/null); then + return 1 + fi + + 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_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" +} + +# 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_info + + if ! block_info=$(get_block_info "$rpc_url" "latest"); then + return 1 + fi + + # Extract just the block number (first field) + echo "$block_info" | awk '{print $1}' +} + +# Get chain ID for a given RPC URL +get_chain_id() { + local rpc_url="$1" + + if ! cast chain-id --rpc-url "$rpc_url" 2>/dev/null; then + return 1 + fi +} + +# 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 +# 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" + + 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 + 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" + + 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 + + + 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 [[ -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 + # 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 + else + # Block number only mode + log_success "$node_name reached block #$current_block (hash: $current_hash)" + return 0 + 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" +} + +# 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/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..4d5e92e8 --- /dev/null +++ b/sequencer-migration/migrate-sequencer.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +# Sequencer Migration Script +# Migrates sequencing from l2geth -> l2reth -> l2geth +# Usage: ./migrate-sequencer.sh + +set -euo pipefail + +# Source common functions +source "$(dirname "$0")/common-functions.sh" + +# Global variables to track state +START_TIME=$(date +%s) +L2GETH_STOP_BLOCK="" +L2RETH_FINAL_BLOCK="" + +migrate_pre_flight_checks() { + perform_pre_flight_checks + + # 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" +} + +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 "L2GETH stopped at block: #$L2GETH_STOP_BLOCK" + log_info "L2RETH final block: #$L2RETH_FINAL_BLOCK" + + 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 "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 + migrate_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_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" "$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) ===" + 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_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 + 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_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" "$L2GETH_RPC_URL" "$L2RETH_FINAL_BLOCK" "$final_hash" + + # Phase 6: Resume L2GETH sequencing + log_info "=== PHASE 6: RESUMING L2GETH SEQUENCING ===" + start_l2geth_mining + + # Wait for at least one new block to confirm sequencing resumed + local confirmation_target=$((L2RETH_FINAL_BLOCK + 1)) + 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 +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/sequencer-migration/revert-l2geth-to-block.sh b/sequencer-migration/revert-l2geth-to-block.sh new file mode 100755 index 00000000..57b46fba --- /dev/null +++ b/sequencer-migration/revert-l2geth-to-block.sh @@ -0,0 +1,163 @@ +#!/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" + +# 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 +} + + + +revert_pre_flight_checks() { + perform_pre_flight_checks + + # Get current l2geth block for validation + local l2geth_info=$(get_latest_block_info "$L2GETH_RPC_URL") + local current_l2geth_block=$(echo "$l2geth_info" | awk '{print $1}') + + # 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)" +} + +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 + revert_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 ===" + 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 +} + +# Run main function +main "$@" \ No newline at end of file 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 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!( diff --git a/tests/tests/migrate_sequencer.rs b/tests/tests/migrate_sequencer.rs new file mode 100644 index 00000000..ae679d84 --- /dev/null +++ b/tests/tests/migrate_sequencer.rs @@ -0,0 +1,51 @@ +use eyre::Result; +use tests::*; + +#[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(()) +}