-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathrun.sh
More file actions
1511 lines (1309 loc) · 50 KB
/
Copy pathrun.sh
File metadata and controls
1511 lines (1309 loc) · 50 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# Set strict error handling
set -euo pipefail
RELEASE_TAG="telos-v1.0.1"
TELOSZERO_CORE_VERSION="1.2.2"
TELOSZERO_CORE_RELEASE_TAG="teloszero-v$TELOSZERO_CORE_VERSION"
TELOSZERO_CORE_DEB="teloszero-core_${TELOSZERO_CORE_VERSION}_amd64.deb"
TELOSZERO_CORE_DEB_URL="https://github.com/telosnetwork/teloszero-core/releases/download/$TELOSZERO_CORE_RELEASE_TAG/$TELOSZERO_CORE_DEB"
TELOSZERO_CORE_DEB_SHA256="285fdfc1abde5104892d94b1f380c6d79aba35eac3413f139119ea88574c5007"
TELOSZERO_CORE_PACKAGE="teloszero-core"
CONFLICTING_NODEOS_PACKAGES=(eosio mandel leap spring antelope-spring)
STORAGE_BASE_URL="${STORAGE_BASE_URL:-https://storage.telos.net/evm_backups/mainnet}"
NODEOS_BACKUP_BASE_URL="${NODEOS_BACKUP_BASE_URL:-$STORAGE_BASE_URL}"
RETH_BACKUP_BASE_URL="${RETH_BACKUP_BASE_URL:-$STORAGE_BASE_URL/telos-evm-2}"
SHIP_ARCHIVE_BASE_URL="${SHIP_ARCHIVE_BASE_URL:-$STORAGE_BASE_URL/mainnet-ship}"
NATIVE_RPC_URL="${NATIVE_RPC_URL:-https://mainnet.telos.net}"
EVM_RPC_URL="${EVM_RPC_URL:-https://rpc.telos.net/evm}"
MAX_BACKUP_STALENESS_BLOCKS="${MAX_BACKUP_STALENESS_BLOCKS:-345600}" # 2 days at 0.5s blocks
ALLOW_STALE_BACKUPS="${ALLOW_STALE_BACKUPS:-false}"
SKIP_BACKUP_FRESHNESS_CHECK="${SKIP_BACKUP_FRESHNESS_CHECK:-false}"
STRICT_BACKUP_FRESHNESS_CHECK="${STRICT_BACKUP_FRESHNESS_CHECK:-false}"
EXPECTED_RETH_VERSION="${EXPECTED_RETH_VERSION:-1.0.8}"
SKIP_RETH_BACKUP_COMPATIBILITY_CHECK="${SKIP_RETH_BACKUP_COMPATIBILITY_CHECK:-false}"
NODEOS_SYNC_TIMEOUT_SECONDS="${NODEOS_SYNC_TIMEOUT_SECONDS:-0}"
RETH_VERIFY_TIMEOUT_SECONDS="${RETH_VERIFY_TIMEOUT_SECONDS:-300}"
FAST_REQUIRED_GIB="${FAST_REQUIRED_GIB:-500}"
ARCHIVE_REQUIRED_GIB="${ARCHIVE_REQUIRED_GIB:-3800}"
if [[ -n "${TELOS_BOOTSTRAP_MODE:-}" ]]; then
BOOTSTRAP_MODE_EXPLICIT=true
else
BOOTSTRAP_MODE_EXPLICIT=false
TELOS_BOOTSTRAP_MODE="fast"
fi
LOCAL_DEBUGGING=true
# Global array to keep track of selected ports
SELECTED_PORTS=()
NODEOS_SNAPSHOT_ARTIFACT=""
NODEOS_SNAPSHOT_URL=""
NODEOS_SNAPSHOT_HEIGHT=""
RETH_BACKUP_ARTIFACT=""
RETH_BACKUP_URL=""
RETH_BACKUP_MANIFEST_URL=""
RETH_BACKUP_SHA_URL=""
RETH_BACKUP_HEIGHT=""
RETH_BACKUP_HASH=""
RETH_BACKUP_SHA256=""
RETH_BACKUP_SOURCE_DATADIR=""
RETH_BACKUP_BINARY=""
RETH_BACKUP_BINARY_VERSION=""
RETH_BACKUP_BINARY_COMMIT=""
RETH_BACKUP_CONSENSUS_BINARY=""
LIVE_NATIVE_HEAD=""
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Log functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
usage() {
cat << EOF
Usage: $0 [--bootstrap-mode fast|archive] [--allow-stale-backups]
Bootstrap modes:
fast Use a nodeos state snapshot plus the reth backup. Native nodeos block
history starts at the snapshot block. This is the default.
archive Also restore native block logs and state-history from mainnet-ship.
This requires multiple TiB of disk and a long transfer.
Environment overrides:
MAX_BACKUP_STALENESS_BLOCKS=$MAX_BACKUP_STALENESS_BLOCKS
STRICT_BACKUP_FRESHNESS_CHECK=$STRICT_BACKUP_FRESHNESS_CHECK
ALLOW_STALE_BACKUPS=$ALLOW_STALE_BACKUPS
NODEOS_BACKUP_BASE_URL=$NODEOS_BACKUP_BASE_URL
RETH_BACKUP_BASE_URL=$RETH_BACKUP_BASE_URL
SHIP_ARCHIVE_BASE_URL=$SHIP_ARCHIVE_BASE_URL
EXPECTED_RETH_VERSION=$EXPECTED_RETH_VERSION
SKIP_RETH_BACKUP_COMPATIBILITY_CHECK=$SKIP_RETH_BACKUP_COMPATIBILITY_CHECK
NODEOS_SYNC_TIMEOUT_SECONDS=$NODEOS_SYNC_TIMEOUT_SECONDS
RETH_VERIFY_TIMEOUT_SECONDS=$RETH_VERIFY_TIMEOUT_SECONDS
FAST_REQUIRED_GIB=$FAST_REQUIRED_GIB
ARCHIVE_REQUIRED_GIB=$ARCHIVE_REQUIRED_GIB
EOF
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--bootstrap-mode)
if [[ $# -lt 2 ]]; then
log_error "--bootstrap-mode requires fast or archive"
exit 1
fi
TELOS_BOOTSTRAP_MODE="$2"
BOOTSTRAP_MODE_EXPLICIT=true
shift 2
;;
--allow-stale-backups)
ALLOW_STALE_BACKUPS=true
shift
;;
--help|-h)
usage
exit 0
;;
*)
log_error "Unknown argument: $1"
usage
exit 1
;;
esac
done
case "$TELOS_BOOTSTRAP_MODE" in
fast|archive) ;;
*)
log_error "Invalid bootstrap mode '$TELOS_BOOTSTRAP_MODE'. Use fast or archive."
exit 1
;;
esac
}
# Check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
bool_is_true() {
case "${1:-}" in
true|TRUE|yes|YES|1) return 0 ;;
*) return 1 ;;
esac
}
download_url() {
local url="$1"
local output="$2"
local remote_size=""
local local_size=""
remote_size=$(curl -fsSIL "$url" | awk 'tolower($1) == "content-length:" {value=$2} END {gsub(/\r/, "", value); print value}' || true)
if [[ -f "$output" && "$remote_size" =~ ^[0-9]+$ ]]; then
local_size=$(wc -c < "$output" | tr -d ' ')
if (( local_size == remote_size )); then
log_info "$output already exists and matches remote size; skipping download"
return
fi
if (( local_size > remote_size )); then
log_warning "$output is larger than the remote artifact; removing local file and downloading again"
rm "$output"
fi
fi
curl -L --fail --retry 3 --retry-delay 2 --continue-at - "$url" --output "$output"
}
download_fresh_url() {
local url="$1"
local output="$2"
curl -L --fail --retry 3 --retry-delay 2 "$url" --output "$output"
}
file_sha256() {
sha256sum "$1" | awk '{print $1}'
}
read_manifest_value() {
local key="$1"
local file="$2"
awk -F= -v key="$key" '$1 == key {print substr($0, length(key) + 2); exit}' "$file"
}
parse_reth_binary_version() {
local binary="$1"
local version
version="$(echo "$binary" | sed -nE 's/.*[Rr]eth Version: *([^;[:space:]]+).*/\1/p')"
if [[ -z "$version" ]]; then
version="$(echo "$binary" | sed -nE 's#.*[Rr]eth/v([^/[:space:]]+).*#\1#p')"
fi
echo "$version"
}
parse_reth_binary_commit() {
local binary="$1"
echo "$binary" | sed -nE 's/.*Commit SHA: *([^;]+).*/\1/p'
}
normalize_version() {
local version="$1"
version="${version#v}"
version="${version%%-*}"
echo "$version"
}
fetch_native_head() {
curl -fsS "$NATIVE_RPC_URL/v1/chain/get_info" | jq -r '.head_block_num'
}
discover_latest_artifact() {
local base_url="$1"
local pattern="$2"
local listing
if ! listing="$(curl -fsSL "$base_url/" 2>/dev/null)"; then
return 0
fi
echo "$listing" | grep -oE "href=\"$pattern\"" | sed -E 's/^href="([^"]+)"/\1/' | sort | tail -1
}
discover_latest_snapshot_at_or_before() {
local base_url="$1"
local max_height="$2"
local listing
local artifact
local height
local best_artifact=""
local best_height=0
if ! listing="$(curl -fsSL "$base_url/" 2>/dev/null)"; then
return 0
fi
while IFS= read -r artifact; do
height="$(extract_snapshot_height "$artifact")"
if [[ -z "$height" ]]; then
continue
fi
if (( height <= max_height && height > best_height )); then
best_artifact="$artifact"
best_height="$height"
fi
done < <(echo "$listing" | grep -oE 'href="snapshot-[^"]+\.bin\.zst"' | sed -E 's/^href="([^"]+)"/\1/')
echo "$best_artifact"
}
artifact_url() {
local base_url="$1"
local artifact="$2"
echo "$base_url/$artifact"
}
normalize_path() {
local path="$1"
if realpath -m "$path" >/dev/null 2>&1; then
realpath -m "$path"
elif [[ "$path" == /* ]]; then
echo "$path"
else
echo "$(pwd)/$path"
fi
}
extract_snapshot_height() {
local artifact="$1"
local height
height=$(echo "$artifact" | sed -nE 's/.*-([0-9]{10})\.bin\.zst$/\1/p')
if [[ -n "$height" ]]; then
echo "$((10#$height))"
fi
}
dpkg_installed_version() {
local package="$1"
local query_output
query_output=$(dpkg-query -W -f='${Status} ${Version}' "$package" 2>/dev/null || true)
if [[ "$query_output" == install\ ok\ installed* ]]; then
echo "${query_output##* }"
fi
}
download_teloszero_core_package() {
log_info "Downloading TelosZero Core $TELOSZERO_CORE_VERSION..."
curl -L --fail --retry 3 --retry-delay 2 "$TELOSZERO_CORE_DEB_URL" --output "$TELOSZERO_CORE_DEB"
}
verify_teloszero_core_package() {
log_info "Verifying TelosZero Core package checksum..."
if ! echo "$TELOSZERO_CORE_DEB_SHA256 $TELOSZERO_CORE_DEB" | sha256sum -c - >/dev/null; then
log_error "Checksum verification failed for $TELOSZERO_CORE_DEB"
exit 1
fi
}
remove_conflicting_nodeos_packages() {
local installed_conflicts=()
local package
for package in "${CONFLICTING_NODEOS_PACKAGES[@]}"; do
if [[ -n "$(dpkg_installed_version "$package")" ]]; then
installed_conflicts+=("$package")
fi
done
if (( ${#installed_conflicts[@]} > 0 )); then
log_info "Removing conflicting nodeos packages: ${installed_conflicts[*]}"
DEBIAN_FRONTEND=noninteractive sudo apt-get remove -y "${installed_conflicts[@]}"
fi
}
load_backup_metadata() {
log_info "Discovering latest Telos EVM backup metadata..."
local reth_manifest_artifact
reth_manifest_artifact="$(discover_latest_artifact "$RETH_BACKUP_BASE_URL" 'reth-data-[^"]+\.tar\.zst\.manifest\.txt')"
if [[ -z "$reth_manifest_artifact" ]]; then
log_error "Could not discover a Telos EVM 2 reth backup manifest from $RETH_BACKUP_BASE_URL"
log_error "Publish a Reth $EXPECTED_RETH_VERSION-compatible reth-data-*.tar.zst plus .manifest.txt and .sha256 into that folder."
exit 1
fi
RETH_BACKUP_MANIFEST_URL="$(artifact_url "$RETH_BACKUP_BASE_URL" "$reth_manifest_artifact")"
RETH_BACKUP_ARTIFACT="${reth_manifest_artifact%.manifest.txt}"
RETH_BACKUP_URL="$(artifact_url "$RETH_BACKUP_BASE_URL" "$RETH_BACKUP_ARTIFACT")"
RETH_BACKUP_SHA_URL="$(artifact_url "$RETH_BACKUP_BASE_URL" "$RETH_BACKUP_ARTIFACT.sha256")"
mkdir -p "$INSTALL_DIR/manifests"
download_fresh_url "$RETH_BACKUP_MANIFEST_URL" "$INSTALL_DIR/manifests/$reth_manifest_artifact"
if download_fresh_url "$RETH_BACKUP_SHA_URL" "$INSTALL_DIR/manifests/$RETH_BACKUP_ARTIFACT.sha256"; then
RETH_BACKUP_SHA256="$(awk '{print $1; exit}' "$INSTALL_DIR/manifests/$RETH_BACKUP_ARTIFACT.sha256")"
else
log_warning "No checksum file found for $RETH_BACKUP_ARTIFACT"
fi
RETH_BACKUP_HEIGHT="$(read_manifest_value height "$INSTALL_DIR/manifests/$reth_manifest_artifact")"
RETH_BACKUP_HASH="$(read_manifest_value hash "$INSTALL_DIR/manifests/$reth_manifest_artifact")"
RETH_BACKUP_SOURCE_DATADIR="$(read_manifest_value source_datadir "$INSTALL_DIR/manifests/$reth_manifest_artifact")"
RETH_BACKUP_BINARY="$(read_manifest_value reth_binary "$INSTALL_DIR/manifests/$reth_manifest_artifact")"
RETH_BACKUP_BINARY_VERSION="$(parse_reth_binary_version "$RETH_BACKUP_BINARY")"
RETH_BACKUP_BINARY_COMMIT="$(parse_reth_binary_commit "$RETH_BACKUP_BINARY")"
RETH_BACKUP_CONSENSUS_BINARY="$(read_manifest_value consensus_binary "$INSTALL_DIR/manifests/$reth_manifest_artifact")"
if [[ -z "$RETH_BACKUP_HEIGHT" || ! "$RETH_BACKUP_HEIGHT" =~ ^[0-9]+$ ]]; then
log_error "Reth backup manifest does not include a valid numeric height"
exit 1
fi
if [[ "$TELOS_BOOTSTRAP_MODE" == "fast" ]]; then
NODEOS_SNAPSHOT_ARTIFACT="$(discover_latest_snapshot_at_or_before "$NODEOS_BACKUP_BASE_URL" "$RETH_BACKUP_HEIGHT")"
if [[ -z "$NODEOS_SNAPSHOT_ARTIFACT" ]]; then
log_error "Could not discover a nodeos snapshot at or before Reth backup height $RETH_BACKUP_HEIGHT from $NODEOS_BACKUP_BASE_URL"
log_error "Fast mode needs native blocks from the Reth backup height forward. Publish a matching nodeos snapshot, publish a newer Reth backup, or use archive mode."
exit 1
fi
else
NODEOS_SNAPSHOT_ARTIFACT="$(discover_latest_artifact "$NODEOS_BACKUP_BASE_URL" 'snapshot-[^"]+\.bin\.zst')"
if [[ -z "$NODEOS_SNAPSHOT_ARTIFACT" ]]; then
log_error "Could not discover a nodeos snapshot artifact from $NODEOS_BACKUP_BASE_URL"
exit 1
fi
fi
NODEOS_SNAPSHOT_URL="$(artifact_url "$NODEOS_BACKUP_BASE_URL" "$NODEOS_SNAPSHOT_ARTIFACT")"
NODEOS_SNAPSHOT_HEIGHT="$(extract_snapshot_height "$NODEOS_SNAPSHOT_ARTIFACT")"
log_info "Nodeos snapshot: $NODEOS_SNAPSHOT_ARTIFACT${NODEOS_SNAPSHOT_HEIGHT:+ at block $NODEOS_SNAPSHOT_HEIGHT}"
log_info "Reth backup: $RETH_BACKUP_ARTIFACT at block $RETH_BACKUP_HEIGHT"
log_info "Reth backup binary: ${RETH_BACKUP_BINARY_VERSION:-unknown}${RETH_BACKUP_BINARY_COMMIT:+, commit $RETH_BACKUP_BINARY_COMMIT}"
}
validate_reth_backup_compatibility() {
if bool_is_true "$SKIP_RETH_BACKUP_COMPATIBILITY_CHECK"; then
log_warning "Skipping reth backup binary compatibility check"
return
fi
case "$EXPECTED_RETH_VERSION" in
any|ANY)
log_warning "EXPECTED_RETH_VERSION=$EXPECTED_RETH_VERSION disables exact reth backup binary compatibility checks"
return
;;
esac
if [[ -z "$RETH_BACKUP_BINARY" ]]; then
log_error "Reth backup manifest does not include reth_binary, so compatibility cannot be verified."
log_error "Use a manifest that records the producer binary, or set SKIP_RETH_BACKUP_COMPATIBILITY_CHECK=true only after verifying datadir compatibility."
exit 1
fi
if [[ -z "$RETH_BACKUP_BINARY_VERSION" ]]; then
log_error "Could not parse Reth version from manifest reth_binary: $RETH_BACKUP_BINARY"
log_error "Set SKIP_RETH_BACKUP_COMPATIBILITY_CHECK=true only after verifying datadir compatibility."
exit 1
fi
local actual_version
local expected_version
local actual_major
local expected_major
actual_version="$(normalize_version "$RETH_BACKUP_BINARY_VERSION")"
expected_version="$(normalize_version "$EXPECTED_RETH_VERSION")"
if [[ "$actual_version" == "$expected_version" ]]; then
log_info "Reth backup compatibility validated: manifest Reth $actual_version matches expected Reth $expected_version"
return
fi
actual_major="${actual_version%%.*}"
expected_major="${expected_version%%.*}"
log_error "Reth backup binary mismatch."
log_error "Backup manifest reports Reth $actual_version${RETH_BACKUP_BINARY_COMMIT:+, commit $RETH_BACKUP_BINARY_COMMIT}."
log_error "Installer expects Reth $expected_version for telos-reth RELEASE_TAG=$RELEASE_TAG."
if [[ -n "$RETH_BACKUP_SOURCE_DATADIR" ]]; then
log_error "Backup source datadir: $RETH_BACKUP_SOURCE_DATADIR"
fi
if [[ "$actual_major" != "$expected_major" ]]; then
log_error "Reth datadirs are not safe to restore across major versions; do not restore a Reth $actual_major.x backup into Reth $expected_major.x."
fi
log_error "Use a backup generated by the matching telos-reth release, or set EXPECTED_RETH_VERSION to the version built by your selected RELEASE_TAG."
log_error "Set SKIP_RETH_BACKUP_COMPATIBILITY_CHECK=true only after independently verifying datadir compatibility."
exit 1
}
validate_backup_freshness() {
if bool_is_true "$SKIP_BACKUP_FRESHNESS_CHECK"; then
log_warning "Skipping backup freshness check"
return
fi
LIVE_NATIVE_HEAD="$(fetch_native_head)"
if [[ -z "$LIVE_NATIVE_HEAD" || "$LIVE_NATIVE_HEAD" == "null" ]]; then
if bool_is_true "$STRICT_BACKUP_FRESHNESS_CHECK"; then
log_error "Could not fetch live Telos mainnet head from $NATIVE_RPC_URL"
log_error "Set SKIP_BACKUP_FRESHNESS_CHECK=true only if you intentionally want to bypass this check."
exit 1
fi
log_warning "Could not fetch live Telos mainnet head from $NATIVE_RPC_URL; continuing without backup age estimate."
return
fi
local lag=$((LIVE_NATIVE_HEAD - RETH_BACKUP_HEIGHT))
if (( lag < 0 )); then
log_warning "Reth backup height $RETH_BACKUP_HEIGHT is ahead of live head $LIVE_NATIVE_HEAD; continuing."
return
fi
log_info "Live native head: $LIVE_NATIVE_HEAD; reth backup lag: $lag blocks"
if (( lag > MAX_BACKUP_STALENESS_BLOCKS )); then
local approx_days
approx_days=$(awk "BEGIN { printf \"%.1f\", $lag / 172800 }")
if bool_is_true "$STRICT_BACKUP_FRESHNESS_CHECK" && ! bool_is_true "$ALLOW_STALE_BACKUPS"; then
log_error "Backup is stale by $lag blocks (~$approx_days days), exceeding MAX_BACKUP_STALENESS_BLOCKS=$MAX_BACKUP_STALENESS_BLOCKS"
log_error "Refresh the backup or rerun with --allow-stale-backups / ALLOW_STALE_BACKUPS=true."
exit 1
fi
log_warning "Reth backup is $lag blocks behind live head (~$approx_days days); continuing and allowing the node to sync forward."
fi
}
verify_reth_backup_checksum() {
local backup_path="$INSTALL_DIR/$RETH_BACKUP_ARTIFACT"
if [[ -z "$RETH_BACKUP_SHA256" ]]; then
log_warning "No reth backup checksum available; skipping checksum verification"
return
fi
log_info "Verifying reth backup checksum..."
local actual_sha
actual_sha="$(file_sha256 "$backup_path")"
if [[ "$actual_sha" != "$RETH_BACKUP_SHA256" ]]; then
log_error "Checksum verification failed for $RETH_BACKUP_ARTIFACT"
log_error "Expected $RETH_BACKUP_SHA256 but got $actual_sha"
exit 1
fi
}
validate_ship_archive_metadata() {
if [[ "$TELOS_BOOTSTRAP_MODE" != "archive" ]]; then
return
fi
log_info "Validating native SHiP archive metadata..."
local status
local chain_info
local earliest_block
local archive_head
local updated_at
status="$(curl -fsSL "$SHIP_ARCHIVE_BASE_URL/BACKUP-STATUS.txt")"
chain_info="$(echo "$status" | sed -n 's/^chain_info=//p')"
updated_at="$(echo "$status" | awk -F= '$1 == "updated_at" {print $2; exit}')"
earliest_block="$(echo "$chain_info" | jq -r '.earliest_available_block_num')"
archive_head="$(echo "$chain_info" | jq -r '.head_block_num')"
if [[ "$earliest_block" != "1" ]]; then
log_error "Native SHiP archive does not advertise block history from block 1. earliest_available_block_num=$earliest_block"
exit 1
fi
log_info "Native SHiP archive validated: earliest block $earliest_block, head $archive_head, updated $updated_at"
}
check_disk_space() {
local required_gib="$FAST_REQUIRED_GIB"
local override_name="FAST_REQUIRED_GIB"
local available_kib
local available_gib
if [[ "$TELOS_BOOTSTRAP_MODE" == "archive" ]]; then
required_gib="$ARCHIVE_REQUIRED_GIB"
override_name="ARCHIVE_REQUIRED_GIB"
fi
available_kib=$(df -Pk "$INSTALL_DIR" | awk 'NR == 2 {print $4}')
available_gib=$((available_kib / 1024 / 1024))
log_info "Available disk at $INSTALL_DIR: ${available_gib} GiB; required for $TELOS_BOOTSTRAP_MODE mode: ${required_gib} GiB"
if (( available_gib < required_gib )); then
log_error "Not enough free disk space for $TELOS_BOOTSTRAP_MODE mode."
log_error "Use a larger install directory or override $override_name only if you have verified the sizing."
exit 1
fi
}
# Function to check and set a port
check_and_set_port() {
local description="$1"
local default_port="$2"
local selected_port
while true; do
read -p "$description (default: $default_port): " selected_port
selected_port="${selected_port:-$default_port}"
# Check if the port is already selected in this session
if (( ${#SELECTED_PORTS[@]} > 0 )); then
for port in "${SELECTED_PORTS[@]}"; do
if [[ "$port" == "$selected_port" ]]; then
log_info "Port $selected_port has already been selected in this process. Please choose another."
continue 2 # Skip to the next iteration of the outer while loop
fi
done
fi
# Check if the port is in use
if lsof -iTCP:"$selected_port" -sTCP:LISTEN &>/dev/null; then
log_info "Port $selected_port is already in use. Please choose another."
else
# Add the port to the global array
SELECTED_PORTS+=("$selected_port")
echo "$selected_port"
return
fi
done
}
# Initialize inputs
init_inputs() {
if [ "$(pwd)" == "/" ]; then
INSTALL_DIR=/telos
else
INSTALL_DIR=$(pwd)/telos
fi
log_info "Please provide the following values to setup the Telos node"
log_info "All values will be in configuration files which can be changed later"
log_info "Press enter to use the default value"
read -p "Specify the install directory for all services (default: $INSTALL_DIR): " USER_DIR
if [ -n "$USER_DIR" ]; then
INSTALL_DIR="$USER_DIR"
fi
read -p "Specify the version to use (default: $RELEASE_TAG): " USER_TAG
if [ -n "$USER_TAG" ]; then
RELEASE_TAG="$USER_TAG"
fi
if ! bool_is_true "$BOOTSTRAP_MODE_EXPLICIT"; then
read -p "Bootstrap mode: fast or archive (default: $TELOS_BOOTSTRAP_MODE): " USER_BOOTSTRAP_MODE
if [ -n "$USER_BOOTSTRAP_MODE" ]; then
TELOS_BOOTSTRAP_MODE="$USER_BOOTSTRAP_MODE"
fi
fi
case "$TELOS_BOOTSTRAP_MODE" in
fast)
log_info "Fast mode selected: nodeos will start from a state snapshot, so native nodeos block history starts at the snapshot block."
;;
archive)
log_warning "Archive mode selected: this restores native block logs and SHiP state-history and can require more than 3 TiB."
read -p "Type yes to continue with archive mode: " ARCHIVE_CONFIRM
if [[ "$ARCHIVE_CONFIRM" != "yes" ]]; then
log_error "Archive mode was not confirmed"
exit 1
fi
;;
*)
log_error "Invalid bootstrap mode '$TELOS_BOOTSTRAP_MODE'. Use fast or archive."
exit 1
;;
esac
REGION="west"
read -p "For peering with other nodes, are you in the East (Asia/Europe) or West(North/South America). Options are east or west (default: west): " USER_REGION
if [ -n "$USER_REGION" ]; then
REGION="$USER_REGION"
fi
if [ "$REGION" == "west" ]; then
PEERS_URL="https://raw.githubusercontent.com/telosnetwork/telos-evm-installer/refs/heads/main/nodeos-peers/western-peers.txt"
log_info "You have selected the west region"
else
log_info "You have selected the east region"
PEERS_URL="https://raw.githubusercontent.com/telosnetwork/telos-evm-installer/refs/heads/main/nodeos-peers/eastern-peers.txt"
fi
NODEOS_HTTP_RPC_PORT=$(check_and_set_port "Enter the RPC port for http nodeos" 8888)
NODEOS_HTTP_P2P_PORT=$(check_and_set_port "Enter the P2P port for http nodeos" 9876)
NODEOS_SHIP_RPC_PORT=$(check_and_set_port "Enter the RPC port for ship nodeos" 9888)
NODEOS_SHIP_WS_SHIP_PORT=$(check_and_set_port "Enter the SHIP WS port for ship nodeos" 18999)
NODEOS_SHIP_P2P_PORT=$(check_and_set_port "Enter the P2P port for ship nodeos" 9877)
RETH_RPC_PORT=$(check_and_set_port "Enter the RPC port for reth (will be hosted on 0.0.0.0 for external use)" 8545)
RETH_WS_PORT=$(check_and_set_port "Enter the WS RPC port for reth (will be hosted on 0.0.0.0 for external use)" 8546)
RETH_AUTH_RPC_PORT=$(check_and_set_port "Enter the Auth RPC port for reth (this is where the consensus client connects via JWT, will be hosted on 127.0.0.1)" 8551)
RETH_DISCOVERY_PORT=$(check_and_set_port "Enter the discovery port for reth (not used for discovery but reth wants to open it anyway, will be hosted on 127.0.0.1)" 30303)
INSTALL_DIR=$(normalize_path "$INSTALL_DIR")
}
# Initialize workspace
init_workspace() {
log_info "Creating and entering $INSTALL_DIR directory..."
mkdir -p $INSTALL_DIR
cd $INSTALL_DIR || exit 1
}
# Install dependencies
install_dependencies() {
log_info "Installing system dependencies..."
if ! sudo apt-get update; then
log_error "Failed to update package lists"
exit 1
fi
# TODO: Avoid the prompt for timezone
if ! DEBIAN_FRONTEND=noninteractive sudo apt-get install -y \
git \
curl \
build-essential \
clang \
libclang-dev \
gcc \
make \
zstd \
pkg-config \
jq \
libatomic1 \
libcurl4 \
libgmp10 \
zlib1g \
libssl-dev;
then
log_error "Failed to install dependencies"
exit 1
fi
}
# Install Rust
install_rust() {
if ! command_exists rustc; then
log_info "Installing Rust..."
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
. "$HOME/.cargo/env"
else
log_info "Rust is already installed"
fi
}
# Install Nodeos
install_nodeos() {
local installed_version
installed_version="$(dpkg_installed_version "$TELOSZERO_CORE_PACKAGE")"
if [[ "$installed_version" == "$TELOSZERO_CORE_VERSION" ]]; then
log_info "TelosZero Core $TELOSZERO_CORE_VERSION is already installed"
else
if [[ -n "$installed_version" ]]; then
log_info "Upgrading TelosZero Core from $installed_version to $TELOSZERO_CORE_VERSION..."
elif command_exists nodeos; then
log_warning "nodeos is installed, but $TELOSZERO_CORE_PACKAGE $TELOSZERO_CORE_VERSION is not. Replacing old nodeos packages with TelosZero Core."
else
log_info "Installing TelosZero Core $TELOSZERO_CORE_VERSION..."
fi
download_teloszero_core_package
verify_teloszero_core_package
remove_conflicting_nodeos_packages
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y "./$TELOSZERO_CORE_DEB"
fi
installed_version="$(dpkg_installed_version "$TELOSZERO_CORE_PACKAGE")"
if [[ "$installed_version" != "$TELOSZERO_CORE_VERSION" ]]; then
log_error "Expected $TELOSZERO_CORE_PACKAGE $TELOSZERO_CORE_VERSION, found '${installed_version:-not installed}'"
exit 1
fi
if ! command_exists nodeos; then
log_error "TelosZero Core installation completed, but nodeos was not found on PATH"
exit 1
fi
}
setup_nodeos_base() {
local NODEOS_DIR=$INSTALL_DIR/$1
log_info "Setting up nodeos base for $1"
mkdir -p $NODEOS_DIR
cat > $NODEOS_DIR/start.sh << 'EOF'
#!/bin/bash
INSTALL_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
LOG_PATH="$INSTALL_ROOT/nodeos.log"
DATA_DIR_PATH=$INSTALL_ROOT/data
nohup nodeos --disable-replay-opts --data-dir $DATA_DIR_PATH --blocks-log-stride 10000000 --max-retained-block-files 1 --state-history-stride 10000000 --max-retained-history-files 1 --config-dir $INSTALL_ROOT "$@" >> "$LOG_PATH" 2>&1 &
PID="$!"
echo "nodeos started with pid $PID"
echo $PID > $INSTALL_ROOT/nodeos.pid
EOF
cat > $NODEOS_DIR/stop.sh << 'EOF'
#!/bin/bash
INSTALL_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PID_FILE="$INSTALL_ROOT/nodeos.pid"
PID="$( cat $PID_FILE )"
if [ -n "$PID" ]; then
echo "Killing pid " $PID
kill $PID
for i in $(seq 1 20); do
IS_RUNNING=`ps $PID | wc -l`
if [ $IS_RUNNING = "1" ]; then
echo "$INSTALL_ROOT node has been shutdown"
break;
fi
echo "Waiting..."
sleep 2
done
if [ $IS_RUNNING = "2" ]; then
echo "ERROR: Unable to shutdown $INSTALL_ROOT node successfully, check log"
fi
else
echo "No pid found at $PID_FILE"
fi
EOF
chmod +x $NODEOS_DIR/*.sh
cat > $NODEOS_DIR/logging.json << "EOF"
{
"includes": [],
"appenders": [{
"name": "stderr",
"type": "console",
"args": {
"format": "${timestamp} ${thread_name} ${context} ${file}:${line} ${method} ${level}] ${message}",
"stream": "std_error",
"level_colors": [{
"level": "debug",
"color": "green"
},{
"level": "warn",
"color": "brown"
},{
"level": "error",
"color": "red"
}
],
"flush": true
},
"enabled": true
},{
"name": "stdout",
"type": "console",
"args": {
"stream": "std_out",
"level_colors": [{
"level": "debug",
"color": "green"
},{
"level": "warn",
"color": "brown"
},{
"level": "error",
"color": "red"
}
],
"flush": true
},
"enabled": true
},{
"name": "net",
"type": "gelf",
"args": {
"endpoint": "10.10.10.10:12201",
"host": "host_name",
"_network": "mainnet"
},
"enabled": false
}
],
"loggers": [{
"name": "default",
"level": "debug",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "net_plugin_impl",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "http_plugin",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "producer_plugin",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "transaction_success_tracing",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "transaction_failure_tracing",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "trace_api",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr"
]
},{
"name": "transaction_trace_success",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr",
]
},{
"name": "transaction_trace_failure",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr",
]
},{
"name": "state_history",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr",
]
},{
"name": "transaction",
"level": "info",
"enabled": true,
"additivity": false,
"appenders": [
"stderr",
]
}
]
}
EOF
}
setup_nodeos() {
setup_nodeos_base "nodeos-http"
setup_nodeos_base "nodeos-ship"
PEERS=$(curl -s $PEERS_URL)
# Setup http nodeos config
cat > $INSTALL_DIR/nodeos-http/config.ini << EOF
# THIS IS YOUR API SERVER HTTP PORT, PUT IT BEHIND NGINX OR HAPROXY WITH SSL
http-server-address = 127.0.0.1:$NODEOS_HTTP_RPC_PORT
# THIS IS YOUR P2P PORT AND IS ONLY TCP, DO NOT TRY TO PUT SSL IN FRONT OR USE AN HTTP PROXY WITH IT
p2p-listen-endpoint = 127.0.0.1:$NODEOS_HTTP_P2P_PORT
# SET A LOGICAL NAME FOR THIS NODE
agent-name = "Name that peers will see this node as"
# Directory configuration if you are splitting state/blocks/ship/trace...
# note that state currently is not configurable and will be relative to the data-dir
#blocks-dir=
#state-history-dir=
#trace-dir=
wasm-runtime = eos-vm-jit
# DO NOT ENABLE THESE ON A PRODUCER, they may be handy for making replays faster though!
eos-vm-oc-compile-threads = 4
eos-vm-oc-enable = 1
# This can be set as low as the configured RAM on the network,
# but should not be higher than the configured RAM on your server
chain-state-db-size-mb = 65536
contracts-console = true
access-control-allow-origin = *
access-control-allow-headers = *
verbose-http-errors = true
http-validate-host = false
abi-serializer-max-time-ms = 5000
http-max-response-time-ms = 10000
#this must be a high number behind a proxy, as all connections appear to come from the proxy host
p2p-max-nodes-per-host = 100
# PLUGINS
plugin = eosio::http_plugin
plugin = eosio::chain_plugin
plugin = eosio::chain_api_plugin
plugin = eosio::net_plugin
plugin = eosio::producer_plugin
# Peers
$PEERS
EOF
cat > $INSTALL_DIR/nodeos-ship/config.ini << EOF
# THIS IS YOUR API SERVER HTTP PORT, PUT IT BEHIND NGINX OR HAPROXY WITH SSL
http-server-address = 127.0.0.1:$NODEOS_SHIP_RPC_PORT
# THIS IS YOUR P2P PORT AND IS ONLY TCP, DO NOT TRY TO PUT SSL IN FRONT OR USE AN HTTP PROXY WITH IT
p2p-listen-endpoint = 127.0.0.1:$NODEOS_SHIP_P2P_PORT
# SET A LOGICAL NAME FOR THIS NODE
agent-name = "Name that peers will see this node as"
# Directory configuration if you are splitting state/blocks/ship/trace...
# note that state currently is not configurable and will be relative to the data-dir
#blocks-dir=
#state-history-dir=
#trace-dir=
wasm-runtime = eos-vm-jit
# DO NOT ENABLE THESE ON A PRODUCER, they may be handy for making replays faster though!
eos-vm-oc-compile-threads = 4
eos-vm-oc-enable = 1
# This can be set as low as the configured RAM on the network,