Skip to content

Commit dbd400c

Browse files
committed
add initial sequencer migration script and readme
1 parent 20839ca commit dbd400c

File tree

3 files changed

+392
-0
lines changed

3 files changed

+392
-0
lines changed

sequencer-migration/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Scroll Sequencer Migration
2+
3+
This module contains documentation and scripts for Scroll's sequencer migration from `l2geth` to rollup node (RN) aka `l2reth`.
4+
5+
### Risks
6+
We want to minimize risks and minimize service disruption. For this we need to consider following risks:
7+
- invalid L2 blocks produced
8+
- L2 reorg (e.g. different blocks issued at same L2 block height)
9+
- L1 messages skipped/reverted
10+
- general service interruption
11+
12+
## Migration Procedure
13+
14+
To instill confidence we will do many repeated transitions from `l2geth` -> `l2reth` -> `l2geth` with the time that `l2reth` sequences increasing.
15+
16+
The high-level flow of the transition will look like this:
17+
1. `l2geth` is sequencing currently
18+
2. Turn off `l2geth` sequencing
19+
3. Get block height of `l2geth`
20+
4. Wait until `l2reth` has same block height
21+
5. Turn on `l2reth` sequencing
22+
6. Wait until `l2reth` has sequenced until block X or for some time
23+
7. Turn off `l2reth` sequencing
24+
8. Wait until `l2geth` has same block height
25+
9. Turn on `l2geth` sequencingx
26+
27+
### Testing locally
28+
```bash
29+
# this test runs for ~60 seconds and starts with l2geth sequencing and expects all nodes to reach the same block at block number 120.
30+
RUST_LOG=info,docker-compose=off cargo test --package tests --test migrate_sequencer -- docker_test_migrate_sequencer --exact --show-output
31+
32+
source local.env
33+
./migrate-sequencer.sh
34+
```
35+

