forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjustfile
More file actions
884 lines (742 loc) · 33.2 KB
/
Copy pathjustfile
File metadata and controls
884 lines (742 loc) · 33.2 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
set positional-arguments
NIGHTLY := `grep -oE 'nightly-[0-9]{4}-[0-9]{2}-[0-9]{2}' ../mise.toml | head -1`
# The SP1 guest programs live in a nested Cargo workspace (separate for SP1
# crypto patch isolation), so fmt/clippy/tests must target its manifest explicitly.
# Run from this directory so it shares rust/rustfmt.toml and rust/clippy.toml.
SP1_GUEST_MANIFEST := "kona/sp1/programs/Cargo.toml"
# Aliases
alias t := test
alias l := lint
alias f := fmt-fix
alias fmt := fmt-fix
alias b := build
# default recipe to display help information
default:
@just --list
############################### Toolchain ############################
# Install the pinned nightly toolchain
install-nightly:
rustup toolchain install {{NIGHTLY}} --component rustfmt --component rust-src
############################### Build ###############################
# Build the workspace
build *args='':
cargo build --workspace {{args}}
# Build the workspace, excluding example crates, with a fast compile-only profile by default
build-no-examples *args='':
#!/usr/bin/env bash
set -euo pipefail
profile_args="--profile fast-build"
case " {{args}} " in
*" --release "*|*" --profile "*|*" --profile="*)
profile_args=""
;;
esac
exclude_args=""
while IFS= read -r package; do
exclude_args="$exclude_args --exclude $package"
done < <(
cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.manifest_path | contains("/examples/")) | .name'
)
cargo build --workspace $profile_args $exclude_args {{args}}
# Build the workspace in release mode
build-release *args='':
cargo build --workspace --release {{args}}
# Build the kona-sp1 crates under the host toolchain.
build-sp1:
cargo build -p kona-sp1-host-utils -p kona-sp1-client-utils \
-p kona-sp1-ethereum-host-utils -p kona-sp1-ethereum-client-utils \
-p kona-sp1-elfs
# Build kona-node
build-kona-node:
cargo build --release --bin kona-node
# Build kona-node in debug mode (faster compilation for local E2E test iteration)
build-kona-node-debug:
cargo build --bin kona-node
alias build-node := build-kona-node
# Build op-reth
build-op-reth:
cargo build --release --bin op-reth
# Build op-reth in debug mode (faster compilation for local E2E test iteration)
build-op-reth-debug:
cargo build --bin op-reth
############################### Test ################################
# Run all outer-workspace tests. SP1 guests require generated vkeys and are tested by CI after
# their ELFs are built.
test: test-unit test-docs
# Run unit tests (excluding online tests)
test-unit *args="-E '!test(test_online)'":
cargo nextest run --workspace --all-features {{args}}
# Run online tests only
test-online:
cargo nextest run --workspace --all-features -E 'test(test_online)'
# Run doc tests
test-docs:
cargo test --doc --workspace --locked --all-features
# Run tests in the nested SP1 guest program workspace
test-sp1-guest:
cargo test --manifest-path {{SP1_GUEST_MANIFEST}} --workspace --locked
############################### Lint ################################
# Run all lints for the outer workspace. SP1 guests require generated vkeys and are linted by CI
# after their ELFs are built.
lint: fmt-check lint-clippy lint-docs
# Check formatting (requires nightly)
fmt-check:
cargo +{{NIGHTLY}} fmt --all -- --check
# Check formatting across both the main and SP1 guest workspaces. Shared by the
# pre-push git hook and the rust-fmt CI job so the two can't drift.
fmt-check-all: fmt-check fmt-check-sp1-guest
# Fix formatting (requires nightly); also formats the SP1 guest workspace
fmt-fix: fmt-fix-sp1-guest
cargo +{{NIGHTLY}} fmt --all
# Run clippy
lint-clippy:
cargo clippy --workspace --all-features --all-targets -- -D warnings
# Lint-config + self-embed + lock + precompile-patch checks, fmt-check, and clippy for the SP1
# guest program workspace. CI runs this after building the guest ELFs.
lint-sp1-guest: check-no-self-embed check-sp1-guest-lints check-sp1-guest-lock check-sp1-guest-precompile-patches fmt-check-sp1-guest lint-clippy-sp1-guest
# Verify the nested guest workspace uses the root workspace's lint configuration.
[script('bash')]
check-sp1-guest-lints:
set -euo pipefail
begin='# BEGIN SHARED WORKSPACE LINTS'
end='# END SHARED WORKSPACE LINTS'
root=Cargo.toml
guest={{SP1_GUEST_MANIFEST}}
for manifest in "$root" "$guest"; do
[[ $(grep -Fxc "$begin" "$manifest") == 1 ]] || {
echo "error: expected one '$begin' marker in rust/$manifest" >&2
exit 1
}
[[ $(grep -Fxc "$end" "$manifest") == 1 ]] || {
echo "error: expected one '$end' marker in rust/$manifest" >&2
exit 1
}
begin_line=$(grep -Fnx "$begin" "$manifest" | cut -d: -f1)
end_line=$(grep -Fnx "$end" "$manifest" | cut -d: -f1)
[[ "$begin_line" -lt "$end_line" ]] || {
echo "error: '$begin' must precede '$end' in rust/$manifest" >&2
exit 1
}
done
if ! diff -u \
--label rust/Cargo.toml \
--label rust/{{SP1_GUEST_MANIFEST}} \
<(sed -n "/^$begin$/,/^$end$/p" "$root") \
<(sed -n "/^$begin$/,/^$end$/p" "$guest"); then
echo "error: SP1 guest workspace lints differ from rust/Cargo.toml" >&2
echo " Fix: run \`just fmt-fix\` from rust/." >&2
exit 1
fi
[private]
_sync-sp1-guest-lints:
#!/usr/bin/env bash
set -euo pipefail
begin='# BEGIN SHARED WORKSPACE LINTS'
end='# END SHARED WORKSPACE LINTS'
root=Cargo.toml
guest={{SP1_GUEST_MANIFEST}}
for manifest in "$root" "$guest"; do
[[ $(grep -Fxc "$begin" "$manifest") == 1 ]] || {
echo "error: expected one '$begin' marker in rust/$manifest" >&2
exit 1
}
[[ $(grep -Fxc "$end" "$manifest") == 1 ]] || {
echo "error: expected one '$end' marker in rust/$manifest" >&2
exit 1
}
begin_line=$(grep -Fnx "$begin" "$manifest" | cut -d: -f1)
end_line=$(grep -Fnx "$end" "$manifest" | cut -d: -f1)
[[ "$begin_line" -lt "$end_line" ]] || {
echo "error: '$begin' must precede '$end' in rust/$manifest" >&2
exit 1
}
done
tmp=$(mktemp "${guest}.tmp.XXXXXX")
trap 'rm -f "$tmp"' EXIT
cp -p "$guest" "$tmp"
sed "/^$begin$/q" "$guest" | sed '$d' > "$tmp"
sed -n "/^$begin$/,/^$end$/p" "$root" >> "$tmp"
sed -n "/^$end$/,\$p" "$guest" | sed '1d' >> "$tmp"
if ! cmp -s "$tmp" "$guest"; then
mv "$tmp" "$guest"
fi
check-no-self-embed:
#!/usr/bin/env bash
set -euo pipefail
cd kona/sp1
# A leaf guest must not depend on the range-vkey crate: it would embed its own vkey
# (RANGE_VKEY = f(range ELF)), closing a build cycle. Only aggregation guests may depend on it.
# Direct-dep check only; no shared guest crate depends on range-vkeys today.
for f in programs/range/Cargo.toml programs/super-range/Cargo.toml; do
test -f "$f"
if grep -q kona-sp1-range-vkeys "$f"; then
echo "error: $f must not depend on kona-sp1-range-vkeys (build cycle)" >&2
exit 1
fi
done
# Runs before the `--locked` clippy so a kona-only dependency change produces a
# clear fix hint instead of cargo's cryptic "cannot update the lock file because
# --locked was passed" error.
# Verify the SP1 guest Cargo.lock is in sync with its manifests and path-deps
[script('bash')]
check-sp1-guest-lock:
if ! cargo metadata --manifest-path {{SP1_GUEST_MANIFEST}} --format-version 1 --locked >/dev/null 2>&1; then
echo "error: SP1 guest Cargo.lock (rust/kona/sp1/programs/Cargo.lock) is out of date." >&2
echo " A rust/ kona crate's dependencies likely changed; the guest workspace" >&2
echo " path-depends on those crates, so its separate lockfile needs refreshing." >&2
echo " Fix: run \`just lock-sp1-guest\` from rust/ and commit the updated lockfile." >&2
exit 1
fi
# Cargo only warns when a [patch.crates-io] entry is unused. Assert that every
# crypto crate used by each guest program resolves exclusively to its SP1 fork.
[script('bash')]
check-sp1-guest-precompile-patches:
set -euo pipefail
common_patches=(
"sha2 RustCrypto-hashes"
"sha3 RustCrypto-hashes"
"crypto-bigint RustCrypto-bigint"
"k256 elliptic-curves"
"p256 elliptic-curves"
)
verify_program_patches() {
local program="$1"
shift
local dependency_tree
dependency_tree=$(cargo tree \
--manifest-path {{SP1_GUEST_MANIFEST}} \
--locked \
--package "$program" \
--edges normal \
--prefix none \
--format '{p}')
local patch package repository expected_source dependency found
for patch in "$@"; do
read -r package repository <<< "$patch"
expected_source="https://github.com/sp1-patches/${repository}?"
found=false
while IFS= read -r dependency; do
[[ "$dependency" == "$package v"* ]] || continue
found=true
if [[ "$dependency" != *"$expected_source"* ]]; then
echo "error: $program resolves an unpatched $package dependency:" >&2
echo " $dependency" >&2
echo " Expected every $package dependency to resolve from $expected_source" >&2
exit 1
fi
done <<< "$dependency_tree"
if [[ "$found" != true ]]; then
echo "error: $program does not resolve the expected $package dependency." >&2
exit 1
fi
done
}
# The range guests execute the L2 EVM, so they must also use the patched
# substrate-bn backend selected by revm's `bn` feature.
verify_program_patches kona-sp1-range "${common_patches[@]}" "substrate-bn bn"
verify_program_patches kona-sp1-super-range "${common_patches[@]}" "substrate-bn bn"
# Aggregation guests verify proofs but do not execute L2 EVM precompiles.
verify_program_patches kona-sp1-aggregation "${common_patches[@]}"
verify_program_patches kona-sp1-super-aggregation "${common_patches[@]}"
# Resolves without --locked (minimal change), so it only touches entries that
# actually moved rather than bumping unrelated guest deps.
# Refresh the SP1 guest Cargo.lock after changing kona crates it depends on
lock-sp1-guest:
cargo metadata --manifest-path {{SP1_GUEST_MANIFEST}} --format-version 1 >/dev/null
fmt-check-sp1-guest: check-sp1-guest-lints
cargo +{{NIGHTLY}} fmt --manifest-path {{SP1_GUEST_MANIFEST}} --all -- --check
fmt-fix-sp1-guest: _sync-sp1-guest-lints
cargo +{{NIGHTLY}} fmt --manifest-path {{SP1_GUEST_MANIFEST}} --all
lint-clippy-sp1-guest:
cargo clippy --manifest-path {{SP1_GUEST_MANIFEST}} --workspace --all-features --all-targets --locked -- -D warnings
# fmt-check + clippy + test the standalone range-vkeys crate (in neither workspace, so nothing
# else covers it). Requires elf/vkeys.toml and is invoked only after the guest ELFs are built.
check-range-vkeys:
cargo +{{NIGHTLY}} fmt --manifest-path kona/sp1/crates/range-vkeys/Cargo.toml --all -- --check
cargo clippy --manifest-path kona/sp1/crates/range-vkeys/Cargo.toml --all-targets --locked -- -D warnings
cargo test --manifest-path kona/sp1/crates/range-vkeys/Cargo.toml --locked
# Lint Rust documentation
lint-docs:
RUSTDOCFLAGS="--cfg docsrs -D warnings --show-type-layout --generate-link-to-definition -Zunstable-options" \
cargo +{{NIGHTLY}} doc --workspace --all-features --no-deps --document-private-items
############################ Review #################################
# Run the rust-code-reviewer agent over the current branch's Rust diff in an interactive session (latest opus, max effort). The agent reviews, asks which findings to apply, then applies them. Base auto-detects from upstream / origin/HEAD; override with `base=...`.
[script('bash')]
rust-review base='':
set -euo pipefail
command -v claude >/dev/null 2>&1 || { echo "claude CLI not found on PATH; install it before running rust-review." >&2; exit 1; }
base="{{base}}"
if [[ -z "$base" ]]; then
# Prefer origin/HEAD (the repo's default branch) so a pushed feature
# branch that tracks itself on origin doesn't end up diffing against
# HEAD. Fall back to @{upstream}, then origin/develop.
base=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null \
|| git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null \
|| echo origin/develop)
fi
head_ref=$(git rev-parse --abbrev-ref HEAD)
merge_base=$(git merge-base "$base" HEAD)
if git diff --quiet "$merge_base"..HEAD -- ':/rust/**'; then
echo "No Rust changes on $head_ref relative to $base; nothing to review."
exit 0
fi
prompt=$(cat <<EOF
Review the Rust changes on the current branch.
Branch: $head_ref
Base: $base (merge-base $merge_base)
Inspect the diff with:
git diff $merge_base..HEAD -- ':/rust/**'
Follow your standard review process from .claude/agents/rust-code-reviewer.md,
including running the project's lint recipe (mise exec -- just l) from the
appropriate Rust workspace root before reading the diff. Report findings
scoped to the diff only.
After presenting the findings, use the AskUserQuestion tool to ask which
findings (if any) to apply, then apply only the selected ones.
EOF
)
eta=$(date -v +10M '+%H:%M' 2>/dev/null || date -d '+10 min' '+%H:%M')
echo "Reviewer starting with ETA before $eta.."
echo
start=$(date +%s)
trap 'rc=$?; end=$(date +%s); printf "\nReviewer ran for %dm %02ds (exit %d)\n" $(( (end - start) / 60 )) $(( (end - start) % 60 )) "$rc"' EXIT
claude \
--agent rust-code-reviewer \
--model opus \
--effort max \
"$prompt"
echo
echo "Please help us make our reviewer better by contributing to .claude/agents/rust-code-reviewer.md"
############################ no_std #################################
# Check no_std compatibility for proof, protocol, alloy, and op-alloy crates
check-no-std:
#!/usr/bin/env bash
set -euo pipefail
no_std_packages=(
# proof crates
kona-executor
kona-mpt
kona-preimage
kona-proof
kona-proof-interop
# protocol crates
kona-genesis
kona-hardforks
kona-registry
kona-protocol
kona-derive
kona-driver
kona-interop
# utilities
kona-serde
# alloy
alloy-op-evm
alloy-op-hardforks
# op-alloy
op-alloy
op-alloy-consensus
op-alloy-rpc-types
op-alloy-rpc-types-engine
)
# We need to install the riscv32imac-unknown-none-elf target before starting to build the no-std crates.
rustup target add riscv32imac-unknown-none-elf
for package in "${no_std_packages[@]}"; do
echo "Checking no_std build for: $package"
cargo build -p "$package" --target riscv32imac-unknown-none-elf --no-default-features
echo "Successfully checked no_std build for: $package"
done
########################### Benchmarks ##############################
# Run benchmarks (compile only)
bench:
cargo bench --no-run --workspace --features test-utils --exclude example-gossip --exclude example-discovery
########################## Misc tools ###############################
# Audit dependencies for licenses, advisories, and bans
deny:
cargo deny --all-features check all
# Check for unused dependencies (requires nightly + cargo-udeps)
check-udeps:
cargo +{{NIGHTLY}} udeps --release --workspace --all-features --all-targets
# Run cargo hack feature checking, enabling each feature one at a time.
# Cheaper than the full powerset (~n checks per crate vs ~2^n).
# shuffle: "true" to shuffle package order before partitioning (spreads heavy/light crates more evenly)
# seed: deterministic seed for shuffle (all partition nodes must use the same seed)
hack partition="" shuffle="false" seed="default":
#!/usr/bin/env bash
set -euo pipefail
if [ "{{partition}}" != "" ]; then
echo "Running cargo hack with partition {{partition}}"
else
echo "Running cargo hack without partition"
fi
PKG_FLAGS=""
if [ "{{shuffle}}" = "true" ]; then
PKGS=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[].name' \
| shuf --random-source=<(openssl enc -aes-256-ctr -pass "pass:{{seed}}" -nosalt </dev/zero 2>/dev/null))
PKG_FLAGS=$(echo "$PKGS" | sed 's/^/-p /' | tr '\n' ' ')
echo "Shuffled package order (seed={{seed}}): $PKGS"
fi
cargo hack check --each-feature --no-dev-deps $PKG_FLAGS {{ if partition != "" { "--partition " + partition } else { "" } }}
# Check each crate's test targets in isolation with default features only.
# Catches tests that only compile via workspace feature unification: a crate
# whose tests use a dependency's feature they never declare, or a non-default
# feature of the crate itself (enable it for the test build via a self
# dev-dependency).
# seed: deterministic seed for shuffle (all partition nodes must use the same seed)
hack-tests-default partition="" shuffle="false" seed="default":
#!/usr/bin/env bash
set -euo pipefail
if [ "{{partition}}" != "" ]; then
echo "Running cargo hack (tests, default-features) with partition {{partition}}"
else
echo "Running cargo hack (tests, default-features) without partition"
fi
PKG_FLAGS=""
if [ "{{shuffle}}" = "true" ]; then
PKGS=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[].name' \
| shuf --random-source=<(openssl enc -aes-256-ctr -pass "pass:{{seed}}" -nosalt </dev/zero 2>/dev/null))
PKG_FLAGS=$(echo "$PKGS" | sed 's/^/-p /' | tr '\n' ' ')
echo "Shuffled package order (seed={{seed}}): $PKGS"
fi
cargo hack check --tests $PKG_FLAGS {{ if partition != "" { "--partition " + partition } else { "" } }}
########################### Release #################################
# Release crates at the given version.
# target: either "workspace" (releases every subdir under rust/ except op-reth,
# in dependency order) or one of the subdir names (op-alloy,
# alloy-op-hardforks, alloy-op-evm, op-revm, kona).
# mode: "dry" (default, prints changes without applying) or "execute"
# Extra args are forwarded to `cargo release`.
# kona is split into two topologically-ordered batches to stay under the
# crates.io "existing crates" rate limit of 30.
# Example: just release workspace 1.0.0
# Example: just release kona 1.0.0 execute
# Example: just release op-alloy 1.0.0 execute --no-confirm
release TARGET VERSION MODE="dry" *ARGS="":
#!/usr/bin/env bash
set -euo pipefail
if ! command -v cargo-release >/dev/null 2>&1; then
echo "cargo-release not found, installing..."
cargo install cargo-release
fi
case "{{MODE}}" in
dry) EXTRA="" ;;
execute) EXTRA="--execute" ;;
*) echo "error: mode must be 'dry' or 'execute', got '{{MODE}}'" >&2; exit 1 ;;
esac
METADATA=$(cargo metadata --format-version=1 --no-deps)
# Hardcoded split for kona: 32 crates exceeds crates.io's "existing crates"
# rate limit of 30, so we publish in two topologically-ordered batches.
# When adding a new crate under rust/kona/, append it to whichever batch
# leaves both <= 30 (preserving topo order: deps before dependents).
KONA_BATCH_1="kona-genesis kona-macros kona-mpt kona-preimage kona-serde kona-sources kona-registry kona-std-fpvm kona-cli kona-peers kona-protocol kona-std-fpvm-proc kona-disc kona-engine kona-executor kona-hardforks"
KONA_BATCH_2="kona-interop example-discovery kona-gossip execution-fixture kona-derive kona-rpc kona-driver kona-providers-alloy kona-providers-local kona-proof kona-node-service kona-proof-interop example-gossip kona-node kona-client kona-host"
ACTUAL_KONA=$(echo "$METADATA" | jq -r '.packages[] | select((.manifest_path | contains("/rust/kona/")) and (.manifest_path | contains("/rust/kona/sp1/") | not)) | .name' | sort -u)
EXPECTED_KONA=$(echo "$KONA_BATCH_1 $KONA_BATCH_2" | tr ' ' '\n' | sort -u)
if [[ "$ACTUAL_KONA" != "$EXPECTED_KONA" ]]; then
echo "error: hardcoded kona batches don't match actual kona crates." >&2
echo " diff (actual vs expected):" >&2
diff <(echo "$ACTUAL_KONA") <(echo "$EXPECTED_KONA") >&2 || true
echo " Update KONA_BATCH_1/KONA_BATCH_2 in rust/justfile." >&2
exit 1
fi
# Release order is dependency-driven: foundational crates first, kona last.
ALL_SUBDIRS="op-alloy alloy-op-hardforks alloy-op-evm op-revm kona"
EXPECTED_SUBDIRS=$(echo "$ALL_SUBDIRS" | tr ' ' '\n' | sort -u)
ACTUAL_SUBDIRS=$(echo "$METADATA" \
| jq -r '.packages[].manifest_path' \
| sed -nE 's|.*/rust/([^/]+)/.*|\1|p' \
| grep -vx 'op-reth' \
| sort -u)
if [[ "$EXPECTED_SUBDIRS" != "$ACTUAL_SUBDIRS" ]]; then
echo "error: hardcoded ALL_SUBDIRS list doesn't match actual subdirs under rust/." >&2
echo " diff (expected vs actual):" >&2
diff <(echo "$EXPECTED_SUBDIRS") <(echo "$ACTUAL_SUBDIRS") >&2 || true
echo " Update ALL_SUBDIRS in rust/justfile." >&2
exit 1
fi
case "{{TARGET}}" in
workspace) SUBDIRS="$ALL_SUBDIRS" ;;
*)
if ! echo " $ALL_SUBDIRS " | grep -q " {{TARGET}} "; then
echo "error: unknown target '{{TARGET}}'. Must be 'workspace' or one of: $ALL_SUBDIRS" >&2
exit 1
fi
SUBDIRS="{{TARGET}}"
;;
esac
run_release() {
local label="$1"; shift
local pkgs
pkgs=$(printf -- '-p %s ' "$@")
echo "=== $label → version {{VERSION}} (mode: {{MODE}}) ==="
printf '%s\n' "$@"
echo
cargo release {{VERSION}} $pkgs $EXTRA {{ARGS}}
}
for subdir in $SUBDIRS; do
if [[ "$subdir" == "kona" ]]; then
run_release "rust/kona/ batch 1/2" $KONA_BATCH_1
run_release "rust/kona/ batch 2/2" $KONA_BATCH_2
else
PKGS_LIST=$(echo "$METADATA" \
| jq -r --arg dir "/rust/$subdir/" \
'.packages[] | select(.manifest_path | contains($dir)) | .name' \
| sort -u)
[[ -z "$PKGS_LIST" ]] && continue
run_release "rust/$subdir/" $PKGS_LIST
fi
done
######################### Documentation ################################
DOCS_DIR := justfile_directory() / "docs"
# Start the documentation development server
docs-dev:
cd "{{DOCS_DIR}}" && just docs-dev
# Build the documentation for production
docs-build:
cd "{{DOCS_DIR}}" && just docs-build
# Preview the built documentation
docs-preview:
cd "{{DOCS_DIR}}" && just docs-preview
######################### Kona Prestates ##############################
KONA_DIR := justfile_directory() / "kona"
MIPS64_TARGET_SPEC := justfile_directory() / "kona/docker/cannon/mips64-unknown-none.json"
MIPS64_ELF_DIR := justfile_directory() / "target/mips64-unknown-none/release-client-lto"
# Prestate variants, as `<cargo bin>:<artifacts dir>` pairs. The build side
# derives everything from this; the artifacts dir names are additionally
# hardcoded by rust/kona/.gitignore, ops/prestate-reproducibility/build-prestates.sh,
# .circleci/continue/rust-e2e.yml, op-e2e/config/init.go and
# op-devstack/{shared/challenger,sysgo}, so renaming one means updating those too.
KONA_PRESTATE_VARIANTS := "kona-client:prestate-artifacts-cannon kona-client-int:prestate-artifacts-cannon-interop"
# Print the prestate variants as `<cargo bin> <artifacts dir>` lines. This is
# the only place the table is split: every consumer, in this justfile and out,
# reads these lines. A malformed entry fails here rather than downstream of an
# `rm -rf` built from an empty field.
#
# Output is all-or-nothing: the whole table is validated before anything is
# printed, so a consumer reading this through a pipe or process substitution —
# where the exit status is not visible — can treat "no lines" as the only
# failure mode, rather than silently acting on a truncated table.
kona-prestate-variants:
#!/usr/bin/env bash
set -euo pipefail
lines=()
for pair in {{KONA_PRESTATE_VARIANTS}}; do
variant="${pair%%:*}"
dir="${pair#*:}"
if [[ -z "$variant" || "$variant" == "$pair" || ! "$dir" =~ ^prestate-artifacts-[a-zA-Z0-9_-]+$ ]]; then
echo "malformed KONA_PRESTATE_VARIANTS entry '${pair}', want '<cargo bin>:prestate-artifacts-<name>'" >&2
exit 1
fi
lines+=("${variant} ${dir}")
done
(( ${#lines[@]} > 0 )) || { echo "KONA_PRESTATE_VARIANTS is empty" >&2; exit 1; }
printf '%s\n' "${lines[@]}"
# Build the kona-client ELFs for the MIPS64 cannon target. Always builds every
# variant: they are bins of the same package resolving to the same features, so
# one cargo invocation compiles the shared dependency graph once instead of once
# per variant, and the second binary costs only its own LTO link. Building a
# subset would also leave the other variant's stale ELF in the target dir for
# stage-kona-client-elfs and generate-kona-prestates to pick up.
build-kona-client-elfs:
#!/usr/bin/env bash
set -euo pipefail
variants=$(just kona-prestate-variants)
[[ -n "$variants" ]] || { echo "no prestate variants" >&2; exit 1; }
bins=()
while read -r variant _; do bins+=(--bin "$variant"); done <<<"$variants"
# Ensure nightly toolchain with rust-src is installed
just install-nightly
# Cross-compilation environment
export CC_mips64_unknown_none=mips64-linux-gnuabi64-gcc
export CXX_mips64_unknown_none=mips64-linux-gnuabi64-g++
export CARGO_TARGET_MIPS64_UNKNOWN_NONE_LINKER=mips64-linux-gnuabi64-gcc
export RUSTFLAGS="-Clink-arg=-e_start -Cllvm-args=-mno-check-zero-division"
export CARGO_BUILD_TARGET="{{MIPS64_TARGET_SPEC}}"
export RUSTUP_TOOLCHAIN="{{NIGHTLY}}"
# Custom configs support
if [[ -n "${KONA_CUSTOM_CONFIGS_DIR:-}" ]]; then
export KONA_CUSTOM_CONFIGS=true
fi
echo "Building kona-client ELFs"
cargo build \
-Zbuild-std=core,alloc \
-Zjson-target-spec \
-p kona-client \
"${bins[@]}" \
--locked \
--profile release-client-lto
# Copy the built kona-client ELFs into DEST. Only the Docker build needs this:
# it builds into a cache mount, which does not survive the layer it is mounted
# in, so the prestate stage cannot read the ELFs from the cargo target dir.
stage-kona-client-elfs DEST:
#!/usr/bin/env bash
set -euo pipefail
variants=$(just kona-prestate-variants)
[[ -n "$variants" ]] || { echo "no prestate variants" >&2; exit 1; }
mkdir -p "{{DEST}}"
while read -r variant _; do
cp "{{MIPS64_ELF_DIR}}/${variant}" "{{DEST}}/${variant}"
done <<<"$variants"
# Lint kona-std-fpvm for the MIPS64 cannon target
lint-kona-cannon:
#!/usr/bin/env bash
set -euo pipefail
just install-nightly
export CC_mips64_unknown_none=mips64-linux-gnuabi64-gcc
export CXX_mips64_unknown_none=mips64-linux-gnuabi64-g++
export CARGO_TARGET_MIPS64_UNKNOWN_NONE_LINKER=mips64-linux-gnuabi64-gcc
export RUSTFLAGS="-Clink-arg=-e_start -Cllvm-args=-mno-check-zero-division"
export CARGO_BUILD_TARGET="{{MIPS64_TARGET_SPEC}}"
export RUSTUP_TOOLCHAIN="{{NIGHTLY}}"
cargo clippy -p kona-std-fpvm --all-features -Zbuild-std=core,alloc -Zjson-target-spec -- -D warnings
# Build all kona prestates natively. Use build-kona-reproducible-prestate for
# a hash that matches CI and release builds.
build-kona-prestates:
#!/usr/bin/env bash
set -euo pipefail
echo "=== Building cannon ==="
# cannon/justfile imports ../justfiles/go.just which imports git.just.
# These relative imports resolve from cannon/'s directory, so we cd there
# and call just directly — NOT via rust/justfile delegation.
(cd "{{justfile_directory()}}/../cannon" && just cannon)
echo "=== Building kona-client ELFs ==="
just build-kona-client-elfs
just generate-kona-prestates \
"{{justfile_directory()}}/../cannon/bin/cannon" \
"{{MIPS64_ELF_DIR}}" \
"{{KONA_DIR}}"
# Generate the cannon prestate for every variant from already-built ELFs.
# CANNON_BIN: the cannon executable. ELF_DIR: holds one ELF per variant, named
# after its cargo bin. OUT_ROOT: parent of the per-variant artifacts dirs.
generate-kona-prestates CANNON_BIN ELF_DIR OUT_ROOT:
#!/usr/bin/env bash
set -euo pipefail
variants=$(just kona-prestate-variants)
[[ -n "$variants" ]] || { echo "no prestate variants" >&2; exit 1; }
# Collect the lines before looping, so the loop body doesn't inherit the
# variant list on its stdin. Accumulated with `while read` rather than
# `mapfile`: that's a bash 4 builtin, and macOS ships bash 3.2.
entries=()
while IFS= read -r entry; do entries+=("$entry"); done <<<"$variants"
for entry in "${entries[@]}"; do
read -r variant dir <<<"$entry"
[[ -n "$dir" ]] || { echo "empty artifacts dir for '${variant}'" >&2; exit 1; }
output="{{OUT_ROOT}}/${dir}"
# Clear the variant's dir immediately before writing it: a hash-named
# preimage left over from a previous build is indistinguishable from this
# build's output. Nothing is removed until we are about to replace it.
rm -rf "$output"
mkdir -p "$output"
echo "=== Generating prestate for ${variant} ==="
"{{CANNON_BIN}}" load-elf \
--path="{{ELF_DIR}}/${variant}" \
--out="$output/prestate.bin.gz" \
--meta="$output/meta.json" \
--type multithreaded64-5
"{{CANNON_BIN}}" run \
--proof-at "=0" \
--stop-at "=1" \
--input "$output/prestate.bin.gz" \
--meta "$output/meta.json" \
--proof-fmt "$output/%d.json" \
--output ""
mv "$output/0.json" "$output/prestate-proof.json"
# Copy with hash-based name for challenger lookup. -e so a missing or
# null .pre fails here instead of producing a "null.bin.gz".
hash=$(jq -er .pre "$output/prestate-proof.json")
cp "$output/prestate.bin.gz" "$output/${hash}.bin.gz"
echo "Prestate for ${variant}: ${hash}"
done
# Build every reproducible kona prestate via Docker, in a single build so the
# shared dependency graph is compiled once. Cannon is built from source as a
# stage within the Dockerfile. Build context is the monorepo root.
# Set KONA_CUSTOM_CONFIGS_DIR to bake custom chain configs into the prestates.
build-kona-reproducible-prestate:
#!/usr/bin/env bash
set -euo pipefail
variants=$(just kona-prestate-variants)
[[ -n "$variants" ]] || { echo "no prestate variants" >&2; exit 1; }
# Array first, not a `while read` loop around the body: see
# generate-kona-prestates. No `mapfile` — bash 4 only, missing on macOS.
entries=()
while IFS= read -r entry; do entries+=("$entry"); done <<<"$variants"
# Export to a staging dir rather than straight over KONA_DIR: --output is a
# filesystem write of the target stage, so it must not be aimed at the crate
# source root. Each variant is checked and swapped into place only once the
# build has actually produced it, so a failed build leaves the previous
# prestates intact.
export_dir=$(mktemp -d)
tmp_configs_context=""
cleanup() {
rm -rf "${export_dir}"
# Only ever the temp dir we made — never a caller's KONA_CUSTOM_CONFIGS_DIR.
if [[ -n "${tmp_configs_context}" ]]; then
rm -rf "${tmp_configs_context}"
fi
}
trap cleanup EXIT
# The Dockerfile always copies from the `kona-custom-configs` named build
# context, so point it at an empty temp dir when no configs are requested.
if [[ -n "${KONA_CUSTOM_CONFIGS_DIR:-}" ]]; then
if [[ ! -d "${KONA_CUSTOM_CONFIGS_DIR}" ]]; then
echo "KONA_CUSTOM_CONFIGS_DIR=${KONA_CUSTOM_CONFIGS_DIR} is not a directory" >&2
exit 1
fi
custom_configs_context="${KONA_CUSTOM_CONFIGS_DIR}"
custom_configs_flag=true
else
tmp_configs_context=$(mktemp -d)
custom_configs_context="${tmp_configs_context}"
custom_configs_flag=false
fi
docker build \
--platform linux/amd64 \
--target export-stage \
--build-arg KONA_CUSTOM_CONFIGS="${custom_configs_flag}" \
--build-context kona-custom-configs="${custom_configs_context}" \
--output "${export_dir}" \
--progress plain \
-f "{{KONA_DIR}}/docker/fpvm-prestates/cannon-repro.dockerfile" \
"{{justfile_directory()}}/.."
for entry in "${entries[@]}"; do
read -r variant dir <<<"$entry"
if [[ ! -s "${export_dir}/${dir}/prestate-proof.json" ]]; then
echo "build exported no prestate for ${variant} (expected ${dir}/prestate-proof.json)" >&2
exit 1
fi
done
for entry in "${entries[@]}"; do
read -r variant dir <<<"$entry"
[[ -n "$dir" ]] || { echo "empty artifacts dir for '${variant}'" >&2; exit 1; }
rm -rf "{{KONA_DIR}}/${dir}"
mv "${export_dir}/${dir}" "{{KONA_DIR}}/${dir}"
hash=$(jq -er .pre "{{KONA_DIR}}/${dir}/prestate-proof.json")
echo "Prestate for ${variant}: ${hash}"
done
output-kona-prestate-hash:
#!/usr/bin/env bash
set -euo pipefail
variants=$(just kona-prestate-variants)
[[ -n "$variants" ]] || { echo "no prestate variants" >&2; exit 1; }
entries=()
while IFS= read -r entry; do entries+=("$entry"); done <<<"$variants"
echo "-------------------- Kona Prestates --------------------"
echo ""
for entry in "${entries[@]}"; do
read -r variant dir <<<"$entry"
hash=$(jq -er .pre "{{KONA_DIR}}/${dir}/prestate-proof.json")
echo "${variant} absolute prestate hash:"
echo "${hash}"
echo ""
done
reproducible-kona-prestate: build-kona-reproducible-prestate output-kona-prestate-hash
clean-kona-prestates:
#!/usr/bin/env bash
set -euo pipefail
variants=$(just kona-prestate-variants)
[[ -n "$variants" ]] || { echo "no prestate variants" >&2; exit 1; }
while read -r _ dir; do
[[ -n "$dir" ]] || { echo "empty artifacts dir in variant table" >&2; exit 1; }
rm -rf "{{KONA_DIR}}/${dir}"
done <<<"$variants"