sequencer-migration/local.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export L2GETH_RPC_URL=http://localhost:8547
2+
export L2RETH_RPC_URL=http://localhost:8545
Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
#!/bin/bash
2+
3+
# Sequencer Migration Script
4+
# Migrates sequencing from l2geth -> l2reth -> l2geth
5+
# Usage: ./migrate-sequencer.sh <blocks_to_produce>
6+
7+
set -euo pipefail
8+
9+
# Colors for output
10+
RED='\033[0;31m'
11+
GREEN='\033[0;32m'
12+
YELLOW='\033[1;33m'
13+
BLUE='\033[0;34m'
14+
NC='\033[0m' # No Color
15+
16+
# Configuration
17+
SYNC_TIMEOUT=5 # 5 seconds timeout for sync operations
18+
POLL_INTERVAL=0.1 # Poll every 0.1 seconds
19+
20+
# Global variables to track state
21+
START_TIME=$(date +%s)
22+
INITIAL_L2GETH_BLOCK=""
23+
INITIAL_L2RETH_BLOCK=""
24+
L2GETH_STOP_BLOCK=""
25+
L2RETH_FINAL_BLOCK=""
26+
27+
# Helper function to print colored output
28+
log() {
29+
echo -e "${2:-$NC}[$(date '+%H:%M:%S')] $1${NC}"
30+
}
31+
32+
log_info() { log "$1" "$BLUE"; }
33+
log_success() { log "$1" "$GREEN"; }
34+
log_warning() { log "$1" "$YELLOW"; }
35+
log_error() { log "$1" "$RED"; }
36+
37+
# Check if required environment variables are set
38+
check_env_vars() {
39+
log_info "Checking environment variables..."
40+
41+
if [[ -z "${L2GETH_RPC_URL:-}" ]]; then
42+
log_error "L2GETH_RPC_URL environment variable is required"
43+
exit 1
44+
fi
45+
46+
if [[ -z "${L2RETH_RPC_URL:-}" ]]; then
47+
log_error "L2RETH_RPC_URL environment variable is required"
48+
exit 1
49+
fi
50+
51+
log_success "Environment variables configured"
52+
log_info "L2GETH_RPC_URL: $L2GETH_RPC_URL"
53+
log_info "L2RETH_RPC_URL: $L2RETH_RPC_URL"
54+
}
55+
56+
# Get block number and hash for a given RPC URL
57+
get_block_info() {
58+
local rpc_url="$1"
59+
local temp_file=$(mktemp)
60+
61+
if ! cast block latest --rpc-url "$rpc_url" > "$temp_file" 2>/dev/null; then
62+
rm -f "$temp_file"
63+
return 1
64+
fi
65+
66+
local block_number=$(grep "^number" "$temp_file" | awk '{print $2}')
67+
local block_hash=$(grep "^hash" "$temp_file" | awk '{print $2}')
68+
69+
rm -f "$temp_file"
70+
echo "$block_number $block_hash"
71+
}
72+
73+
# Get only block number for a given RPC URL
74+
get_block_number() {
75+
local rpc_url="$1"
76+
cast block latest --rpc-url "$rpc_url" 2>/dev/null | grep "^number" | awk '{print $2}'
77+
}
78+
79+
is_l2geth_mining() {
80+
local result=$(cast rpc eth_mining --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | tr -d '"')
81+
[[ "$result" == "true" ]]
82+
}
83+
84+
start_l2geth_mining() {
85+
log_info "Starting l2geth mining..."
86+
if cast rpc miner_start --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then
87+
log_success "L2GETH mining started"
88+
return 0
89+
else
90+
log_error "Failed to start l2geth mining"
91+
return 1
92+
fi
93+
}
94+
95+
stop_l2geth_mining() {
96+
log_info "Stopping l2geth mining..."
97+
if cast rpc miner_stop --rpc-url "$L2GETH_RPC_URL" >/dev/null 2>&1; then
98+
log_success "L2GETH mining stopped"
99+
return 0
100+
else
101+
log_error "Failed to stop l2geth mining"
102+
return 1
103+
fi
104+
}
105+
106+
enable_l2reth_sequencing() {
107+
log_info "Enabling L2RETH automatic sequencing..."
108+
if cast rpc rollupNode_enableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then
109+
log_success "L2RETH automatic sequencing enabled"
110+
return 0
111+
else
112+
log_error "Failed to enable L2RETH automatic sequencing"
113+
return 1
114+
fi
115+
}
116+
117+
disable_l2reth_sequencing() {
118+
log_info "Disabling L2RETH automatic sequencing..."
119+
if cast rpc rollupNode_disableAutomaticSequencing --rpc-url "$L2RETH_RPC_URL" >/dev/null 2>&1; then
120+
log_success "L2RETH automatic sequencing disabled"
121+
return 0
122+
else
123+
log_error "Failed to disable L2RETH automatic sequencing"
124+
return 1
125+
fi
126+
}
127+
128+
wait_for_block() {
129+
local rpc_url="$1"
130+
local target_block="$2"
131+
local node_name="$3"
132+
local target_hash="$4"
133+
134+
log_info "Waiting for $node_name to reach block #$target_block (hash: $target_hash)..."
135+
136+
local start_time=$(date +%s)
137+
while true; do
138+
local current_time=$(date +%s)
139+
local elapsed=$((current_time - start_time))
140+
141+
if [[ $elapsed -gt $SYNC_TIMEOUT ]]; then
142+
log_error "Timeout waiting for $node_name to reach block #$target_block"
143+
return 1
144+
fi
145+
146+
local block_info
147+
if block_info=$(get_block_info "$rpc_url"); then
148+
local current_block=$(echo "$block_info" | awk '{print $1}')
149+
local current_hash=$(echo "$block_info" | awk '{print $2}')
150+
151+
if [[ "$current_block" -ge "$target_block" ]]; then
152+
if [[ "$current_block" -eq "$target_block" && "$current_hash" == "$target_hash" ]]; then
153+
log_success "$node_name reached target block #$target_block (hash: $target_hash)"
154+
return 0
155+
elif [[ "$current_block" -gt "$target_block" ]]; then
156+
log_success "$node_name surpassed target, now at block #$current_block (hash: $current_hash)"
157+
return 0
158+
else
159+
log_warning "$node_name at block #$current_block but hash mismatch: expected $target_hash, got $current_hash"
160+
fi
161+
fi
162+
fi
163+
164+
sleep $POLL_INTERVAL
165+
done
166+
}
167+
168+
check_rpc_connectivity() {
169+
log_info "Checking RPC connectivity..."
170+
171+
if ! get_block_info "$L2GETH_RPC_URL" >/dev/null; then
172+
log_error "Cannot connect to L2GETH at $L2GETH_RPC_URL"
173+
exit 1
174+
fi
175+
176+
if ! get_block_info "$L2RETH_RPC_URL" >/dev/null; then
177+
log_error "Cannot connect to L2RETH at $L2RETH_RPC_URL"
178+
exit 1
179+
fi
180+
181+
log_success "Both nodes are accessible"
182+
}
183+
184+
pre_flight_checks() {
185+
log_info "=== PRE-FLIGHT CHECKS ==="
186+
187+
check_rpc_connectivity
188+
189+
# Get initial block states
190+
local l2geth_info=$(get_block_info "$L2GETH_RPC_URL")
191+
local l2reth_info=$(get_block_info "$L2RETH_RPC_URL")
192+
193+
INITIAL_L2GETH_BLOCK=$(echo "$l2geth_info" | awk '{print $1}')
194+
local l2geth_hash=$(echo "$l2geth_info" | awk '{print $2}')
195+
INITIAL_L2RETH_BLOCK=$(echo "$l2reth_info" | awk '{print $1}')
196+
local l2reth_hash=$(echo "$l2reth_info" | awk '{print $2}')
197+
198+
log_info "L2GETH current block: #$INITIAL_L2GETH_BLOCK (hash: $l2geth_hash)"
199+
log_info "L2RETH current block: #$INITIAL_L2RETH_BLOCK (hash: $l2reth_hash)"
200+
201+
# Check if l2geth is mining
202+
if ! is_l2geth_mining; then
203+
log_error "L2GETH is not currently mining. Please start mining first."
204+
exit 1
205+
fi
206+
log_success "L2GETH is currently mining"
207+
208+
# Verify nodes are on the same chain by comparing a recent block
209+
local compare_block=$((INITIAL_L2RETH_BLOCK < INITIAL_L2GETH_BLOCK ? INITIAL_L2RETH_BLOCK : INITIAL_L2GETH_BLOCK))
210+
if [[ $compare_block -gt 0 ]]; then
211+
local l2geth_compare_hash=$(cast block "$compare_block" --rpc-url "$L2GETH_RPC_URL" 2>/dev/null | grep "^hash" | awk '{print $2}')
212+
local l2reth_compare_hash=$(cast block "$compare_block" --rpc-url "$L2RETH_RPC_URL" 2>/dev/null | grep "^hash" | awk '{print $2}')
213+
214+
if [[ "$l2geth_compare_hash" != "$l2reth_compare_hash" ]]; then
215+
log_error "Nodes are on different chains! Block #$compare_block hashes differ:"
216+
log_error " L2GETH: $l2geth_compare_hash"
217+
log_error " L2RETH: $l2reth_compare_hash"
218+
exit 1
219+
fi
220+
log_success "Nodes are on the same chain (verified at block #$compare_block)"
221+
fi
222+
223+
log_success "Pre-flight checks completed"
224+
}
225+
226+
print_summary() {
227+
local end_time=$(date +%s)
228+
local total_time=$((end_time - START_TIME))
229+
230+
log_info "=== MIGRATION SUMMARY ==="
231+
log_info "Migration completed in ${total_time}s"
232+
log_info "Initial L2GETH block: #$INITIAL_L2GETH_BLOCK"
233+
log_info "Initial L2RETH block: #$INITIAL_L2RETH_BLOCK"
234+
log_info "L2GETH stopped at block: #$L2GETH_STOP_BLOCK"
235+
log_info "L2RETH final block: #$L2RETH_FINAL_BLOCK"
236+
237+
local final_l2geth_info=$(get_block_info "$L2GETH_RPC_URL")
238+
local final_l2geth_block=$(echo "$final_l2geth_info" | awk '{print $1}')
239+
local final_l2geth_hash=$(echo "$final_l2geth_info" | awk '{print $2}')
240+
log_info "Final L2GETH block: #$final_l2geth_block (hash: $final_l2geth_hash)"
241+
242+
log_success "Sequencer migration completed successfully!"
243+
}
244+
245+
main() {
246+
# Check arguments
247+
if [[ $# -ne 1 ]]; then
248+
echo "Usage: $0 <blocks_to_produce>"
249+
echo " blocks_to_produce: Number of blocks for L2RETH to produce during migration"
250+
exit 1
251+
fi
252+
253+
local blocks_to_produce="$1"
254+
255+
# Validate blocks_to_produce is a positive integer
256+
if ! [[ "$blocks_to_produce" =~ ^[1-9][0-9]*$ ]]; then
257+
log_error "blocks_to_produce must be a positive integer, got: $blocks_to_produce"
258+
exit 1
259+
fi
260+
261+
log_info "Starting sequencer migration: L2GETH -> L2RETH -> L2GETH"
262+
log_info "L2RETH will produce $blocks_to_produce blocks"
263+
264+
check_env_vars
265+
pre_flight_checks
266+
267+
# Double check if user wants to proceed
268+
read -p "Proceed with migration? (y/N): " confirm
269+
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
270+
log_warning "Migration aborted by user"
271+
exit 0
272+
fi
273+
274+
# Phase 1: Stop L2GETH sequencing
275+
log_info "=== PHASE 1: STOPPING L2GETH SEQUENCING ==="
276+
stop_l2geth_mining
277+
278+
# Record where L2GETH stopped
279+
local stop_info=$(get_block_info "$L2GETH_RPC_URL")
280+
L2GETH_STOP_BLOCK=$(echo "$stop_info" | awk '{print $1}')
281+
local stop_hash=$(echo "$stop_info" | awk '{print $2}')
282+
log_success "L2GETH sequencing stopped at block #$L2GETH_STOP_BLOCK (hash: $stop_hash)"
283+
284+
# Phase 2: Wait for L2RETH to sync
285+
log_info "=== PHASE 2: WAITING FOR L2RETH SYNC ==="
286+
wait_for_block "$L2RETH_RPC_URL" "$L2GETH_STOP_BLOCK" "L2RETH" "$stop_hash"
287+
288+
# Phase 3: Enable L2RETH sequencing and wait for blocks
289+
log_info "=== PHASE 3: L2RETH SEQUENCING ($blocks_to_produce blocks) ==="
290+
enable_l2reth_sequencing
291+
292+
local target_block=$((L2GETH_STOP_BLOCK + blocks_to_produce))
293+
log_info "Waiting for L2RETH to produce $blocks_to_produce blocks (target: #$target_block)..."
294+
295+
# Monitor block production
296+
local current_block=$L2GETH_STOP_BLOCK
297+
while [[ $current_block -lt $target_block ]]; do
298+
sleep $POLL_INTERVAL
299+
local new_block=$(get_block_number "$L2RETH_RPC_URL")
300+
if [[ $new_block -gt $current_block ]]; then
301+
local block_info=$(get_block_info "$L2RETH_RPC_URL")
302+
local block_hash=$(echo "$block_info" | awk '{print $2}')
303+
log_success "L2RETH produced block #$new_block (hash: $block_hash)"
304+
current_block=$new_block
305+
fi
306+
done
307+
308+
# Phase 4: Stop L2RETH sequencing
309+
log_info "=== PHASE 4: STOPPING L2RETH SEQUENCING ==="
310+
disable_l2reth_sequencing
311+
312+
# Record final L2RETH block
313+
local final_info=$(get_block_info "$L2RETH_RPC_URL")
314+
L2RETH_FINAL_BLOCK=$(echo "$final_info" | awk '{print $1}')
315+
local final_hash=$(echo "$final_info" | awk '{print $2}')
316+
log_success "L2RETH sequencing stopped at block #$L2RETH_FINAL_BLOCK (hash: $final_hash)"
317+
318+
# Phase 5: Wait for L2GETH to sync
319+
log_info "=== PHASE 5: WAITING FOR L2GETH SYNC ==="
320+
wait_for_block "$L2GETH_RPC_URL" "$L2RETH_FINAL_BLOCK" "L2GETH" "$final_hash"
321+
322+
# Phase 6: Resume L2GETH sequencing
323+
log_info "=== PHASE 6: RESUMING L2GETH SEQUENCING ==="
324+
start_l2geth_mining
325+
326+
# TODO: this could be done with wait for function?
327+
# Wait for at least one new block to confirm
328+
log_info "Waiting for L2GETH to produce at least one new block..."
329+
local confirmation_target=$((L2RETH_FINAL_BLOCK + 1))
330+
local start_time=$(date +%s)
331+
while true; do
332+
local current_time=$(date +%s)
333+
local elapsed=$((current_time - start_time))
334+
335+
if [[ $elapsed -gt 60 ]]; then # 1 minute timeout for first block
336+
log_error "Timeout waiting for L2GETH to produce new block"
337+
exit 1
338+
fi
339+
340+
local current_block=$(get_block_number "$L2GETH_RPC_URL")
341+
if [[ $current_block -ge $confirmation_target ]]; then
342+
local confirm_info=$(get_block_info "$L2GETH_RPC_URL")
343+
local confirm_hash=$(echo "$confirm_info" | awk '{print $2}')
344+
log_success "L2GETH sequencing resumed, produced block #$current_block (hash: $confirm_hash)"
345+
break
346+
fi
347+
348+
sleep $POLL_INTERVAL
349+
done
350+
351+
print_summary
352+
}
353+
354+
# Run main function
355+
main "$@"

0 commit comments

Comments
 (0)