diff --git a/.github/workflows/arbitrator-ci.yml b/.github/workflows/arbitrator-ci.yml index 392eb876c0..47646017ac 100644 --- a/.github/workflows/arbitrator-ci.yml +++ b/.github/workflows/arbitrator-ci.yml @@ -50,15 +50,13 @@ jobs: - name: Install go uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: 1.23.x - name: Install custom go-ethereum run: | cd /tmp - git clone --branch v1.13.8 --depth 1 https://github.com/ethereum/go-ethereum.git + git clone --branch v1.14.11 --depth 1 https://github.com/ethereum/go-ethereum.git cd go-ethereum - # Enable KZG point evaluation precompile early - sed -i 's#var PrecompiledContractsBerlin = map\[common.Address\]PrecompiledContract{#\0 common.BytesToAddress([]byte{0x0a}): \&kzgPointEvaluation{},#g' core/vm/contracts.go go build -o /usr/local/bin/geth ./cmd/geth - name: Setup nodejs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acd6295b7c..8ed49634ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: - name: Install go uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: 1.23.x - name: Install wasm-ld run: | @@ -87,12 +87,12 @@ jobs: uses: actions/cache@v3 with: path: | - ~/.cargo/registry/ - ~/.cargo/git/ + ~/.cargo/ arbitrator/target/ arbitrator/wasm-libraries/target/ - arbitrator/wasm-libraries/soft-float/SoftFloat/build + arbitrator/wasm-libraries/soft-float/ target/etc/initial-machine-cache/ + /home/runner/.rustup/toolchains/ key: ${{ runner.os }}-cargo-${{ steps.install-rust.outputs.rustc_hash }}-min-${{ hashFiles('arbitrator/Cargo.lock') }}-${{ matrix.test-mode }} restore-keys: ${{ runner.os }}-cargo-${{ steps.install-rust.outputs.rustc_hash }}- @@ -145,89 +145,62 @@ jobs: env: TEST_STATE_SCHEME: path run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -timeout 20m -tags=cionly > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then - exit 1 - fi - done + echo "Running tests with Path Scheme" >> full.log + ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 20m --cover - name: run tests without race detection and hash state scheme if: matrix.test-mode == 'defaults' env: TEST_STATE_SCHEME: hash run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -timeout 20m -tags=cionly; then - exit 1 - fi - done - - - name: run tests with race detection and path state scheme - if: matrix.test-mode == 'race' - env: - TEST_STATE_SCHEME: path - run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -race -timeout 30m > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then - exit 1 - fi - done + echo "Running tests with Hash Scheme" >> full.log + ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 20m - name: run tests with race detection and hash state scheme if: matrix.test-mode == 'race' env: TEST_STATE_SCHEME: hash run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -race -timeout 30m; then - exit 1 - fi - done + echo "Running tests with Hash Scheme" >> full.log + ${{ github.workspace }}/.github/workflows/gotestsum.sh --race --timeout 30m - name: run redis tests if: matrix.test-mode == 'defaults' - run: TEST_REDIS=redis://localhost:6379/0 gotestsum --format short-verbose -- -p 1 -run TestRedis ./arbnode/... ./system_tests/... -coverprofile=coverage-redis.txt -covermode=atomic -coverpkg=./... + run: | + echo "Running redis tests" >> full.log + TEST_REDIS=redis://localhost:6379/0 gotestsum --format short-verbose -- -p 1 -run TestRedis ./arbnode/... ./system_tests/... -coverprofile=coverage-redis.txt -covermode=atomic -coverpkg=./... + + - name: create block input json file + if: matrix.test-mode == 'defaults' + run: | + gotestsum --format short-verbose -- -run TestProgramStorage$ ./system_tests/... --count 1 --recordBlockInputs.WithBaseDir="${{ github.workspace }}/target" --recordBlockInputs.WithTimestampDirEnabled=false --recordBlockInputs.WithBlockIdInFileNameEnabled=false + + - name: run arbitrator prover on block input json + if: matrix.test-mode == 'defaults' + run: | + make build-prover-bin + target/bin/prover target/machines/latest/machine.wavm.br -b --json-inputs="${{ github.workspace }}/target/TestProgramStorage/block_inputs.json" + + - name: run jit prover on block input json + if: matrix.test-mode == 'defaults' + run: | + make build-jit + if [ -n "$(target/bin/jit --binary target/machines/latest/replay.wasm --cranelift --json-inputs='${{ github.workspace }}/target/TestProgramStorage/block_inputs.json')" ]; then + echo "Error: Command produced output." + exit 1 + fi - name: run challenge tests if: matrix.test-mode == 'challenge' - run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=challengetest -run=TestChallenge > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then - exit 1 - fi - done + run: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags challengetest --run TestChallenge --cover - name: run stylus tests if: matrix.test-mode == 'stylus' - run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=stylustest -run="TestProgramArbitrator" > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then - exit 1 - fi - done + run: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags stylustest --run TestProgramArbitrator --timeout 60m --cover - name: run long stylus tests if: matrix.test-mode == 'long' - run: | - packages=`go list ./...` - for package in $packages; do - echo running tests for $package - if ! stdbuf -oL gotestsum --format short-verbose --packages="$package" --rerun-fails=2 --no-color=false -- -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -tags=stylustest -run="TestProgramLong" > >(stdbuf -oL tee -a full.log | grep -vE "INFO|seal"); then - exit 1 - fi - done + run: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags stylustest --run TestProgramLong --timeout 60m --cover - name: Archive detailed run log uses: actions/upload-artifact@v3 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1cde8f06b9..26447947d4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -73,7 +73,7 @@ jobs: - name: Install go uses: actions/setup-go@v4 with: - go-version: 1.21.x + go-version: 1.23.x - name: Install rust stable uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/gotestsum.sh b/.github/workflows/gotestsum.sh new file mode 100755 index 0000000000..ed631847b7 --- /dev/null +++ b/.github/workflows/gotestsum.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +check_missing_value() { + if [[ $1 -eq 0 || $2 == -* ]]; then + echo "missing $3 argument value" + exit 1 + fi +} + +timeout="" +tags="" +run="" +race=false +cover=false +while [[ $# -gt 0 ]]; do + case $1 in + --timeout) + shift + check_missing_value $# "$1" "--timeout" + timeout=$1 + shift + ;; + --tags) + shift + check_missing_value $# "$1" "--tags" + tags=$1 + shift + ;; + --run) + shift + check_missing_value $# "$1" "--run" + run=$1 + shift + ;; + --race) + race=true + shift + ;; + --cover) + cover=true + shift + ;; + *) + echo "Invalid argument: $1" + exit 1 + ;; + esac +done + +packages=$(go list ./...) +for package in $packages; do + cmd="stdbuf -oL gotestsum --format short-verbose --packages=\"$package\" --rerun-fails=2 --no-color=false --" + + if [ "$timeout" != "" ]; then + cmd="$cmd -timeout $timeout" + fi + + if [ "$tags" != "" ]; then + cmd="$cmd -tags=$tags" + fi + + if [ "$run" != "" ]; then + cmd="$cmd -run=$run" + fi + + if [ "$race" == true ]; then + cmd="$cmd -race" + fi + + if [ "$cover" == true ]; then + cmd="$cmd -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/..." + fi + + cmd="$cmd > >(stdbuf -oL tee -a full.log | grep -vE \"INFO|seal\")" + + echo "" + echo running tests for "$package" + echo "$cmd" + + if ! eval "$cmd"; then + exit 1 + fi +done diff --git a/.github/workflows/shellcheck-ci.yml b/.github/workflows/shellcheck-ci.yml new file mode 100644 index 0000000000..d1c7b58580 --- /dev/null +++ b/.github/workflows/shellcheck-ci.yml @@ -0,0 +1,30 @@ +name: ShellCheck CI +run-name: ShellCheck CI triggered from @${{ github.actor }} of ${{ github.head_ref }} + +on: + workflow_dispatch: + merge_group: + pull_request: + push: + branches: + - master + +jobs: + shellcheck: + name: Run ShellCheck + runs-on: ubuntu-8 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + with: + ignore_paths: >- + ./fastcache/** + ./contracts/** + ./safe-smart-account/** + ./go-ethereum/** + ./nitro-testnode/** + ./brotli/** + ./arbitrator/** diff --git a/.github/workflows/submodule-pin-check.sh b/.github/workflows/submodule-pin-check.sh deleted file mode 100755 index aecb287ce1..0000000000 --- a/.github/workflows/submodule-pin-check.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -declare -Ar exceptions=( - [contracts]=origin/develop - [nitro-testnode]=origin/master - - #TODO Rachel to check these are the intended branches. - [arbitrator/langs/c]=origin/vm-storage-cache - [arbitrator/tools/wasmer]=origin/adopt-v4.2.8 -) - -divergent=0 -for mod in `git submodule --quiet foreach 'echo $name'`; do - branch=origin/HEAD - if [[ -v exceptions[$mod] ]]; then - branch=${exceptions[$mod]} - fi - - if ! git -C $mod merge-base --is-ancestor HEAD $branch; then - echo $mod diverges from $branch - divergent=1 - fi -done - -exit $divergent - diff --git a/.github/workflows/submodule-pin-check.yml b/.github/workflows/submodule-pin-check.yml index f045f71f68..60dd8ad827 100644 --- a/.github/workflows/submodule-pin-check.yml +++ b/.github/workflows/submodule-pin-check.yml @@ -18,11 +18,33 @@ jobs: with: fetch-depth: 0 submodules: true + persist-credentials: false + ref: "${{ github.event.pull_request.head.sha }}" - name: Check all submodules are ancestors of origin/HEAD or configured branch run: | status_state="pending" - if ${{ github.workspace }}/.github/workflows/submodule-pin-check.sh; then + declare -Ar exceptions=( + [contracts]=origin/develop + [nitro-testnode]=origin/master + + #TODO Rachel to check these are the intended branches. + [arbitrator/langs/c]=origin/vm-storage-cache + [arbitrator/tools/wasmer]=origin/adopt-v4.2.8 + ) + divergent=0 + for mod in `git submodule --quiet foreach 'echo $name'`; do + branch=origin/HEAD + if [[ -v exceptions[$mod] ]]; then + branch=${exceptions[$mod]} + fi + + if ! git -C $mod merge-base --is-ancestor HEAD $branch; then + echo $mod diverges from $branch + divergent=1 + fi + done + if [ $divergent -eq 0 ]; then status_state="success" else resp="$(curl -sSL --fail-with-body \ diff --git a/Dockerfile b/Dockerfile index 37226c397c..aba5432254 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,7 +66,7 @@ COPY --from=wasm-libs-builder /workspace/ / FROM wasm-base AS wasm-bin-builder # pinned go version -RUN curl -L https://golang.org/dl/go1.21.10.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf - +RUN curl -L https://golang.org/dl/go1.23.1.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf - COPY ./Makefile ./go.mod ./go.sum ./ COPY ./arbcompress ./arbcompress COPY ./arbos ./arbos @@ -218,8 +218,9 @@ COPY ./scripts/download-machine.sh . #RUN ./download-machine.sh consensus-v20 0x8b104a2e80ac6165dc58b9048de12f301d70b02a0ab51396c22b4b4b802a16a4 RUN ./download-machine.sh consensus-v30 0xb0de9cb89e4d944ae6023a3b62276e54804c242fd8c4c2d8e6cc4450f5fa8b1b && true RUN ./download-machine.sh consensus-v31 0x260f5fa5c3176a856893642e149cf128b5a8de9f828afec8d11184415dd8dc69 +RUN ./download-machine.sh consensus-v32 0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39 -FROM golang:1.21.10-bookworm AS node-builder +FROM golang:1.23.1-bookworm AS node-builder WORKDIR /workspace ARG version="" ARG datetime="" diff --git a/LICENSE.md b/LICENSE.md index ea9a53da75..25768b3010 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -22,7 +22,7 @@ Additional Use Grant: You may use the Licensed Work in a production environment Expansion Program Term of Use](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf). For purposes of this Additional Use Grant, the "Covered Arbitrum Chains" are (a) Arbitrum One (chainid:42161), Arbitrum Nova (chainid:42170), - rbitrum Rinkeby testnet/Rinkarby (chainid:421611),Arbitrum Nitro + Arbitrum Rinkeby testnet/Rinkarby (chainid:421611),Arbitrum Nitro Goerli testnet (chainid:421613), and Arbitrum Sepolia Testnet (chainid:421614); (b) any future blockchains authorized to be designated as Covered Arbitrum Chains by the decentralized autonomous diff --git a/Makefile b/Makefile index b736b04906..88bbd8dabe 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ replay_wasm=$(output_latest)/replay.wasm arb_brotli_files = $(wildcard arbitrator/brotli/src/*.* arbitrator/brotli/src/*/*.* arbitrator/brotli/*.toml arbitrator/brotli/*.rs) .make/cbrotli-lib .make/cbrotli-wasm arbitrator_generated_header=$(output_root)/include/arbitrator.h -arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, wasi_stub host_io soft-float arbcompress user_host program_exec) +arbitrator_wasm_libs=$(patsubst %, $(output_root)/machines/latest/%.wasm, forward wasi_stub host_io soft-float arbcompress user_host program_exec) arbitrator_stylus_lib=$(output_root)/lib/libstylus.a prover_bin=$(output_root)/bin/prover arbitrator_jit=$(output_root)/bin/jit @@ -75,12 +75,16 @@ arbitrator_test_wasms=$(patsubst %.wat,%.wasm, $(arbitrator_tests_wat)) $(patsub arbitrator_tests_link_info = $(shell cat $(arbitrator_cases)/link.txt | xargs) arbitrator_tests_link_deps = $(patsubst %,$(arbitrator_cases)/%.wasm, $(arbitrator_tests_link_info)) +arbitrator_tests_forward_wats = $(wildcard $(arbitrator_cases)/forward/*.wat) +arbitrator_tests_forward_deps = $(arbitrator_tests_forward_wats:wat=wasm) + WASI_SYSROOT?=/opt/wasi-sdk/wasi-sysroot arbitrator_wasm_lib_flags=$(patsubst %, -l %, $(arbitrator_wasm_libs)) rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/src/*/*.* arbitrator/arbutil/*.toml arbitrator/caller-env/src/*.* arbitrator/caller-env/src/*/*.* arbitrator/caller-env/*.toml) .make/cbrotli-lib +prover_direct_includes = $(patsubst %,$(output_latest)/%.wasm, forward forward_stub) prover_dir = arbitrator/prover/ rust_prover_files = $(wildcard $(prover_dir)/src/*.* $(prover_dir)/src/*/*.* $(prover_dir)/*.toml $(prover_dir)/*.rs) $(rust_arbutil_files) $(prover_direct_includes) $(arb_brotli_files) @@ -88,9 +92,12 @@ wasm_lib = arbitrator/wasm-libraries wasm_lib_cargo = $(wasm_lib)/.cargo/config.toml wasm_lib_deps = $(wildcard $(wasm_lib)/$(1)/*.toml $(wasm_lib)/$(1)/src/*.rs $(wasm_lib)/$(1)/*.rs) $(wasm_lib_cargo) $(rust_arbutil_files) $(arb_brotli_files) .make/machines wasm_lib_go_abi = $(call wasm_lib_deps,go-abi) +wasm_lib_forward = $(call wasm_lib_deps,forward) wasm_lib_user_host_trait = $(call wasm_lib_deps,user-host-trait) wasm_lib_user_host = $(call wasm_lib_deps,user-host) $(wasm_lib_user_host_trait) +forward_dir = $(wasm_lib)/forward + stylus_files = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(wasm_lib_user_host_trait) $(rust_prover_files) jit_dir = arbitrator/jit @@ -142,8 +149,10 @@ stylus_test_erc20_wasm = $(call get_stylus_test_wasm,erc20) stylus_test_erc20_src = $(call get_stylus_test_rust,erc20) stylus_test_read-return-data_wasm = $(call get_stylus_test_wasm,read-return-data) stylus_test_read-return-data_src = $(call get_stylus_test_rust,read-return-data) +stylus_test_hostio-test_wasm = $(call get_stylus_test_wasm,hostio-test) +stylus_test_hostio-test_src = $(call get_stylus_test_rust,hostio-test) -stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_math_wasm) $(stylus_test_sdk-storage_wasm) $(stylus_test_erc20_wasm) $(stylus_test_read-return-data_wasm) $(stylus_test_evm-data_wasm) $(stylus_test_bfs:.b=.wasm) +stylus_test_wasms = $(stylus_test_keccak_wasm) $(stylus_test_keccak-100_wasm) $(stylus_test_fallible_wasm) $(stylus_test_storage_wasm) $(stylus_test_multicall_wasm) $(stylus_test_log_wasm) $(stylus_test_create_wasm) $(stylus_test_math_wasm) $(stylus_test_sdk-storage_wasm) $(stylus_test_erc20_wasm) $(stylus_test_read-return-data_wasm) $(stylus_test_evm-data_wasm) $(stylus_test_hostio-test_wasm) $(stylus_test_bfs:.b=.wasm) stylus_benchmarks = $(wildcard $(stylus_dir)/*.toml $(stylus_dir)/src/*.rs) $(stylus_test_wasms) # user targets @@ -274,7 +283,9 @@ clean: rm -f arbitrator/wasm-libraries/soft-float/*.o rm -f arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/*.o rm -f arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/*.a + rm -f arbitrator/wasm-libraries/forward/*.wat rm -rf arbitrator/stylus/tests/*/target/ arbitrator/stylus/tests/*/*.wasm + rm -rf brotli/buildfiles @rm -rf contracts/build contracts/cache solgen/go/ @rm -f .make/* @@ -391,7 +402,7 @@ $(output_latest)/host_io.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,host-io) $( cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --config $(wasm_lib_cargo) --package host-io install arbitrator/wasm-libraries/$(wasm32_wasi)/host_io.wasm $@ -$(output_latest)/user_host.wasm: $(DEP_PREDICATE) $(wasm_lib_user_host) $(rust_prover_files) .make/machines +$(output_latest)/user_host.wasm: $(DEP_PREDICATE) $(wasm_lib_user_host) $(rust_prover_files) $(output_latest)/forward_stub.wasm .make/machines cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --config $(wasm_lib_cargo) --package user-host install arbitrator/wasm-libraries/$(wasm32_wasi)/user_host.wasm $@ @@ -407,9 +418,17 @@ $(output_latest)/arbcompress.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,brotli) cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --config $(wasm_lib_cargo) --package arbcompress install arbitrator/wasm-libraries/$(wasm32_wasi)/arbcompress.wasm $@ +$(output_latest)/forward.wasm: $(DEP_PREDICATE) $(wasm_lib_forward) .make/machines + cargo run --manifest-path $(forward_dir)/Cargo.toml -- --path $(forward_dir)/forward.wat + wat2wasm $(wasm_lib)/forward/forward.wat -o $@ + +$(output_latest)/forward_stub.wasm: $(DEP_PREDICATE) $(wasm_lib_forward) .make/machines + cargo run --manifest-path $(forward_dir)/Cargo.toml -- --path $(forward_dir)/forward_stub.wat --stub + wat2wasm $(wasm_lib)/forward/forward_stub.wat -o $@ + $(output_latest)/machine.wavm.br: $(DEP_PREDICATE) $(prover_bin) $(arbitrator_wasm_libs) $(replay_wasm) - $(prover_bin) $(replay_wasm) --generate-binaries $(output_latest) --with-forwarder \ - $(patsubst %,-l $(output_latest)/%.wasm, soft-float wasi_stub host_io user_host arbcompress program_exec) + $(prover_bin) $(replay_wasm) --generate-binaries $(output_latest) \ + $(patsubst %,-l $(output_latest)/%.wasm, forward soft-float wasi_stub host_io user_host arbcompress program_exec) $(arbitrator_cases)/%.wasm: $(arbitrator_cases)/%.wat wat2wasm $< -o $@ @@ -465,6 +484,10 @@ $(stylus_test_erc20_wasm): $(stylus_test_erc20_src) $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) @touch -c $@ # cargo might decide to not rebuild the binary +$(stylus_test_hostio-test_wasm): $(stylus_test_hostio-test_src) + $(cargo_nightly) --manifest-path $< --release --config $(stylus_cargo) + @touch -c $@ # cargo might decide to not rebuild the binary + contracts/test/prover/proofs/float%.json: $(arbitrator_cases)/float%.wasm $(prover_bin) $(output_latest)/soft-float.wasm $(prover_bin) $< -l $(output_latest)/soft-float.wasm -o $@ -b --allow-hostapi --require-success @@ -476,10 +499,10 @@ target/testdata/preimages.bin: python3 scripts/create-test-preimages.py $@ contracts/test/prover/proofs/rust-%.json: $(arbitrator_cases)/rust/$(wasm32_wasi)/%.wasm $(prover_bin) $(arbitrator_wasm_libs) target/testdata/preimages.bin - $(prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -b --allow-hostapi --require-success --inbox-add-stub-headers --inbox $(arbitrator_cases)/rust/data/msg0.bin --inbox $(arbitrator_cases)/rust/data/msg1.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg0.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg1.bin --preimages target/testdata/preimages.bin --with-forwarder + $(prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -b --allow-hostapi --require-success --inbox-add-stub-headers --inbox $(arbitrator_cases)/rust/data/msg0.bin --inbox $(arbitrator_cases)/rust/data/msg1.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg0.bin --delayed-inbox $(arbitrator_cases)/rust/data/msg1.bin --preimages target/testdata/preimages.bin contracts/test/prover/proofs/go.json: $(arbitrator_cases)/go/testcase.wasm $(prover_bin) $(arbitrator_wasm_libs) target/testdata/preimages.bin $(arbitrator_tests_link_deps) $(arbitrator_cases)/user.wasm - $(prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -b --require-success --preimages target/testdata/preimages.bin --stylus-modules $(arbitrator_cases)/user.wasm --with-forwarder + $(prover_bin) $< $(arbitrator_wasm_lib_flags) -o $@ -b --require-success --preimages target/testdata/preimages.bin --stylus-modules $(arbitrator_cases)/user.wasm # avoid testing user.wasm in onestepproofs. It can only run as stylus program. contracts/test/prover/proofs/user.json: @@ -492,6 +515,9 @@ contracts/test/prover/proofs/read-inboxmsg-10.json: contracts/test/prover/proofs/global-state.json: echo "[]" > $@ +contracts/test/prover/proofs/forward-test.json: $(arbitrator_cases)/forward-test.wasm $(arbitrator_tests_forward_deps) $(prover_bin) + $(prover_bin) $< -o $@ --allow-hostapi $(patsubst %,-l %, $(arbitrator_tests_forward_deps)) + contracts/test/prover/proofs/link.json: $(arbitrator_cases)/link.wasm $(arbitrator_tests_link_deps) $(prover_bin) $(prover_bin) $< -o $@ --allow-hostapi --stylus-modules $(arbitrator_tests_link_deps) --require-success diff --git a/arbcompress/native.go b/arbcompress/native.go index 8244010979..f7b8f0b8e0 100644 --- a/arbcompress/native.go +++ b/arbcompress/native.go @@ -7,7 +7,7 @@ package arbcompress /* -#cgo CFLAGS: -g -Wall -I${SRCDIR}/../target/include/ +#cgo CFLAGS: -g -I${SRCDIR}/../target/include/ #cgo LDFLAGS: ${SRCDIR}/../target/lib/libstylus.a -lm #include "arbitrator.h" */ diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index 79a9117a31..2b437968fa 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -215,7 +215,6 @@ dependencies = [ "prover", "serde", "serde_json", - "serde_with 3.9.0", ] [[package]] @@ -496,6 +495,12 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "clru" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" + [[package]] name = "colorchoice" version = "1.0.2" @@ -705,38 +710,14 @@ dependencies = [ "typenum", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -753,24 +734,13 @@ dependencies = [ "syn 2.0.72", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.10", + "darling_core", "quote", "syn 2.0.72", ] @@ -928,7 +898,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" dependencies = [ - "darling 0.20.10", + "darling", "proc-macro2", "quote", "syn 2.0.72", @@ -1750,7 +1720,7 @@ dependencies = [ "rustc-demangle", "serde", "serde_json", - "serde_with 1.14.0", + "serde_with", "sha2 0.9.9", "sha3 0.9.1", "smallvec", @@ -2073,16 +2043,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros 1.5.2", -] - [[package]] name = "serde_with" version = "3.9.0" @@ -2097,29 +2057,17 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with_macros 3.9.0", + "serde_with_macros", "time", ] -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling 0.13.4", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "serde_with_macros" version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ - "darling 0.20.10", + "darling", "proc-macro2", "quote", "syn 2.0.72", @@ -2226,12 +2174,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -2270,13 +2212,13 @@ dependencies = [ "bincode", "brotli", "caller-env", + "clru", "derivative", "eyre", "fnv", "hex", "lazy_static", "libc", - "lru", "num-bigint", "parking_lot", "prover", diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index 94ca08b0b5..eaafb6e439 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -24,9 +24,7 @@ repository = "https://github.com/OffchainLabs/nitro.git" rust-version = "1.67" [workspace.dependencies] -cfg-if = "1.0.0" lazy_static = "1.4.0" -lru = "0.12.3" num_enum = { version = "0.7.2", default-features = false } ruint2 = "1.9.0" wasmparser = "0.121" diff --git a/arbitrator/arbutil/src/evm/api.rs b/arbitrator/arbutil/src/evm/api.rs index 093e7f2984..0a603a3bb2 100644 --- a/arbitrator/arbutil/src/evm/api.rs +++ b/arbitrator/arbutil/src/evm/api.rs @@ -73,19 +73,103 @@ impl DataReader for VecReader { } } +macro_rules! derive_math { + ($t:ident) => { + impl std::ops::Add for $t { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } + } + + impl std::ops::AddAssign for $t { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } + } + + impl std::ops::Sub for $t { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } + } + + impl std::ops::SubAssign for $t { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } + } + + impl std::ops::Mul for $t { + type Output = Self; + + fn mul(self, rhs: u64) -> Self { + Self(self.0 * rhs) + } + } + + impl std::ops::Mul<$t> for u64 { + type Output = $t; + + fn mul(self, rhs: $t) -> $t { + $t(self * rhs.0) + } + } + + impl $t { + /// Equivalent to the Add trait, but const. + pub const fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } + + /// Equivalent to the Sub trait, but const. + pub const fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } + + pub const fn saturating_add(self, rhs: Self) -> Self { + Self(self.0.saturating_add(rhs.0)) + } + + pub const fn saturating_sub(self, rhs: Self) -> Self { + Self(self.0.saturating_sub(rhs.0)) + } + + pub fn to_be_bytes(self) -> [u8; 8] { + self.0.to_be_bytes() + } + } + }; +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +#[must_use] +pub struct Gas(pub u64); + +derive_math!(Gas); + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +#[must_use] +pub struct Ink(pub u64); + +derive_math!(Ink); + pub trait EvmApi: Send + 'static { /// Reads the 32-byte value in the EVM state trie at offset `key`. /// Returns the value and the access cost in gas. /// Analogous to `vm.SLOAD`. - fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64); + fn get_bytes32(&mut self, key: Bytes32, evm_api_gas_to_use: Gas) -> (Bytes32, Gas); /// Stores the given value at the given key in Stylus VM's cache of the EVM state trie. /// Note that the actual values only get written after calls to `set_trie_slots`. - fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64; + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas; /// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested. /// Analogous to repeated invocations of `vm.SSTORE`. - fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result; + fn flush_storage_cache(&mut self, clear: bool, gas_left: Gas) -> Result; /// Reads the 32-byte value in the EVM's transient state trie at offset `key`. /// Analogous to `vm.TLOAD`. @@ -102,10 +186,10 @@ pub trait EvmApi: Send + 'static { &mut self, contract: Bytes20, calldata: &[u8], - gas_left: u64, - gas_req: u64, + gas_left: Gas, + gas_req: Gas, value: Bytes32, - ) -> (u32, u64, UserOutcomeKind); + ) -> (u32, Gas, UserOutcomeKind); /// Delegate-calls the contract at the given address. /// Returns the EVM return data's length, the gas cost, and whether the call succeeded. @@ -114,9 +198,9 @@ pub trait EvmApi: Send + 'static { &mut self, contract: Bytes20, calldata: &[u8], - gas_left: u64, - gas_req: u64, - ) -> (u32, u64, UserOutcomeKind); + gas_left: Gas, + gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind); /// Static-calls the contract at the given address. /// Returns the EVM return data's length, the gas cost, and whether the call succeeded. @@ -125,9 +209,9 @@ pub trait EvmApi: Send + 'static { &mut self, contract: Bytes20, calldata: &[u8], - gas_left: u64, - gas_req: u64, - ) -> (u32, u64, UserOutcomeKind); + gas_left: Gas, + gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind); /// Deploys a new contract using the init code provided. /// Returns the new contract's address on success, or the error reason on failure. @@ -137,8 +221,8 @@ pub trait EvmApi: Send + 'static { &mut self, code: Vec, endowment: Bytes32, - gas: u64, - ) -> (eyre::Result, u32, u64); + gas: Gas, + ) -> (eyre::Result, u32, Gas); /// Deploys a new contract using the init code provided, with an address determined in part by the `salt`. /// Returns the new contract's address on success, or the error reason on failure. @@ -149,8 +233,8 @@ pub trait EvmApi: Send + 'static { code: Vec, endowment: Bytes32, salt: Bytes32, - gas: u64, - ) -> (eyre::Result, u32, u64); + gas: Gas, + ) -> (eyre::Result, u32, Gas); /// Returns the EVM return data. /// Analogous to `vm.RETURNDATACOPY`. @@ -164,21 +248,21 @@ pub trait EvmApi: Send + 'static { /// Gets the balance of the given account. /// Returns the balance and the access cost in gas. /// Analogous to `vm.BALANCE`. - fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64); + fn account_balance(&mut self, address: Bytes20) -> (Bytes32, Gas); /// Returns the code and the access cost in gas. /// Analogous to `vm.EXTCODECOPY`. - fn account_code(&mut self, address: Bytes20, gas_left: u64) -> (D, u64); + fn account_code(&mut self, address: Bytes20, gas_left: Gas) -> (D, Gas); /// Gets the hash of the given address's code. /// Returns the hash and the access cost in gas. /// Analogous to `vm.EXTCODEHASH`. - fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64); + fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, Gas); /// Determines the cost in gas of allocating additional wasm pages. /// Note: has the side effect of updating Geth's memory usage tracker. /// Not analogous to any EVM opcode. - fn add_pages(&mut self, pages: u16) -> u64; + fn add_pages(&mut self, pages: u16) -> Gas; /// Captures tracing information for hostio invocations during native execution. fn capture_hostio( @@ -186,7 +270,7 @@ pub trait EvmApi: Send + 'static { name: &str, args: &[u8], outs: &[u8], - start_ink: u64, - end_ink: u64, + start_ink: Ink, + end_ink: Ink, ); } diff --git a/arbitrator/arbutil/src/evm/mod.rs b/arbitrator/arbutil/src/evm/mod.rs index 1671e67072..063194b0c6 100644 --- a/arbitrator/arbutil/src/evm/mod.rs +++ b/arbitrator/arbutil/src/evm/mod.rs @@ -2,6 +2,7 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{Bytes20, Bytes32}; +use api::Gas; pub mod api; pub mod req; @@ -9,74 +10,77 @@ pub mod storage; pub mod user; // params.SstoreSentryGasEIP2200 -pub const SSTORE_SENTRY_GAS: u64 = 2300; +pub const SSTORE_SENTRY_GAS: Gas = Gas(2300); // params.ColdAccountAccessCostEIP2929 -pub const COLD_ACCOUNT_GAS: u64 = 2600; +pub const COLD_ACCOUNT_GAS: Gas = Gas(2600); // params.ColdSloadCostEIP2929 -pub const COLD_SLOAD_GAS: u64 = 2100; +pub const COLD_SLOAD_GAS: Gas = Gas(2100); // params.WarmStorageReadCostEIP2929 -pub const WARM_SLOAD_GAS: u64 = 100; +pub const WARM_SLOAD_GAS: Gas = Gas(100); // params.WarmStorageReadCostEIP2929 (see enable1153 in jump_table.go) -pub const TLOAD_GAS: u64 = WARM_SLOAD_GAS; -pub const TSTORE_GAS: u64 = WARM_SLOAD_GAS; +pub const TLOAD_GAS: Gas = WARM_SLOAD_GAS; +pub const TSTORE_GAS: Gas = WARM_SLOAD_GAS; // params.LogGas and params.LogDataGas -pub const LOG_TOPIC_GAS: u64 = 375; -pub const LOG_DATA_GAS: u64 = 8; +pub const LOG_TOPIC_GAS: Gas = Gas(375); +pub const LOG_DATA_GAS: Gas = Gas(8); // params.CopyGas -pub const COPY_WORD_GAS: u64 = 3; +pub const COPY_WORD_GAS: Gas = Gas(3); // params.Keccak256Gas -pub const KECCAK_256_GAS: u64 = 30; -pub const KECCAK_WORD_GAS: u64 = 6; +pub const KECCAK_256_GAS: Gas = Gas(30); +pub const KECCAK_WORD_GAS: Gas = Gas(6); // vm.GasQuickStep (see gas.go) -pub const GAS_QUICK_STEP: u64 = 2; +pub const GAS_QUICK_STEP: Gas = Gas(2); // vm.GasQuickStep (see jump_table.go) -pub const ADDRESS_GAS: u64 = GAS_QUICK_STEP; +pub const ADDRESS_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see eips.go) -pub const BASEFEE_GAS: u64 = GAS_QUICK_STEP; +pub const BASEFEE_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see eips.go) -pub const CHAINID_GAS: u64 = GAS_QUICK_STEP; +pub const CHAINID_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const COINBASE_GAS: u64 = GAS_QUICK_STEP; +pub const COINBASE_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const GASLIMIT_GAS: u64 = GAS_QUICK_STEP; +pub const GASLIMIT_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const NUMBER_GAS: u64 = GAS_QUICK_STEP; +pub const NUMBER_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const TIMESTAMP_GAS: u64 = GAS_QUICK_STEP; +pub const TIMESTAMP_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const GASLEFT_GAS: u64 = GAS_QUICK_STEP; +pub const GASLEFT_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const CALLER_GAS: u64 = GAS_QUICK_STEP; +pub const CALLER_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const CALLVALUE_GAS: u64 = GAS_QUICK_STEP; +pub const CALLVALUE_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const GASPRICE_GAS: u64 = GAS_QUICK_STEP; +pub const GASPRICE_GAS: Gas = GAS_QUICK_STEP; // vm.GasQuickStep (see jump_table.go) -pub const ORIGIN_GAS: u64 = GAS_QUICK_STEP; +pub const ORIGIN_GAS: Gas = GAS_QUICK_STEP; + +pub const ARBOS_VERSION_STYLUS_CHARGING_FIXES: u64 = 32; #[derive(Clone, Copy, Debug, Default)] #[repr(C)] pub struct EvmData { + pub arbos_version: u64, pub block_basefee: Bytes32, pub chainid: u64, pub block_coinbase: Bytes20, diff --git a/arbitrator/arbutil/src/evm/req.rs b/arbitrator/arbutil/src/evm/req.rs index 287db357f3..621f41e951 100644 --- a/arbitrator/arbutil/src/evm/req.rs +++ b/arbitrator/arbutil/src/evm/req.rs @@ -7,14 +7,15 @@ use crate::{ storage::{StorageCache, StorageWord}, user::UserOutcomeKind, }, - pricing::EVM_API_INK, Bytes20, Bytes32, }; use eyre::{bail, eyre, Result}; use std::collections::hash_map::Entry; +use super::api::{Gas, Ink}; + pub trait RequestHandler: Send + 'static { - fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, u64); + fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, Gas); } pub struct EvmApiRequestor> { @@ -34,7 +35,7 @@ impl> EvmApiRequestor { } } - fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, u64) { + fn request(&mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>) -> (Vec, D, Gas) { self.handler.request(req_type, req_data) } @@ -44,10 +45,10 @@ impl> EvmApiRequestor { call_type: EvmApiMethod, contract: Bytes20, input: &[u8], - gas_left: u64, - gas_req: u64, + gas_left: Gas, + gas_req: Gas, value: Bytes32, - ) -> (u32, u64, UserOutcomeKind) { + ) -> (u32, Gas, UserOutcomeKind) { let mut request = Vec::with_capacity(20 + 32 + 8 + 8 + input.len()); request.extend(contract); request.extend(value); @@ -72,8 +73,8 @@ impl> EvmApiRequestor { code: Vec, endowment: Bytes32, salt: Option, - gas: u64, - ) -> (Result, u32, u64) { + gas: Gas, + ) -> (Result, u32, Gas) { let mut request = Vec::with_capacity(8 + 2 * 32 + code.len()); request.extend(gas.to_be_bytes()); request.extend(endowment); @@ -99,19 +100,19 @@ impl> EvmApiRequestor { } impl> EvmApi for EvmApiRequestor { - fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + fn get_bytes32(&mut self, key: Bytes32, evm_api_gas_to_use: Gas) -> (Bytes32, Gas) { let cache = &mut self.storage_cache; let mut cost = cache.read_gas(); let value = cache.entry(key).or_insert_with(|| { let (res, _, gas) = self.handler.request(EvmApiMethod::GetBytes32, key); - cost = cost.saturating_add(gas).saturating_add(EVM_API_INK); + cost = cost.saturating_add(gas).saturating_add(evm_api_gas_to_use); StorageWord::known(res.try_into().unwrap()) }); (value.value, cost) } - fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas { let cost = self.storage_cache.write_gas(); match self.storage_cache.entry(key) { Entry::Occupied(mut key) => key.get_mut().value = value, @@ -120,7 +121,7 @@ impl> EvmApi for EvmApiRequestor { cost } - fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result { + fn flush_storage_cache(&mut self, clear: bool, gas_left: Gas) -> Result { let mut data = Vec::with_capacity(64 * self.storage_cache.len() + 8); data.extend(gas_left.to_be_bytes()); @@ -135,7 +136,7 @@ impl> EvmApi for EvmApiRequestor { self.storage_cache.clear(); } if data.len() == 8 { - return Ok(0); // no need to make request + return Ok(Gas(0)); // no need to make request } let (res, _, cost) = self.request(EvmApiMethod::SetTrieSlots, data); @@ -175,10 +176,10 @@ impl> EvmApi for EvmApiRequestor { &mut self, contract: Bytes20, input: &[u8], - gas_left: u64, - gas_req: u64, + gas_left: Gas, + gas_req: Gas, value: Bytes32, - ) -> (u32, u64, UserOutcomeKind) { + ) -> (u32, Gas, UserOutcomeKind) { self.call_request( EvmApiMethod::ContractCall, contract, @@ -193,9 +194,9 @@ impl> EvmApi for EvmApiRequestor { &mut self, contract: Bytes20, input: &[u8], - gas_left: u64, - gas_req: u64, - ) -> (u32, u64, UserOutcomeKind) { + gas_left: Gas, + gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind) { self.call_request( EvmApiMethod::DelegateCall, contract, @@ -210,9 +211,9 @@ impl> EvmApi for EvmApiRequestor { &mut self, contract: Bytes20, input: &[u8], - gas_left: u64, - gas_req: u64, - ) -> (u32, u64, UserOutcomeKind) { + gas_left: Gas, + gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind) { self.call_request( EvmApiMethod::StaticCall, contract, @@ -227,8 +228,8 @@ impl> EvmApi for EvmApiRequestor { &mut self, code: Vec, endowment: Bytes32, - gas: u64, - ) -> (Result, u32, u64) { + gas: Gas, + ) -> (Result, u32, Gas) { self.create_request(EvmApiMethod::Create1, code, endowment, None, gas) } @@ -237,8 +238,8 @@ impl> EvmApi for EvmApiRequestor { code: Vec, endowment: Bytes32, salt: Bytes32, - gas: u64, - ) -> (Result, u32, u64) { + gas: Gas, + ) -> (Result, u32, Gas) { self.create_request(EvmApiMethod::Create2, code, endowment, Some(salt), gas) } @@ -259,15 +260,15 @@ impl> EvmApi for EvmApiRequestor { Ok(()) } - fn account_balance(&mut self, address: Bytes20) -> (Bytes32, u64) { + fn account_balance(&mut self, address: Bytes20) -> (Bytes32, Gas) { let (res, _, cost) = self.request(EvmApiMethod::AccountBalance, address); (res.try_into().unwrap(), cost) } - fn account_code(&mut self, address: Bytes20, gas_left: u64) -> (D, u64) { + fn account_code(&mut self, address: Bytes20, gas_left: Gas) -> (D, Gas) { if let Some((stored_address, data)) = self.last_code.as_ref() { if address == *stored_address { - return (data.clone(), 0); + return (data.clone(), Gas(0)); } } let mut req = Vec::with_capacity(20 + 8); @@ -279,12 +280,12 @@ impl> EvmApi for EvmApiRequestor { (data, cost) } - fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, u64) { + fn account_codehash(&mut self, address: Bytes20) -> (Bytes32, Gas) { let (res, _, cost) = self.request(EvmApiMethod::AccountCodeHash, address); (res.try_into().unwrap(), cost) } - fn add_pages(&mut self, pages: u16) -> u64 { + fn add_pages(&mut self, pages: u16) -> Gas { self.request(EvmApiMethod::AddPages, pages.to_be_bytes()).2 } @@ -293,18 +294,20 @@ impl> EvmApi for EvmApiRequestor { name: &str, args: &[u8], outs: &[u8], - start_ink: u64, - end_ink: u64, + start_ink: Ink, + end_ink: Ink, ) { let mut request = Vec::with_capacity(2 * 8 + 3 * 2 + name.len() + args.len() + outs.len()); request.extend(start_ink.to_be_bytes()); request.extend(end_ink.to_be_bytes()); - request.extend((name.len() as u16).to_be_bytes()); - request.extend((args.len() as u16).to_be_bytes()); - request.extend((outs.len() as u16).to_be_bytes()); + // u32 is enough to represent the slices lengths because the WASM environment runs in 32 bits. + request.extend((name.len() as u32).to_be_bytes()); + request.extend((args.len() as u32).to_be_bytes()); + request.extend((outs.len() as u32).to_be_bytes()); request.extend(name.as_bytes()); request.extend(args); request.extend(outs); - self.request(EvmApiMethod::CaptureHostIO, request); + // ignore response (including gas) as we're just tracing + _ = self.request(EvmApiMethod::CaptureHostIO, request); } } diff --git a/arbitrator/arbutil/src/evm/storage.rs b/arbitrator/arbutil/src/evm/storage.rs index 32b60dd21b..5f688364d7 100644 --- a/arbitrator/arbutil/src/evm/storage.rs +++ b/arbitrator/arbutil/src/evm/storage.rs @@ -5,6 +5,8 @@ use crate::Bytes32; use fnv::FnvHashMap as HashMap; use std::ops::{Deref, DerefMut}; +use super::api::Gas; + /// Represents the EVM word at a given key. #[derive(Debug)] pub struct StorageWord { @@ -37,23 +39,23 @@ pub struct StorageCache { } impl StorageCache { - pub const REQUIRED_ACCESS_GAS: u64 = 10; + pub const REQUIRED_ACCESS_GAS: Gas = Gas(10); - pub fn read_gas(&mut self) -> u64 { + pub fn read_gas(&mut self) -> Gas { self.reads += 1; match self.reads { - 0..=32 => 0, - 33..=128 => 2, - _ => 10, + 0..=32 => Gas(0), + 33..=128 => Gas(2), + _ => Gas(10), } } - pub fn write_gas(&mut self) -> u64 { + pub fn write_gas(&mut self) -> Gas { self.writes += 1; match self.writes { - 0..=8 => 0, - 9..=64 => 7, - _ => 10, + 0..=8 => Gas(0), + 9..=64 => Gas(7), + _ => Gas(10), } } } diff --git a/arbitrator/arbutil/src/hostios.rs b/arbitrator/arbutil/src/hostios.rs deleted file mode 100644 index 05bcb94f73..0000000000 --- a/arbitrator/arbutil/src/hostios.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::fmt::Display; - -pub enum ParamType { - I32, - I64, -} - -impl Display for ParamType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use ParamType::*; - match self { - I32 => write!(f, "i32"), - I64 => write!(f, "i64"), - } - } -} - -use ParamType::*; - -/// order matters! -pub const HOSTIOS: [(&str, &[ParamType], &[ParamType]); 42] = [ - ("read_args", &[I32], &[]), - ("write_result", &[I32, I32], &[]), - ("exit_early", &[I32], &[]), - ("storage_load_bytes32", &[I32, I32], &[]), - ("storage_cache_bytes32", &[I32, I32], &[]), - ("storage_flush_cache", &[I32], &[]), - ("transient_load_bytes32", &[I32, I32], &[]), - ("transient_store_bytes32", &[I32, I32], &[]), - ("call_contract", &[I32, I32, I32, I32, I64, I32], &[I32]), - ("delegate_call_contract", &[I32, I32, I32, I64, I32], &[I32]), - ("static_call_contract", &[I32, I32, I32, I64, I32], &[I32]), - ("create1", &[I32, I32, I32, I32, I32], &[]), - ("create2", &[I32, I32, I32, I32, I32, I32], &[]), - ("read_return_data", &[I32, I32, I32], &[I32]), - ("return_data_size", &[], &[I32]), - ("emit_log", &[I32, I32, I32], &[]), - ("account_balance", &[I32, I32], &[]), - ("account_code", &[I32, I32, I32, I32], &[I32]), - ("account_code_size", &[I32], &[I32]), - ("account_codehash", &[I32, I32], &[]), - ("evm_gas_left", &[], &[I64]), - ("evm_ink_left", &[], &[I64]), - ("block_basefee", &[I32], &[]), - ("chainid", &[], &[I64]), - ("block_coinbase", &[I32], &[]), - ("block_gas_limit", &[], &[I64]), - ("block_number", &[], &[I64]), - ("block_timestamp", &[], &[I64]), - ("contract_address", &[I32], &[]), - ("math_div", &[I32, I32], &[]), - ("math_mod", &[I32, I32], &[]), - ("math_pow", &[I32, I32], &[]), - ("math_add_mod", &[I32, I32, I32], &[]), - ("math_mul_mod", &[I32, I32, I32], &[]), - ("msg_reentrant", &[], &[I32]), - ("msg_sender", &[I32], &[]), - ("msg_value", &[I32], &[]), - ("native_keccak256", &[I32, I32, I32], &[]), - ("tx_gas_price", &[I32], &[]), - ("tx_ink_price", &[], &[I32]), - ("tx_origin", &[I32], &[]), - ("pay_for_memory_grow", &[I32], &[]), -]; diff --git a/arbitrator/arbutil/src/lib.rs b/arbitrator/arbutil/src/lib.rs index 8d8c1d0fca..9c48a9fefc 100644 --- a/arbitrator/arbutil/src/lib.rs +++ b/arbitrator/arbutil/src/lib.rs @@ -6,7 +6,6 @@ pub mod color; pub mod crypto; pub mod evm; pub mod format; -pub mod hostios; pub mod math; pub mod operator; pub mod pricing; diff --git a/arbitrator/arbutil/src/pricing.rs b/arbitrator/arbutil/src/pricing.rs index 4614b02a2a..4d6bf827be 100644 --- a/arbitrator/arbutil/src/pricing.rs +++ b/arbitrator/arbutil/src/pricing.rs @@ -1,20 +1,22 @@ // Copyright 2023, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +use crate::evm::api::Ink; + /// For hostios that may return something. -pub const HOSTIO_INK: u64 = 8400; +pub const HOSTIO_INK: Ink = Ink(8400); /// For hostios that include pointers. -pub const PTR_INK: u64 = 13440 - HOSTIO_INK; +pub const PTR_INK: Ink = Ink(13440).sub(HOSTIO_INK); /// For hostios that involve an API cost. -pub const EVM_API_INK: u64 = 59673; +pub const EVM_API_INK: Ink = Ink(59673); /// For hostios that involve a div or mod. -pub const DIV_INK: u64 = 20000; +pub const DIV_INK: Ink = Ink(20000); /// For hostios that involve a mulmod. -pub const MUL_MOD_INK: u64 = 24100; +pub const MUL_MOD_INK: Ink = Ink(24100); /// For hostios that involve an addmod. -pub const ADD_MOD_INK: u64 = 21000; +pub const ADD_MOD_INK: Ink = Ink(21000); diff --git a/arbitrator/arbutil/src/types.rs b/arbitrator/arbutil/src/types.rs index 6cf1d6cdf7..722a89b81e 100644 --- a/arbitrator/arbutil/src/types.rs +++ b/arbitrator/arbutil/src/types.rs @@ -8,6 +8,7 @@ use std::{ borrow::Borrow, fmt, ops::{Deref, DerefMut}, + str::FromStr, }; // These values must be kept in sync with `arbutil/preimage_type.go`, @@ -83,6 +84,32 @@ impl From for Bytes32 { } } +impl FromStr for Bytes32 { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + // Remove the "0x" prefix if present + let s = s.strip_prefix("0x").unwrap_or(s); + + // Pad with leading zeros if the string is shorter than 64 characters (32 bytes) + let padded = format!("{:0>64}", s); + + // Decode the hex string using the hex crate + let decoded_bytes = hex::decode(padded).map_err(|_| "Invalid hex string")?; + + // Ensure the decoded bytes is exactly 32 bytes + if decoded_bytes.len() != 32 { + return Err("Hex string too long for Bytes32"); + } + + // Create a 32-byte array and fill it with the decoded bytes. + let mut b = [0u8; 32]; + b.copy_from_slice(&decoded_bytes); + + Ok(Bytes32(b)) + } +} + impl TryFrom<&[u8]> for Bytes32 { type Error = std::array::TryFromSliceError; @@ -249,3 +276,77 @@ impl From for Bytes20 { <[u8; 20]>::from(x).into() } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_bytes32() { + let b = Bytes32::from(0x12345678u32); + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x12, 0x34, 0x56, 0x78, + ]; + assert_eq!(b, Bytes32(expected)); + } + + #[test] + fn test_from_str_short() { + // Short hex string + let b = Bytes32::from_str("0x12345678").unwrap(); + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x12, 0x34, 0x56, 0x78, + ]; + assert_eq!(b, Bytes32(expected)); + } + + #[test] + fn test_from_str_very_short() { + // Short hex string + let b = Bytes32::from_str("0x1").unwrap(); + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0x1, + ]; + assert_eq!(b, Bytes32(expected)); + } + + #[test] + fn test_from_str_no_prefix() { + // Short hex string + let b = Bytes32::from_str("12345678").unwrap(); + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x12, 0x34, 0x56, 0x78, + ]; + assert_eq!(b, Bytes32(expected)); + } + + #[test] + fn test_from_str_full() { + // Full-length hex string + let b = + Bytes32::from_str("0x0000000000000000000000000000000000000000000000000000000012345678") + .unwrap(); + let expected = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x12, 0x34, 0x56, 0x78, + ]; + assert_eq!(b, Bytes32(expected)); + } + + #[test] + fn test_from_str_invalid_non_hex() { + let s = "0x123g5678"; // Invalid character 'g' + assert!(Bytes32::from_str(s).is_err()); + } + + #[test] + fn test_from_str_too_big() { + let s = + "0123456789ABCDEF0123456789ABCDEF01234567890123456789ABCDEF01234567890123456789ABCDEF0"; // 65 characters + assert!(Bytes32::from_str(s).is_err()); + } +} diff --git a/arbitrator/bench/Cargo.toml b/arbitrator/bench/Cargo.toml index 3ab5b99b08..74b948aca8 100644 --- a/arbitrator/bench/Cargo.toml +++ b/arbitrator/bench/Cargo.toml @@ -3,10 +3,6 @@ name = "bench" version = "0.1.0" edition = "2021" -[lib] -name = "bench" -path = "src/lib.rs" - [[bin]] name = "benchbin" path = "src/bin.rs" @@ -20,7 +16,6 @@ clap = { version = "4.4.8", features = ["derive"] } gperftools = { version = "0.2.0", optional = true } serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = "1.0.67" -serde_with = { version = "3.8.1", features = ["base64"] } [features] counters = [] diff --git a/arbitrator/bench/src/bin.rs b/arbitrator/bench/src/bin.rs index f7e69f5373..60a7036e2b 100644 --- a/arbitrator/bench/src/bin.rs +++ b/arbitrator/bench/src/bin.rs @@ -1,6 +1,5 @@ use std::{path::PathBuf, time::Duration}; -use bench::prepare::*; use clap::Parser; use eyre::bail; @@ -10,11 +9,12 @@ use gperftools::profiler::PROFILER; #[cfg(feature = "heapprof")] use gperftools::heap_profiler::HEAP_PROFILER; -use prover::machine::MachineStatus; - #[cfg(feature = "counters")] use prover::{machine, memory, merkle}; +use prover::machine::MachineStatus; +use prover::prepare::prepare_machine; + #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { diff --git a/arbitrator/bench/src/lib.rs b/arbitrator/bench/src/lib.rs deleted file mode 100644 index 5f7c024094..0000000000 --- a/arbitrator/bench/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod parse_input; -pub mod prepare; diff --git a/arbitrator/bench/src/parse_input.rs b/arbitrator/bench/src/parse_input.rs deleted file mode 100644 index decc67372a..0000000000 --- a/arbitrator/bench/src/parse_input.rs +++ /dev/null @@ -1,76 +0,0 @@ -use arbutil::Bytes32; -use serde::{Deserialize, Serialize}; -use serde_json; -use serde_with::base64::Base64; -use serde_with::As; -use serde_with::DisplayFromStr; -use std::{ - collections::HashMap, - io::{self, BufRead}, -}; - -mod prefixed_hex { - use serde::{self, Deserialize, Deserializer, Serializer}; - - pub fn serialize(bytes: &Vec, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("0x{}", hex::encode(bytes))) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - if let Some(s) = s.strip_prefix("0x") { - hex::decode(s).map_err(serde::de::Error::custom) - } else { - Err(serde::de::Error::custom("missing 0x prefix")) - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct PreimageMap(HashMap>); - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(rename_all = "PascalCase")] -pub struct BatchInfo { - pub number: u64, - #[serde(with = "As::")] - pub data_b64: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "PascalCase")] -pub struct StartState { - #[serde(with = "prefixed_hex")] - pub block_hash: Vec, - #[serde(with = "prefixed_hex")] - pub send_root: Vec, - pub batch: u64, - pub pos_in_batch: u64, -} - -#[derive(Debug, Deserialize, Serialize)] -#[serde(rename_all = "PascalCase")] -pub struct FileData { - pub id: u64, - pub has_delayed_msg: bool, - pub delayed_msg_nr: u64, - #[serde(with = "As::>>")] - pub preimages_b64: HashMap>>, - pub batch_info: Vec, - #[serde(with = "As::")] - pub delayed_msg_b64: Vec, - pub start_state: StartState, -} - -impl FileData { - pub fn from_reader(mut reader: R) -> io::Result { - let data = serde_json::from_reader(&mut reader)?; - Ok(data) - } -} diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index 2a3c5c5616..0d74c74ef6 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -2,8 +2,8 @@ // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::{ - arbcompress, caller_env::GoRuntimeState, program, socket, stylus_backend::CothreadHandler, - wasip1_stub, wavmio, Opts, + arbcompress, caller_env::GoRuntimeState, prepare::prepare_env, program, socket, + stylus_backend::CothreadHandler, wasip1_stub, wavmio, Opts, }; use arbutil::{Bytes32, Color, PreimageType}; use eyre::{bail, ErrReport, Result, WrapErr}; @@ -129,7 +129,9 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto "send_response" => func!(program::send_response), "create_stylus_config" => func!(program::create_stylus_config), "create_evm_data" => func!(program::create_evm_data), + "create_evm_data_v2" => func!(program::create_evm_data_v2), "activate" => func!(program::activate), + "activate_v2" => func!(program::activate_v2), }, }; @@ -213,72 +215,76 @@ pub struct WasmEnv { impl WasmEnv { pub fn cli(opts: &Opts) -> Result { - let mut env = WasmEnv::default(); - env.process.forks = opts.forks; - env.process.debug = opts.debug; + if let Some(json_inputs) = opts.json_inputs.clone() { + prepare_env(json_inputs, opts.debug) + } else { + let mut env = WasmEnv::default(); + env.process.forks = opts.forks; + env.process.debug = opts.debug; - let mut inbox_position = opts.inbox_position; - let mut delayed_position = opts.delayed_inbox_position; + let mut inbox_position = opts.inbox_position; + let mut delayed_position = opts.delayed_inbox_position; - for path in &opts.inbox { - let mut msg = vec![]; - File::open(path)?.read_to_end(&mut msg)?; - env.sequencer_messages.insert(inbox_position, msg); - inbox_position += 1; - } - for path in &opts.delayed_inbox { - let mut msg = vec![]; - File::open(path)?.read_to_end(&mut msg)?; - env.delayed_messages.insert(delayed_position, msg); - delayed_position += 1; - } + for path in &opts.inbox { + let mut msg = vec![]; + File::open(path)?.read_to_end(&mut msg)?; + env.sequencer_messages.insert(inbox_position, msg); + inbox_position += 1; + } + for path in &opts.delayed_inbox { + let mut msg = vec![]; + File::open(path)?.read_to_end(&mut msg)?; + env.delayed_messages.insert(delayed_position, msg); + delayed_position += 1; + } - if let Some(path) = &opts.preimages { - let mut file = BufReader::new(File::open(path)?); - let mut preimages = Vec::new(); - let filename = path.to_string_lossy(); - loop { - let mut size_buf = [0u8; 8]; - match file.read_exact(&mut size_buf) { - Ok(()) => {} - Err(err) if err.kind() == ErrorKind::UnexpectedEof => break, - Err(err) => bail!("Failed to parse {filename}: {}", err), + if let Some(path) = &opts.preimages { + let mut file = BufReader::new(File::open(path)?); + let mut preimages = Vec::new(); + let filename = path.to_string_lossy(); + loop { + let mut size_buf = [0u8; 8]; + match file.read_exact(&mut size_buf) { + Ok(()) => {} + Err(err) if err.kind() == ErrorKind::UnexpectedEof => break, + Err(err) => bail!("Failed to parse {filename}: {}", err), + } + let size = u64::from_le_bytes(size_buf) as usize; + let mut buf = vec![0u8; size]; + file.read_exact(&mut buf)?; + preimages.push(buf); + } + let keccak_preimages = env.preimages.entry(PreimageType::Keccak256).or_default(); + for preimage in preimages { + let mut hasher = Keccak256::new(); + hasher.update(&preimage); + let hash = hasher.finalize().into(); + keccak_preimages.insert(hash, preimage); } - let size = u64::from_le_bytes(size_buf) as usize; - let mut buf = vec![0u8; size]; - file.read_exact(&mut buf)?; - preimages.push(buf); - } - let keccak_preimages = env.preimages.entry(PreimageType::Keccak256).or_default(); - for preimage in preimages { - let mut hasher = Keccak256::new(); - hasher.update(&preimage); - let hash = hasher.finalize().into(); - keccak_preimages.insert(hash, preimage); } - } - fn parse_hex(arg: &Option, name: &str) -> Result { - match arg { - Some(arg) => { - let mut arg = arg.as_str(); - if arg.starts_with("0x") { - arg = &arg[2..]; + fn parse_hex(arg: &Option, name: &str) -> Result { + match arg { + Some(arg) => { + let mut arg = arg.as_str(); + if arg.starts_with("0x") { + arg = &arg[2..]; + } + let mut bytes32 = [0u8; 32]; + hex::decode_to_slice(arg, &mut bytes32) + .wrap_err_with(|| format!("failed to parse {} contents", name))?; + Ok(bytes32.into()) } - let mut bytes32 = [0u8; 32]; - hex::decode_to_slice(arg, &mut bytes32) - .wrap_err_with(|| format!("failed to parse {} contents", name))?; - Ok(bytes32.into()) + None => Ok(Bytes32::default()), } - None => Ok(Bytes32::default()), } - } - let last_block_hash = parse_hex(&opts.last_block_hash, "--last-block-hash")?; - let last_send_root = parse_hex(&opts.last_send_root, "--last-send-root")?; - env.small_globals = [opts.inbox_position, opts.position_within_message]; - env.large_globals = [last_block_hash, last_send_root]; - Ok(env) + let last_block_hash = parse_hex(&opts.last_block_hash, "--last-block-hash")?; + let last_send_root = parse_hex(&opts.last_send_root, "--last-send-root")?; + env.small_globals = [opts.inbox_position, opts.position_within_message]; + env.large_globals = [last_block_hash, last_send_root]; + Ok(env) + } } pub fn send_results(&mut self, error: Option, memory_used: Pages) { diff --git a/arbitrator/jit/src/main.rs b/arbitrator/jit/src/main.rs index e432dc215c..6e44500215 100644 --- a/arbitrator/jit/src/main.rs +++ b/arbitrator/jit/src/main.rs @@ -10,6 +10,7 @@ use structopt::StructOpt; mod arbcompress; mod caller_env; mod machine; +mod prepare; mod program; mod socket; mod stylus_backend; @@ -46,6 +47,10 @@ pub struct Opts { debug: bool, #[structopt(long)] require_success: bool, + // JSON inputs supercede any of the command-line inputs which could + // be specified in the JSON file. + #[structopt(long)] + json_inputs: Option, } fn main() -> Result<()> { diff --git a/arbitrator/jit/src/prepare.rs b/arbitrator/jit/src/prepare.rs new file mode 100644 index 0000000000..e7a7ba0f4d --- /dev/null +++ b/arbitrator/jit/src/prepare.rs @@ -0,0 +1,73 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::WasmEnv; +use arbutil::{Bytes32, PreimageType}; +use eyre::Ok; +use prover::parse_input::FileData; +use std::env; +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + +// local_target matches rawdb.LocalTarget() on the go side. +// While generating json_inputs file, one should make sure user_wasms map +// has entry for the system's arch that jit validation is being run on +pub fn local_target() -> String { + if env::consts::OS == "linux" { + match env::consts::ARCH { + "aarch64" => "arm64".to_string(), + "x86_64" => "amd64".to_string(), + _ => "host".to_string(), + } + } else { + "host".to_string() + } +} + +pub fn prepare_env(json_inputs: PathBuf, debug: bool) -> eyre::Result { + let file = File::open(json_inputs)?; + let reader = BufReader::new(file); + + let data = FileData::from_reader(reader)?; + + let mut env = WasmEnv::default(); + env.process.forks = false; // Should be set to false when using json_inputs + env.process.debug = debug; + + let block_hash: [u8; 32] = data.start_state.block_hash.try_into().unwrap(); + let block_hash: Bytes32 = block_hash.into(); + let send_root: [u8; 32] = data.start_state.send_root.try_into().unwrap(); + let send_root: Bytes32 = send_root.into(); + let bytes32_vals: [Bytes32; 2] = [block_hash, send_root]; + let u64_vals: [u64; 2] = [data.start_state.batch, data.start_state.pos_in_batch]; + env.small_globals = u64_vals; + env.large_globals = bytes32_vals; + + for batch_info in data.batch_info.iter() { + env.sequencer_messages + .insert(batch_info.number, batch_info.data_b64.clone()); + } + + if data.delayed_msg_nr != 0 && !data.delayed_msg_b64.is_empty() { + env.delayed_messages + .insert(data.delayed_msg_nr, data.delayed_msg_b64.clone()); + } + + for (ty, inner_map) in data.preimages_b64 { + let preimage_ty = PreimageType::try_from(ty as u8)?; + let map = env.preimages.entry(preimage_ty).or_default(); + for (hash, preimage) in inner_map { + map.insert(hash, preimage); + } + } + + if let Some(user_wasms) = data.user_wasms.get(&local_target()) { + for (module_hash, module_asm) in user_wasms.iter() { + env.module_asms + .insert(*module_hash, module_asm.as_vec().into()); + } + } + + Ok(env) +} diff --git a/arbitrator/jit/src/program.rs b/arbitrator/jit/src/program.rs index c608a3cf85..f10a059748 100644 --- a/arbitrator/jit/src/program.rs +++ b/arbitrator/jit/src/program.rs @@ -6,6 +6,7 @@ use crate::caller_env::JitEnv; use crate::machine::{Escape, MaybeEscape, WasmEnvMut}; use crate::stylus_backend::exec_wasm; +use arbutil::evm::api::Gas; use arbutil::Bytes32; use arbutil::{evm::EvmData, format::DebugBytes, heapify}; use caller_env::{GuestPtr, MemAccess}; @@ -16,8 +17,45 @@ use prover::{ programs::{config::PricingParams, prelude::*}, }; -/// activates a user program +const DEFAULT_STYLUS_ARBOS_VERSION: u64 = 31; + pub fn activate( + env: WasmEnvMut, + wasm_ptr: GuestPtr, + wasm_size: u32, + pages_ptr: GuestPtr, + asm_estimate_ptr: GuestPtr, + init_cost_ptr: GuestPtr, + cached_init_cost_ptr: GuestPtr, + stylus_version: u16, + debug: u32, + codehash: GuestPtr, + module_hash_ptr: GuestPtr, + gas_ptr: GuestPtr, + err_buf: GuestPtr, + err_buf_len: u32, +) -> Result { + activate_v2( + env, + wasm_ptr, + wasm_size, + pages_ptr, + asm_estimate_ptr, + init_cost_ptr, + cached_init_cost_ptr, + stylus_version, + DEFAULT_STYLUS_ARBOS_VERSION, + debug, + codehash, + module_hash_ptr, + gas_ptr, + err_buf, + err_buf_len, + ) +} + +/// activates a user program +pub fn activate_v2( mut env: WasmEnvMut, wasm_ptr: GuestPtr, wasm_size: u32, @@ -25,7 +63,8 @@ pub fn activate( asm_estimate_ptr: GuestPtr, init_cost_ptr: GuestPtr, cached_init_cost_ptr: GuestPtr, - version: u16, + stylus_version: u16, + arbos_version_for_gas: u64, debug: u32, codehash: GuestPtr, module_hash_ptr: GuestPtr, @@ -40,7 +79,15 @@ pub fn activate( let page_limit = mem.read_u16(pages_ptr); let gas_left = &mut mem.read_u64(gas_ptr); - match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) { + match Module::activate( + &wasm, + codehash, + stylus_version, + arbos_version_for_gas, + page_limit, + debug, + gas_left, + ) { Ok((module, data)) => { mem.write_u64(gas_ptr, *gas_left); mem.write_u16(pages_ptr, data.footprint); @@ -85,7 +132,7 @@ pub fn new_program( // buy ink let pricing = config.stylus.pricing; - let ink = pricing.gas_to_ink(gas); + let ink = pricing.gas_to_ink(Gas(gas)); let Some(module) = exec.module_asms.get(&compiled_hash).cloned() else { return Err(Escape::Failure(format!( @@ -171,7 +218,7 @@ pub fn set_response( let raw_data = mem.read_slice(raw_data_ptr, raw_data_len as usize); let thread = exec.threads.last_mut().unwrap(); - thread.set_response(id, result, raw_data, gas) + thread.set_response(id, result, raw_data, Gas(gas)) } /// sends previos response @@ -222,9 +269,47 @@ pub fn create_stylus_config( Ok(res as u64) } -/// Creates an `EvmData` handler from its component parts. pub fn create_evm_data( + env: WasmEnvMut, + block_basefee_ptr: GuestPtr, + chainid: u64, + block_coinbase_ptr: GuestPtr, + block_gas_limit: u64, + block_number: u64, + block_timestamp: u64, + contract_address_ptr: GuestPtr, + module_hash_ptr: GuestPtr, + msg_sender_ptr: GuestPtr, + msg_value_ptr: GuestPtr, + tx_gas_price_ptr: GuestPtr, + tx_origin_ptr: GuestPtr, + cached: u32, + reentrant: u32, +) -> Result { + create_evm_data_v2( + env, + DEFAULT_STYLUS_ARBOS_VERSION, + block_basefee_ptr, + chainid, + block_coinbase_ptr, + block_gas_limit, + block_number, + block_timestamp, + contract_address_ptr, + module_hash_ptr, + msg_sender_ptr, + msg_value_ptr, + tx_gas_price_ptr, + tx_origin_ptr, + cached, + reentrant, + ) +} + +/// Creates an `EvmData` handler from its component parts. +pub fn create_evm_data_v2( mut env: WasmEnvMut, + arbos_version: u64, block_basefee_ptr: GuestPtr, chainid: u64, block_coinbase_ptr: GuestPtr, @@ -243,6 +328,7 @@ pub fn create_evm_data( let (mut mem, _) = env.jit_env(); let evm_data = EvmData { + arbos_version, block_basefee: mem.read_bytes32(block_basefee_ptr), cached: cached != 0, chainid, diff --git a/arbitrator/jit/src/stylus_backend.rs b/arbitrator/jit/src/stylus_backend.rs index 61dbf258d4..0d8c477c6c 100644 --- a/arbitrator/jit/src/stylus_backend.rs +++ b/arbitrator/jit/src/stylus_backend.rs @@ -4,7 +4,7 @@ #![allow(clippy::too_many_arguments)] use crate::machine::{Escape, MaybeEscape}; -use arbutil::evm::api::VecReader; +use arbutil::evm::api::{Gas, Ink, VecReader}; use arbutil::evm::{ api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET}, req::EvmApiRequestor, @@ -28,7 +28,7 @@ use stylus::{native::NativeInstance, run::RunProgram}; struct MessageToCothread { result: Vec, raw_data: Vec, - cost: u64, + cost: Gas, } #[derive(Clone)] @@ -47,7 +47,7 @@ impl RequestHandler for CothreadRequestor { &mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>, - ) -> (Vec, VecReader, u64) { + ) -> (Vec, VecReader, Gas) { let msg = MessageFromCothread { req_type: req_type as u32 + EVM_API_METHOD_REQ_OFFSET, req_data: req_data.as_ref().to_vec(), @@ -104,7 +104,7 @@ impl CothreadHandler { id: u32, result: Vec, raw_data: Vec, - cost: u64, + cost: Gas, ) -> MaybeEscape { let Some(msg) = self.last_request.clone() else { return Escape::hostio("trying to set response but no message pending"); @@ -131,7 +131,7 @@ pub fn exec_wasm( compile: CompileConfig, config: StylusConfig, evm_data: EvmData, - ink: u64, + ink: Ink, ) -> Result { let (tothread_tx, tothread_rx) = mpsc::sync_channel::(0); let (fromthread_tx, fromthread_rx) = mpsc::sync_channel::(0); @@ -150,7 +150,7 @@ pub fn exec_wasm( let outcome = instance.run_main(&calldata, config, ink); let ink_left = match outcome.as_ref() { - Ok(UserOutcome::OutOfStack) => 0, // take all ink when out of stack + Ok(UserOutcome::OutOfStack) => Ink(0), // take all ink when out of stack _ => instance.ink_left().into(), }; diff --git a/arbitrator/prover/Cargo.toml b/arbitrator/prover/Cargo.toml index a634dba19e..da329b1cb5 100644 --- a/arbitrator/prover/Cargo.toml +++ b/arbitrator/prover/Cargo.toml @@ -19,10 +19,10 @@ num = "0.4" rustc-demangle = "0.1.21" serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = "1.0.67" +serde_with = { version = "3.8.1", features = ["base64"] } sha3 = "0.9.1" static_assertions = "1.1.0" structopt = "0.3.23" -serde_with = "1.12.1" parking_lot = "0.12.1" lazy_static.workspace = true itertools = "0.10.5" @@ -47,10 +47,6 @@ enum-iterator = "2.0.1" criterion = { version = "0.5.0", features = ["html_reports"] } rand = "0.8.4" -[build-dependencies] -wasmer = { path = "../tools/wasmer/lib/api" } -arbutil = { path = "../arbutil/" } - [[bench]] name = "merkle_bench" harness = false diff --git a/arbitrator/prover/build.rs b/arbitrator/prover/build.rs deleted file mode 100644 index db0560b2c5..0000000000 --- a/arbitrator/prover/build.rs +++ /dev/null @@ -1,89 +0,0 @@ -use arbutil::hostios::HOSTIOS; -use std::{env, fmt::Write, fs, path::Path}; - -pub fn gen_forwarder(out_path: &Path) { - let mut wat = String::new(); - macro_rules! wln { - ($($text:tt)*) => { - writeln!(wat, $($text)*).unwrap(); - }; - } - let s = " "; - - wln!("(module"); - - macro_rules! group { - ($list:expr, $kind:expr) => { - (!$list.is_empty()) - .then(|| { - format!( - " ({} {})", - $kind, - $list - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(" ") - ) - }) - .unwrap_or_default() - }; - } - - wln!("{s};; symbols to re-export"); - for (name, ins, outs) in HOSTIOS { - let params = group!(ins, "param"); - let result = group!(outs, "result"); - wln!(r#"{s}(import "user_host" "{name}" (func $_{name}{params}{result}))"#); - } - wln!(); - - wln!("{s};; reserved offsets for future user_host imports"); - for i in HOSTIOS.len()..512 { - wln!("{s}(func $reserved_{i} unreachable)"); - } - wln!(); - - wln!( - "{s};; allows user_host to request a trap\n\ - {s}(global $trap (mut i32) (i32.const 0))\n\ - {s}(func $check\n\ - {s}{s}global.get $trap ;; see if set\n\ - {s}{s}(global.set $trap (i32.const 0)) ;; reset the flag\n\ - {s}{s}(if (then (unreachable)))\n\ - {s})\n\ - {s}(func (export \"forward__set_trap\")\n\ - {s}{s}(global.set $trap (i32.const 1))\n\ - {s})\n" - ); - - wln!("{s};; user linkage"); - for (name, ins, outs) in HOSTIOS { - let params = group!(ins, "param"); - let result = group!(outs, "result"); - wln!("{s}(func (export \"vm_hooks__{name}\"){params}{result}"); - - for i in 0..ins.len() { - wln!("{s}{s}local.get {i}"); - } - - wln!( - "{s}{s}call $_{name}\n\ - {s}{s}call $check\n\ - {s})" - ); - } - - wln!(")"); - eprintln!("{}", &wat); - - let wasm = wasmer::wat2wasm(wat.as_bytes()).unwrap(); - - fs::write(out_path, wasm.as_ref()).unwrap(); -} - -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - let forwarder_path = Path::new(&out_dir).join("forwarder.wasm"); - gen_forwarder(&forwarder_path); -} diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs index b1d819deb2..2260f6bf48 100644 --- a/arbitrator/prover/src/binary.rs +++ b/arbitrator/prover/src/binary.rs @@ -9,7 +9,9 @@ use crate::{ }, value::{ArbValueType, FunctionType, IntegerValType, Value}, }; -use arbutil::{math::SaturatingSum, Bytes32, Color, DebugColor}; +use arbutil::{ + evm::ARBOS_VERSION_STYLUS_CHARGING_FIXES, math::SaturatingSum, Bytes32, Color, DebugColor, +}; use eyre::{bail, ensure, eyre, Result, WrapErr}; use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use nom::{ @@ -471,14 +473,10 @@ pub fn parse<'a>(input: &'a [u8], path: &'_ Path) -> Result> { let export = export.rsplit("__").take(1); exports.extend(export); } - // forwarder is allowed to re-export the same name - // TODO: is there a real problem if import_name == export_name but module names don't? - if path != Path::new("forwarder") { - for import in &binary.imports { - let name = import.name; - if exports.contains(name) { - bail!("binary exports an import with the same name {}", name.red()); - } + for import in &binary.imports { + let name = import.name; + if exports.contains(name) { + bail!("binary exports an import with the same name {}", name.red()); } } @@ -645,6 +643,7 @@ impl<'a> WasmBinary<'a> { /// Parses and instruments a user wasm pub fn parse_user( wasm: &'a [u8], + arbos_version_for_gas: u64, page_limit: u16, compile: &CompileConfig, codehash: &Bytes32, @@ -682,6 +681,10 @@ impl<'a> WasmBinary<'a> { limit!(65536, code.expr.len(), "opcodes in func body"); } + if arbos_version_for_gas >= ARBOS_VERSION_STYLUS_CHARGING_FIXES { + limit!(513, bin.imports.len(), "imports") + } + let table_entries = bin.tables.iter().map(|x| x.initial).saturating_sum(); limit!(4096, table_entries, "table entries"); diff --git a/arbitrator/prover/src/host.rs b/arbitrator/prover/src/host.rs index dc0f0b74b2..1d0fe658ec 100644 --- a/arbitrator/prover/src/host.rs +++ b/arbitrator/prover/src/host.rs @@ -405,7 +405,7 @@ impl Hostio { } } -pub fn get_impl(module: &str, name: &str) -> Result { +pub fn get_impl(module: &str, name: &str) -> Result<(Function, bool)> { let hostio: Hostio = format!("{module}__{name}").parse()?; let append = |code: &mut Vec| { @@ -414,11 +414,8 @@ pub fn get_impl(module: &str, name: &str) -> Result { Ok(()) }; - Function::new(&[], append, hostio.ty(), &[]) -} - -pub fn hostio_module_is_debug(module: &str) -> bool { - module == "console" || module == "debug" + let debug = module == "console" || module == "debug"; + Function::new(&[], append, hostio.ty(), &[]).map(|x| (x, debug)) } /// Adds internal functions to a module. diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index e45d12c492..08473c2598 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -11,6 +11,8 @@ pub mod machine; /// cbindgen:ignore pub mod memory; pub mod merkle; +pub mod parse_input; +pub mod prepare; mod print; pub mod programs; mod reinterpret; @@ -70,16 +72,10 @@ pub unsafe extern "C" fn arbitrator_load_machine( library_paths: *const *const c_char, library_paths_size: isize, debug_chain: usize, - with_forwarder: bool, ) -> *mut Machine { let debug_chain = debug_chain != 0; - match arbitrator_load_machine_impl( - binary_path, - library_paths, - library_paths_size, - debug_chain, - with_forwarder, - ) { + match arbitrator_load_machine_impl(binary_path, library_paths, library_paths_size, debug_chain) + { Ok(mach) => mach, Err(err) => { eprintln!("Error loading binary: {:?}", err); @@ -93,7 +89,6 @@ unsafe fn arbitrator_load_machine_impl( library_paths: *const *const c_char, library_paths_size: isize, debug_chain: bool, - with_forwarder: bool, ) -> Result<*mut Machine> { let binary_path = cstr_to_string(binary_path); let binary_path = Path::new(&binary_path); @@ -114,7 +109,6 @@ unsafe fn arbitrator_load_machine_impl( Default::default(), Default::default(), get_empty_preimage_resolver(), - with_forwarder, )?; Ok(Box::into_raw(Box::new(mach))) } diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 2418d3cc2e..dec355ac7c 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -19,7 +19,7 @@ use crate::{ IBinOpType, IRelOpType, IUnOpType, Instruction, Opcode, }, }; -use arbutil::{crypto, hostios::HOSTIOS, math, Bytes32, Color, DebugColor, PreimageType}; +use arbutil::{crypto, math, Bytes32, Color, DebugColor, PreimageType}; use brotli::Dictionary; #[cfg(feature = "native")] use c_kzg::BYTES_PER_BLOB; @@ -330,115 +330,36 @@ pub struct Module { pub(crate) extra_hash: Arc, } -#[cfg(feature = "native")] -static FORWARDER_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/forwarder.wasm")); - lazy_static! { static ref USER_IMPORTS: HashMap = { let mut imports = HashMap::default(); - // 0-513 are internal - for (index, (name, ins, outs)) in HOSTIOS.iter().enumerate() { - let import = AvailableImport::new(FunctionType::new(ins.iter().map(|x|x.into()).collect::>(), outs.iter().map(|x|x.into()).collect::>()), 1, (index + 514).try_into().unwrap()); - imports.insert(format!("vm_hooks__{name}"), import); - } - imports - }; -} - -trait ImportResolver: for<'a, 'b> Fn(&'a str, &'b str) -> Result + Sized { - fn compose(self, other: impl ImportResolver) -> impl ImportResolver { - move |module, name| { - self(module, name).or_else(|original_err| { - let mut result = other(module, name); - if result.is_err() { - for err_layer in original_err.chain().rev() { - result = result.wrap_err(format!("{err_layer}")); - } - } - result - }) - } - } + let forward = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); + let forward = binary::parse(forward, Path::new("forward")).unwrap(); - fn condition(self, condition: bool) -> impl ImportResolver { - move |module, name| { - if condition { - self(module, name) - } else { - bail!("resolver disabled") + for (name, &(export, kind)) in &forward.exports { + if kind == ExportKind::Func { + let ty = match forward.get_function(FunctionIndex::from_u32(export)) { + Ok(ty) => ty, + Err(error) => panic!("failed to read export {name}: {error:?}"), + }; + let import = AvailableImport::new(ty, 1, export); + imports.insert(name.to_owned(), import); } } - } + imports + }; } -impl Fn(&'a str, &'b str) -> Result> ImportResolver for F {} - impl Module { - fn make_imports_resolver( - import_map: &HashMap, - ) -> impl ImportResolver + '_ { - move |module, name| { - let qualified_name = format!("{module}__{name}"); - - let Some(import) = import_map.get(&qualified_name) else { - bail!("func not found {module} {name}") - }; - let wavm = vec![ - Instruction::simple(Opcode::InitFrame), - Instruction::with_data( - Opcode::CrossModuleCall, - pack_cross_module_call(import.module, import.func), - ), - Instruction::simple(Opcode::Return), - ]; - Ok(Function::new_from_wavm(wavm, import.ty.clone(), vec![])) - } - } - - fn make_forward_resolver( - import_map: &HashMap, - ) -> impl ImportResolver + '_ { - move |module, name| { - let qualified_name = format!("{module}__{name}"); - - let Some(import) = import_map.get(&qualified_name) else { - bail!("func not found {module} {name}") - }; - let wavm = vec![ - Instruction::simple(Opcode::InitFrame), - Instruction::with_data( - Opcode::CrossModuleForward, - pack_cross_module_call(import.module, import.func), - ), - Instruction::simple(Opcode::Return), - ]; - Ok(Function::new_from_wavm(wavm, import.ty.clone(), vec![])) - } - } - - fn hostio_resolver(module: &str, name: &str) -> Result { - if host::hostio_module_is_debug(module) { - return Self::notfound_resolver(module, name); - } - host::get_impl(module, name) - } - - fn debug_resolver(module: &str, name: &str) -> Result { - if !host::hostio_module_is_debug(module) { - return Self::notfound_resolver(module, name); - } - host::get_impl(module, name) - } + const FORWARDING_PREFIX: &'static str = "arbitrator_forward__"; - fn notfound_resolver(module: &str, name: &str) -> Result { - bail!("import not found {module} {name}") - } - - fn from_binary( + fn from_binary( bin: &WasmBinary, - import_resolver: R, + available_imports: &HashMap, floating_point_impls: &FloatingPointImpls, + allow_hostapi: bool, + debug_funcs: bool, stylus_data: Option, ) -> Result { let mut code = Vec::new(); @@ -450,18 +371,56 @@ impl Module { for import in &bin.imports { let module = import.module; let have_ty = &bin.types[import.offset as usize]; + // allow_hostapi is only set for system modules like the + // forwarder. We restrict stripping the prefix for user modules. + let (forward, import_name) = + if allow_hostapi && import.name.starts_with(Self::FORWARDING_PREFIX) { + (true, &import.name[Self::FORWARDING_PREFIX.len()..]) + } else { + (false, import.name) + }; - let func = import_resolver(import.module, import.name)?; + let qualified_name = format!("{module}__{import_name}"); + let func = if let Some(import) = available_imports.get(&qualified_name) { + let call = match forward { + true => Opcode::CrossModuleForward, + false => Opcode::CrossModuleCall, + }; + let wavm = vec![ + Instruction::simple(Opcode::InitFrame), + Instruction::with_data( + call, + pack_cross_module_call(import.module, import.func), + ), + Instruction::simple(Opcode::Return), + ]; + Function::new_from_wavm(wavm, import.ty.clone(), vec![]) + } else if let Ok((hostio, debug)) = host::get_impl(import.module, import_name) { + ensure!( + (debug && debug_funcs) || (!debug && allow_hostapi), + "Host func {} in {} not enabled debug_funcs={debug_funcs} hostapi={allow_hostapi} debug={debug}", + import_name.red(), + import.module.red(), + ); + hostio + } else { + bail!( + "No such import {} in {} for {}", + import_name.red(), + import.module.red(), + bin_name.red() + ) + }; ensure!( &func.ty == have_ty, "Import {} for {} has different function signature than export.\nexpected {} in {}\nbut have {}", - import.name.red(), bin_name.red(), func.ty.red(), module.red(), have_ty.red(), + import_name.red(), bin_name.red(), func.ty.red(), module.red(), have_ty.red(), ); func_type_idxs.push(import.offset); code.push(func); - host_call_hooks.push(Some((import.module.into(), import.name.into()))); + host_call_hooks.push(Some((import.module.into(), import_name.into()))); } func_type_idxs.extend(bin.functions.iter()); @@ -657,9 +616,10 @@ impl Module { ) -> Result { Self::from_binary( bin, - Module::make_imports_resolver(&USER_IMPORTS) - .compose(Module::debug_resolver.condition(debug_funcs)), + &USER_IMPORTS, &HashMap::default(), + false, + debug_funcs, stylus_data, ) } @@ -1270,7 +1230,6 @@ impl Machine { global_state: GlobalState, inbox_contents: HashMap<(InboxIdentifier, u64), Vec>, preimage_resolver: PreimageResolver, - with_forwarder: bool, ) -> Result { let bin_source = file_bytes(binary_path)?; let bin = parse(&bin_source, binary_path) @@ -1296,7 +1255,6 @@ impl Machine { inbox_contents, preimage_resolver, None, - with_forwarder, ) } @@ -1326,7 +1284,6 @@ impl Machine { HashMap::default(), Arc::new(|_, _, _| panic!("tried to read preimage")), Some(stylus_data), - false, )?; let footprint: u32 = stylus_data.footprint.into(); @@ -1374,7 +1331,6 @@ impl Machine { inbox_contents: HashMap<(InboxIdentifier, u64), Vec>, preimage_resolver: PreimageResolver, stylus_data: Option, - with_forwarder: bool, ) -> Result { use ArbValueType::*; @@ -1382,19 +1338,25 @@ impl Machine { let mut modules = vec![Module::default()]; let mut available_imports = HashMap::default(); let mut floating_point_impls = HashMap::default(); - - let forwarder = if with_forwarder { - #[cfg(not(feature = "native"))] - bail!("forwarder not supported without native"); - - #[cfg(feature = "native")] - Some(parse(FORWARDER_WASM, Path::new("forwarder"))?) - } else { - None - }; + let main_module_index = u32::try_from(modules.len() + libraries.len())?; + + // make the main module's exports available to libraries + for (name, &(export, kind)) in &bin.exports { + if kind == ExportKind::Func { + let index: usize = export.try_into()?; + if let Some(index) = index.checked_sub(bin.imports.len()) { + let ty: usize = bin.functions[index].try_into()?; + let ty = bin.types[ty].clone(); + available_imports.insert( + format!("env__wavm_guest_call__{name}"), + AvailableImport::new(ty, main_module_index, export), + ); + } + } + } // collect all the library exports in advance so they can use each other's - for (index, lib) in forwarder.iter().chain(libraries.iter()).enumerate() { + for (index, lib) in libraries.iter().enumerate() { let module = 1 + index as u32; // off by one due to the entry point for (name, &(export, kind)) in &lib.exports { if kind == ExportKind::Func { @@ -1402,57 +1364,54 @@ impl Machine { Ok(ty) => ty, Err(error) => bail!("failed to read export {name}: {error}"), }; - let import = AvailableImport::new(ty.clone(), module, export); + let import = AvailableImport::new(ty, module, export); available_imports.insert(name.to_owned(), import); - if let Ok(op) = name.parse::() { - let mut sig = op.signature(); - // wavm codegen takes care of effecting this type change at callsites - for ty in sig.inputs.iter_mut().chain(sig.outputs.iter_mut()) { - if *ty == F32 { - *ty = I32; - } else if *ty == F64 { - *ty = I64; - } - } - ensure!( - ty == sig, - "Wrong type for floating point impl {} expecting {} but got {}", - name.red(), - sig.red(), - ty.red() - ); - floating_point_impls.insert(op, (module, export)); - } } } } - if let Some(lib) = forwarder { - modules.push(Module::from_binary( - &lib, - Module::make_forward_resolver(&available_imports), - &floating_point_impls, - None, - )?); - } - - for lib in libraries.iter() { - modules.push(Module::from_binary( + for lib in libraries { + let module = Module::from_binary( lib, - Module::hostio_resolver - .compose(Module::debug_resolver.condition(debug_funcs)) - .compose(Module::make_imports_resolver(&available_imports)), + &available_imports, &floating_point_impls, + true, + debug_funcs, None, - )?); + )?; + for (name, &func) in &*module.func_exports { + let ty = module.func_types[func as usize].clone(); + if let Ok(op) = name.parse::() { + let mut sig = op.signature(); + // wavm codegen takes care of effecting this type change at callsites + for ty in sig.inputs.iter_mut().chain(sig.outputs.iter_mut()) { + if *ty == F32 { + *ty = I32; + } else if *ty == F64 { + *ty = I64; + } + } + ensure!( + ty == sig, + "Wrong type for floating point impl {} expecting {} but got {}", + name.red(), + sig.red(), + ty.red() + ); + floating_point_impls.insert(op, (modules.len() as u32, func)); + } + } + modules.push(module); } + // Shouldn't be necessary, but to be safe, don't allow the main binary to import its own guest calls + available_imports.retain(|_, i| i.module as usize != modules.len()); modules.push(Module::from_binary( &bin, - Module::make_imports_resolver(&available_imports) - .compose(Module::hostio_resolver.condition(allow_hostapi_from_main)) - .compose(Module::debug_resolver.condition(debug_funcs)), + &available_imports, &floating_point_impls, + allow_hostapi_from_main, + debug_funcs, stylus_data, )?); @@ -1857,7 +1816,12 @@ impl Machine { } #[cfg(feature = "native")] - pub fn call_user_func(&mut self, func: &str, args: Vec, ink: u64) -> Result> { + pub fn call_user_func( + &mut self, + func: &str, + args: Vec, + ink: arbutil::evm::api::Ink, + ) -> Result> { self.set_ink(ink); self.call_function("user", func, args) } diff --git a/arbitrator/prover/src/main.rs b/arbitrator/prover/src/main.rs index 442b35992d..a889cc60f3 100644 --- a/arbitrator/prover/src/main.rs +++ b/arbitrator/prover/src/main.rs @@ -8,6 +8,7 @@ use eyre::{eyre, Context, Result}; use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use prover::{ machine::{GlobalState, InboxIdentifier, Machine, MachineStatus, PreimageResolver, ProofInfo}, + prepare::prepare_machine, utils::{file_bytes, hash_preimage, CBytes}, wavm::Opcode, }; @@ -86,8 +87,10 @@ struct Opts { skip_until_host_io: bool, #[structopt(long)] max_steps: Option, - #[structopt(short, long)] - with_forwarder: bool, + // JSON inputs supercede any of the command-line inputs which could + // be specified in the JSON file. + #[structopt(long)] + json_inputs: Option, } fn file_with_stub_header(path: &Path, headerlength: usize) -> Result> { @@ -137,84 +140,8 @@ fn main() -> Result<()> { } } } - let mut inbox_contents = HashMap::default(); - let mut inbox_position = opts.inbox_position; - let mut delayed_position = opts.delayed_inbox_position; - let inbox_header_len; - let delayed_header_len; - if opts.inbox_add_stub_headers { - inbox_header_len = INBOX_HEADER_LEN; - delayed_header_len = DELAYED_HEADER_LEN + 1; - } else { - inbox_header_len = 0; - delayed_header_len = 0; - } - - for path in opts.inbox { - inbox_contents.insert( - (InboxIdentifier::Sequencer, inbox_position), - file_with_stub_header(&path, inbox_header_len)?, - ); - println!("read file {:?} to seq. inbox {}", &path, inbox_position); - inbox_position += 1; - } - for path in opts.delayed_inbox { - inbox_contents.insert( - (InboxIdentifier::Delayed, delayed_position), - file_with_stub_header(&path, delayed_header_len)?, - ); - delayed_position += 1; - } - let mut preimages: HashMap> = HashMap::default(); - if let Some(path) = opts.preimages { - let mut file = BufReader::new(File::open(path)?); - loop { - let mut ty_buf = [0u8; 1]; - match file.read_exact(&mut ty_buf) { - Ok(()) => {} - Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, - Err(e) => return Err(e.into()), - } - let preimage_ty: PreimageType = ty_buf[0].try_into()?; - - let mut size_buf = [0u8; 8]; - file.read_exact(&mut size_buf)?; - let size = u64::from_le_bytes(size_buf) as usize; - let mut buf = vec![0u8; size]; - file.read_exact(&mut buf)?; - - let hash = hash_preimage(&buf, preimage_ty)?; - preimages - .entry(preimage_ty) - .or_default() - .insert(hash.into(), buf.as_slice().into()); - } - } - let preimage_resolver = - Arc::new(move |_, ty, hash| preimages.get(&ty).and_then(|m| m.get(&hash)).cloned()) - as PreimageResolver; - - let last_block_hash = decode_hex_arg(&opts.last_block_hash, "--last-block-hash")?; - let last_send_root = decode_hex_arg(&opts.last_send_root, "--last-send-root")?; - - let global_state = GlobalState { - u64_vals: [opts.inbox_position, opts.position_within_message], - bytes32_vals: [last_block_hash, last_send_root], - }; - - let mut mach = Machine::from_paths( - &opts.libraries, - &opts.binary, - true, - opts.allow_hostapi, - opts.debug_funcs, - true, - global_state, - inbox_contents, - preimage_resolver, - opts.with_forwarder, - )?; + let mut mach = initialize_machine(&opts)?; for path in &opts.stylus_modules { let err = || eyre!("failed to read module at {}", path.to_string_lossy().red()); @@ -417,6 +344,13 @@ fn main() -> Result<()> { }); } + println!( + "End GlobalState:\n BlockHash: {:?}\n SendRoot: {:?}\n Batch: {}\n PosInBatch: {}", + mach.get_global_state().bytes32_vals[0], + mach.get_global_state().bytes32_vals[1], + mach.get_global_state().u64_vals[0], + mach.get_global_state().u64_vals[1] + ); println!("End machine status: {:?}", mach.get_status()); println!("End machine hash: {}", mach.hash()); println!("End machine stack: {:?}", mach.get_data_stack()); @@ -465,7 +399,6 @@ fn main() -> Result<()> { } } } - let opts_binary = opts.binary; let opts_libraries = opts.libraries; let format_pc = |module_num: usize, func_num: usize| -> (String, String) { @@ -546,3 +479,87 @@ fn main() -> Result<()> { } Ok(()) } + +fn initialize_machine(opts: &Opts) -> eyre::Result { + if let Some(json_inputs) = opts.json_inputs.clone() { + prepare_machine(json_inputs, opts.binary.clone()) + } else { + let mut inbox_contents = HashMap::default(); + let mut inbox_position = opts.inbox_position; + let mut delayed_position = opts.delayed_inbox_position; + let inbox_header_len; + let delayed_header_len; + if opts.inbox_add_stub_headers { + inbox_header_len = INBOX_HEADER_LEN; + delayed_header_len = DELAYED_HEADER_LEN + 1; + } else { + inbox_header_len = 0; + delayed_header_len = 0; + } + + for path in opts.inbox.clone() { + inbox_contents.insert( + (InboxIdentifier::Sequencer, inbox_position), + file_with_stub_header(&path, inbox_header_len)?, + ); + println!("read file {:?} to seq. inbox {}", &path, inbox_position); + inbox_position += 1; + } + for path in opts.delayed_inbox.clone() { + inbox_contents.insert( + (InboxIdentifier::Delayed, delayed_position), + file_with_stub_header(&path, delayed_header_len)?, + ); + delayed_position += 1; + } + + let mut preimages: HashMap> = HashMap::default(); + if let Some(path) = opts.preimages.clone() { + let mut file = BufReader::new(File::open(path)?); + loop { + let mut ty_buf = [0u8; 1]; + match file.read_exact(&mut ty_buf) { + Ok(()) => {} + Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, + Err(e) => return Err(e.into()), + } + let preimage_ty: PreimageType = ty_buf[0].try_into()?; + + let mut size_buf = [0u8; 8]; + file.read_exact(&mut size_buf)?; + let size = u64::from_le_bytes(size_buf) as usize; + let mut buf = vec![0u8; size]; + file.read_exact(&mut buf)?; + + let hash = hash_preimage(&buf, preimage_ty)?; + preimages + .entry(preimage_ty) + .or_default() + .insert(hash.into(), buf.as_slice().into()); + } + } + let preimage_resolver = + Arc::new(move |_, ty, hash| preimages.get(&ty).and_then(|m| m.get(&hash)).cloned()) + as PreimageResolver; + + let last_block_hash = decode_hex_arg(&opts.last_block_hash, "--last-block-hash")?; + let last_send_root = decode_hex_arg(&opts.last_send_root, "--last-send-root")?; + + let global_state = GlobalState { + u64_vals: [opts.inbox_position, opts.position_within_message], + bytes32_vals: [last_block_hash, last_send_root], + }; + + Machine::from_paths( + &opts.libraries, + &opts.binary, + true, + opts.allow_hostapi, + opts.debug_funcs, + true, + global_state, + inbox_contents, + preimage_resolver, + ) + } +} diff --git a/arbitrator/prover/src/merkle.rs b/arbitrator/prover/src/merkle.rs index 4a1278b4cb..fbd704dfc6 100644 --- a/arbitrator/prover/src/merkle.rs +++ b/arbitrator/prover/src/merkle.rs @@ -549,8 +549,7 @@ mod test { let mut empty_node = Bytes32([ 57, 29, 211, 154, 252, 227, 18, 99, 65, 126, 203, 166, 252, 232, 32, 3, 98, 194, 254, 186, 118, 14, 139, 192, 101, 156, 55, 194, 101, 11, 11, 168, - ]) - .clone(); + ]); for _ in 0..64 { print!("Bytes32(["); for i in 0..32 { @@ -607,7 +606,7 @@ mod test { for layer in 0..64 { // empty_hash_at is just a lookup, but empty_hash is calculated iteratively. assert_eq!(empty_hash_at(ty, layer), &empty_hash); - empty_hash = hash_node(ty, &empty_hash, &empty_hash); + empty_hash = hash_node(ty, empty_hash, empty_hash); } } } diff --git a/arbitrator/prover/src/parse_input.rs b/arbitrator/prover/src/parse_input.rs new file mode 100644 index 0000000000..fa7adb4c41 --- /dev/null +++ b/arbitrator/prover/src/parse_input.rs @@ -0,0 +1,112 @@ +use arbutil::Bytes32; +use serde::Deserialize; +use serde_json; +use serde_with::base64::Base64; +use serde_with::As; +use serde_with::DisplayFromStr; +use std::{ + collections::HashMap, + io::{self, BufRead}, +}; + +/// prefixed_hex deserializes hex strings which are prefixed with `0x` +/// +/// The default hex deserializer does not support prefixed hex strings. +/// +/// It is an error to use this deserializer on a string that does not +/// begin with `0x`. +mod prefixed_hex { + use serde::{self, Deserialize, Deserializer}; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if let Some(s) = s.strip_prefix("0x") { + hex::decode(s).map_err(serde::de::Error::custom) + } else { + Err(serde::de::Error::custom("missing 0x prefix")) + } + } +} + +#[derive(Debug)] +pub struct UserWasm(Vec); + +/// UserWasm is a wrapper around Vec +/// +/// It is useful for decompressing a brotli-compressed wasm module. +/// +/// Note: The wrapped Vec is already Base64 decoded before +/// from(Vec) is called by serde. +impl UserWasm { + /// as_vec returns the decompressed wasm module as a Vec + pub fn as_vec(&self) -> Vec { + self.0.clone() + } +} + +impl AsRef<[u8]> for UserWasm { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// The Vec is compressed using brotli, and must be decompressed before use. +impl From> for UserWasm { + fn from(data: Vec) -> Self { + let decompressed = brotli::decompress(&data, brotli::Dictionary::Empty).unwrap(); + Self(decompressed) + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct BatchInfo { + pub number: u64, + #[serde(with = "As::")] + pub data_b64: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct StartState { + #[serde(with = "prefixed_hex")] + pub block_hash: Vec, + #[serde(with = "prefixed_hex")] + pub send_root: Vec, + pub batch: u64, + pub pos_in_batch: u64, +} + +/// FileData is the deserialized form of the input JSON file. +/// +/// The go JSON library in json.go uses some custom serialization and +/// compression logic that needs to be reversed when deserializing the +/// JSON in rust. +/// +/// Note: It is important to change this file whenever the go JSON +/// serialization changes. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct FileData { + pub id: u64, + pub has_delayed_msg: bool, + pub delayed_msg_nr: u64, + #[serde(with = "As::>>")] + pub preimages_b64: HashMap>>, + pub batch_info: Vec, + #[serde(with = "As::")] + pub delayed_msg_b64: Vec, + pub start_state: StartState, + #[serde(with = "As::>>")] + pub user_wasms: HashMap>, +} + +impl FileData { + pub fn from_reader(mut reader: R) -> io::Result { + let data = serde_json::from_reader(&mut reader)?; + Ok(data) + } +} diff --git a/arbitrator/bench/src/prepare.rs b/arbitrator/prover/src/prepare.rs similarity index 85% rename from arbitrator/bench/src/prepare.rs rename to arbitrator/prover/src/prepare.rs index 741a7350ac..a485267f39 100644 --- a/arbitrator/bench/src/prepare.rs +++ b/arbitrator/prover/src/prepare.rs @@ -1,13 +1,13 @@ use arbutil::{Bytes32, PreimageType}; -use prover::machine::{argument_data_to_inbox, GlobalState, Machine}; -use prover::utils::CBytes; use std::collections::HashMap; use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::sync::Arc; +use crate::machine::{argument_data_to_inbox, GlobalState, Machine}; use crate::parse_input::*; +use crate::utils::CBytes; pub fn prepare_machine(preimages: PathBuf, machines: PathBuf) -> eyre::Result { let file = File::open(preimages)?; @@ -40,6 +40,15 @@ pub fn prepare_machine(preimages: PathBuf, machines: PathBuf) -> eyre::Result u64 { - gas.saturating_mul(self.ink_price.into()) + pub fn gas_to_ink(&self, gas: Gas) -> Ink { + Ink(gas.0.saturating_mul(self.ink_price.into())) } - pub fn ink_to_gas(&self, ink: u64) -> u64 { - ink / self.ink_price as u64 // never 0 + pub fn ink_to_gas(&self, ink: Ink) -> Gas { + Gas(ink.0 / self.ink_price as u64) // ink_price is never 0 } } diff --git a/arbitrator/prover/src/programs/memory.rs b/arbitrator/prover/src/programs/memory.rs index 7253b59dc4..82c4d4469d 100644 --- a/arbitrator/prover/src/programs/memory.rs +++ b/arbitrator/prover/src/programs/memory.rs @@ -1,6 +1,8 @@ // Copyright 2023, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +use arbutil::evm::api::Gas; + #[derive(Clone, Copy, Debug)] #[repr(C)] pub struct MemoryModel { @@ -28,20 +30,20 @@ impl MemoryModel { } /// Determines the gas cost of allocating `new` pages given `open` are active and `ever` have ever been. - pub fn gas_cost(&self, new: u16, open: u16, ever: u16) -> u64 { + pub fn gas_cost(&self, new: u16, open: u16, ever: u16) -> Gas { let new_open = open.saturating_add(new); let new_ever = ever.max(new_open); // free until expansion beyond the first few if new_ever <= self.free_pages { - return 0; + return Gas(0); } let credit = |pages: u16| pages.saturating_sub(self.free_pages); let adding = credit(new_open).saturating_sub(credit(open)) as u64; let linear = adding.saturating_mul(self.page_gas.into()); let expand = Self::exp(new_ever) - Self::exp(ever); - linear.saturating_add(expand) + Gas(linear.saturating_add(expand)) } fn exp(pages: u16) -> u64 { @@ -81,14 +83,14 @@ fn test_model() { let model = MemoryModel::new(2, 1000); for jump in 1..=128 { - let mut total = 0; + let mut total = Gas(0); let mut pages = 0; while pages < 128 { let jump = jump.min(128 - pages); total += model.gas_cost(jump, pages, pages); pages += jump; } - assert_eq!(total, 31999998); + assert_eq!(total, Gas(31999998)); } for jump in 1..=128 { @@ -98,7 +100,7 @@ fn test_model() { let mut adds = 0; while ever < 128 { let jump = jump.min(128 - open); - total += model.gas_cost(jump, open, ever); + total += model.gas_cost(jump, open, ever).0; open += jump; ever = ever.max(open); @@ -114,12 +116,12 @@ fn test_model() { } // check saturation - assert_eq!(u64::MAX, model.gas_cost(129, 0, 0)); - assert_eq!(u64::MAX, model.gas_cost(u16::MAX, 0, 0)); + assert_eq!(Gas(u64::MAX), model.gas_cost(129, 0, 0)); + assert_eq!(Gas(u64::MAX), model.gas_cost(u16::MAX, 0, 0)); // check free pages let model = MemoryModel::new(128, 1000); - assert_eq!(0, model.gas_cost(128, 0, 0)); - assert_eq!(0, model.gas_cost(128, 0, 128)); - assert_eq!(u64::MAX, model.gas_cost(129, 0, 0)); + assert_eq!(Gas(0), model.gas_cost(128, 0, 0)); + assert_eq!(Gas(0), model.gas_cost(128, 0, 128)); + assert_eq!(Gas(u64::MAX), model.gas_cost(129, 0, 0)); } diff --git a/arbitrator/prover/src/programs/meter.rs b/arbitrator/prover/src/programs/meter.rs index ab069fd911..0d7b3151d7 100644 --- a/arbitrator/prover/src/programs/meter.rs +++ b/arbitrator/prover/src/programs/meter.rs @@ -9,7 +9,14 @@ use crate::{ value::FunctionType, Machine, }; -use arbutil::{evm, operator::OperatorInfo, Bytes32}; +use arbutil::{ + evm::{ + self, + api::{Gas, Ink}, + }, + operator::OperatorInfo, + Bytes32, +}; use derivative::Derivative; use eyre::Result; use fnv::FnvHashMap as HashMap; @@ -188,15 +195,15 @@ impl<'a, F: OpcodePricer> FuncMiddleware<'a> for FuncMeter<'a, F> { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MachineMeter { - Ready(u64), + Ready(Ink), Exhausted, } impl MachineMeter { - pub fn ink(self) -> u64 { + pub fn ink(self) -> Ink { match self { Self::Ready(ink) => ink, - Self::Exhausted => 0, + Self::Exhausted => Ink(0), } } @@ -210,8 +217,8 @@ impl MachineMeter { /// We don't implement `From` since it's unclear what 0 would map to #[allow(clippy::from_over_into)] -impl Into for MachineMeter { - fn into(self) -> u64 { +impl Into for MachineMeter { + fn into(self) -> Ink { self.ink() } } @@ -219,7 +226,7 @@ impl Into for MachineMeter { impl Display for MachineMeter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Ready(ink) => write!(f, "{ink} ink"), + Self::Ready(ink) => write!(f, "{} ink", ink.0), Self::Exhausted => write!(f, "exhausted"), } } @@ -241,7 +248,7 @@ pub trait MeteredMachine { fn ink_left(&self) -> MachineMeter; fn set_meter(&mut self, meter: MachineMeter); - fn set_ink(&mut self, ink: u64) { + fn set_ink(&mut self, ink: Ink) { self.set_meter(MachineMeter::Ready(ink)); } @@ -250,14 +257,14 @@ pub trait MeteredMachine { Err(OutOfInkError) } - fn ink_ready(&mut self) -> Result { + fn ink_ready(&mut self) -> Result { let MachineMeter::Ready(ink_left) = self.ink_left() else { return self.out_of_ink(); }; Ok(ink_left) } - fn buy_ink(&mut self, ink: u64) -> Result<(), OutOfInkError> { + fn buy_ink(&mut self, ink: Ink) -> Result<(), OutOfInkError> { let ink_left = self.ink_ready()?; if ink_left < ink { return self.out_of_ink(); @@ -267,7 +274,7 @@ pub trait MeteredMachine { } /// Checks if the user has enough ink, but doesn't burn any - fn require_ink(&mut self, ink: u64) -> Result<(), OutOfInkError> { + fn require_ink(&mut self, ink: Ink) -> Result<(), OutOfInkError> { let ink_left = self.ink_ready()?; if ink_left < ink { return self.out_of_ink(); @@ -277,18 +284,18 @@ pub trait MeteredMachine { /// Pays for a write into the client. fn pay_for_write(&mut self, bytes: u32) -> Result<(), OutOfInkError> { - self.buy_ink(sat_add_mul(5040, 30, bytes.saturating_sub(32))) + self.buy_ink(Ink(sat_add_mul(5040, 30, bytes.saturating_sub(32)))) } /// Pays for a read into the host. fn pay_for_read(&mut self, bytes: u32) -> Result<(), OutOfInkError> { - self.buy_ink(sat_add_mul(16381, 55, bytes.saturating_sub(32))) + self.buy_ink(Ink(sat_add_mul(16381, 55, bytes.saturating_sub(32)))) } /// Pays for both I/O and keccak. fn pay_for_keccak(&mut self, bytes: u32) -> Result<(), OutOfInkError> { let words = evm::evm_words(bytes).saturating_sub(2); - self.buy_ink(sat_add_mul(121800, 21000, words)) + self.buy_ink(Ink(sat_add_mul(121800, 21000, words))) } /// Pays for copying bytes from geth. @@ -305,14 +312,14 @@ pub trait MeteredMachine { false => break, } } - self.buy_ink(3000 + exp * 17500) + self.buy_ink(Ink(3000 + exp * 17500)) } } pub trait GasMeteredMachine: MeteredMachine { fn pricing(&self) -> PricingParams; - fn gas_left(&self) -> Result { + fn gas_left(&self) -> Result { let pricing = self.pricing(); match self.ink_left() { MachineMeter::Ready(ink) => Ok(pricing.ink_to_gas(ink)), @@ -320,13 +327,13 @@ pub trait GasMeteredMachine: MeteredMachine { } } - fn buy_gas(&mut self, gas: u64) -> Result<(), OutOfInkError> { + fn buy_gas(&mut self, gas: Gas) -> Result<(), OutOfInkError> { let pricing = self.pricing(); self.buy_ink(pricing.gas_to_ink(gas)) } /// Checks if the user has enough gas, but doesn't burn any - fn require_gas(&mut self, gas: u64) -> Result<(), OutOfInkError> { + fn require_gas(&mut self, gas: Gas) -> Result<(), OutOfInkError> { let pricing = self.pricing(); self.require_ink(pricing.gas_to_ink(gas)) } @@ -350,7 +357,7 @@ impl MeteredMachine for Machine { }}; } - let ink = || convert!(self.get_global(STYLUS_INK_LEFT)); + let ink = || Ink(convert!(self.get_global(STYLUS_INK_LEFT))); let status: u32 = convert!(self.get_global(STYLUS_INK_STATUS)); match status { @@ -362,7 +369,7 @@ impl MeteredMachine for Machine { fn set_meter(&mut self, meter: MachineMeter) { let ink = meter.ink(); let status = meter.status(); - self.set_global(STYLUS_INK_LEFT, ink.into()).unwrap(); + self.set_global(STYLUS_INK_LEFT, ink.0.into()).unwrap(); self.set_global(STYLUS_INK_STATUS, status.into()).unwrap(); } } diff --git a/arbitrator/prover/src/programs/mod.rs b/arbitrator/prover/src/programs/mod.rs index a5df2e31a8..a35308e7ff 100644 --- a/arbitrator/prover/src/programs/mod.rs +++ b/arbitrator/prover/src/programs/mod.rs @@ -8,7 +8,7 @@ use crate::{ programs::config::CompileConfig, value::{FunctionType as ArbFunctionType, Value}, }; -use arbutil::{math::SaturatingSum, Bytes32, Color}; +use arbutil::{evm::ARBOS_VERSION_STYLUS_CHARGING_FIXES, math::SaturatingSum, Bytes32, Color}; use eyre::{bail, eyre, Report, Result, WrapErr}; use fnv::FnvHashMap as HashMap; use std::fmt::Debug; @@ -418,58 +418,64 @@ impl Module { pub fn activate( wasm: &[u8], codehash: &Bytes32, - version: u16, + stylus_version: u16, + arbos_version_for_gas: u64, // must only be used for activation gas page_limit: u16, debug: bool, gas: &mut u64, ) -> Result<(Self, StylusData)> { - // converts a number of microseconds to gas - // TODO: collapse to a single value after finalizing factors - let us_to_gas = |us: u64| { - let fudge = 2; - let sync_rate = 1_000_000 / 2; - let speed = 7_000_000; - us.saturating_mul(fudge * speed) / sync_rate - }; - - macro_rules! pay { - ($us:expr) => { - let amount = us_to_gas($us); - if *gas < amount { - *gas = 0; - bail!("out of gas"); - } - *gas -= amount; + let compile = CompileConfig::version(stylus_version, debug); + let (bin, stylus_data) = + WasmBinary::parse_user(wasm, arbos_version_for_gas, page_limit, &compile, codehash) + .wrap_err("failed to parse wasm")?; + + if arbos_version_for_gas > 0 { + // converts a number of microseconds to gas + // TODO: collapse to a single value after finalizing factors + let us_to_gas = |us: u64| { + let fudge = 2; + let sync_rate = 1_000_000 / 2; + let speed = 7_000_000; + us.saturating_mul(fudge * speed) / sync_rate }; - } - - // pay for wasm - let wasm_len = wasm.len() as u64; - pay!(wasm_len.saturating_mul(31_733 / 100_000)); - - let compile = CompileConfig::version(version, debug); - let (bin, stylus_data) = WasmBinary::parse_user(wasm, page_limit, &compile, codehash) - .wrap_err("failed to parse wasm")?; - // pay for funcs - let funcs = bin.functions.len() as u64; - pay!(funcs.saturating_mul(17_263) / 100_000); - - // pay for data - let data = bin.datas.iter().map(|x| x.data.len()).saturating_sum() as u64; - pay!(data.saturating_mul(17_376) / 100_000); - - // pay for elements - let elems = bin.elements.iter().map(|x| x.range.len()).saturating_sum() as u64; - pay!(elems.saturating_mul(17_376) / 100_000); - - // pay for memory - let mem = bin.memories.first().map(|x| x.initial).unwrap_or_default(); - pay!(mem.saturating_mul(2217)); - - // pay for code - let code = bin.codes.iter().map(|x| x.expr.len()).saturating_sum() as u64; - pay!(code.saturating_mul(535) / 1_000); + macro_rules! pay { + ($us:expr) => { + let amount = us_to_gas($us); + if *gas < amount { + *gas = 0; + bail!("out of gas"); + } + *gas -= amount; + }; + } + + // pay for wasm + if arbos_version_for_gas >= ARBOS_VERSION_STYLUS_CHARGING_FIXES { + let wasm_len = wasm.len() as u64; + pay!(wasm_len.saturating_mul(31_733) / 100_000); + } + + // pay for funcs + let funcs = bin.functions.len() as u64; + pay!(funcs.saturating_mul(17_263) / 100_000); + + // pay for data + let data = bin.datas.iter().map(|x| x.data.len()).saturating_sum() as u64; + pay!(data.saturating_mul(17_376) / 100_000); + + // pay for elements + let elems = bin.elements.iter().map(|x| x.range.len()).saturating_sum() as u64; + pay!(elems.saturating_mul(17_376) / 100_000); + + // pay for memory + let mem = bin.memories.first().map(|x| x.initial).unwrap_or_default(); + pay!(mem.saturating_mul(2217)); + + // pay for code + let code = bin.codes.iter().map(|x| x.expr.len()).saturating_sum() as u64; + pay!(code.saturating_mul(535) / 1_000); + } let module = Self::from_user_binary(&bin, compile.debug.debug_funcs, Some(stylus_data)) .wrap_err("failed to build user module")?; diff --git a/arbitrator/prover/src/test.rs b/arbitrator/prover/src/test.rs index 2f32aff012..4fd739342e 100644 --- a/arbitrator/prover/src/test.rs +++ b/arbitrator/prover/src/test.rs @@ -1,8 +1,6 @@ // Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -#![cfg(test)] - use crate::binary; use brotli::Dictionary; use eyre::Result; @@ -57,14 +55,14 @@ pub fn reject_ambiguous_imports() { #[test] pub fn test_compress() -> Result<()> { - let data = include_bytes!(concat!(env!("OUT_DIR"), "/forwarder.wasm")); + let data = include_bytes!("../../../target/machines/latest/forward_stub.wasm"); let mut last = vec![]; for dict in [Dictionary::Empty, Dictionary::StylusProgram] { let deflate = brotli::compress(data, 11, 22, dict).unwrap(); let inflate = brotli::decompress(&deflate, dict).unwrap(); assert_eq!(hex::encode(inflate), hex::encode(data)); - assert!(&deflate != &last); + assert!(deflate != last); last = deflate; } Ok(()) diff --git a/arbitrator/prover/src/value.rs b/arbitrator/prover/src/value.rs index f2f54404f0..6afffdf7a0 100644 --- a/arbitrator/prover/src/value.rs +++ b/arbitrator/prover/src/value.rs @@ -2,7 +2,7 @@ // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::binary::FloatType; -use arbutil::{hostios::ParamType, Bytes32, Color}; +use arbutil::{Bytes32, Color}; use digest::Digest; use eyre::{bail, ErrReport, Result}; use serde::{Deserialize, Serialize}; @@ -77,16 +77,6 @@ impl From for ValType { } } -impl From<&ParamType> for ArbValueType { - fn from(ty: &ParamType) -> Self { - use ParamType as V; - match ty { - V::I32 => Self::I32, - V::I64 => Self::I64, - } - } -} - #[cfg(feature = "native")] pub fn parser_type(ty: &wasmer::Type) -> wasmer::wasmparser::ValType { match ty { diff --git a/arbitrator/prover/test-cases/forward-test.wat b/arbitrator/prover/test-cases/forward-test.wat new file mode 100644 index 0000000000..b9beff0d82 --- /dev/null +++ b/arbitrator/prover/test-cases/forward-test.wat @@ -0,0 +1,32 @@ + +(module + (import "forward" "add" (func $add (param i32 i32) (result i32))) + (import "forward" "sub" (func $sub (param i32 i32) (result i32))) + (import "forward" "mul" (func $mul (param i32 i32) (result i32))) + (func $start + ;; this address will update each time a forwarded call is made + i32.const 0xa4b + i32.const 805 + i32.store + + i32.const 11 + i32.const 5 + call $sub + + i32.const 3 + i32.const -2 + call $mul + + call $add + (if + (then (unreachable))) + + ;; check that the address has changed + i32.const 0xa4b + i32.load + i32.const 808 + i32.ne + (if + (then (unreachable)))) + (start $start) + (memory 1)) diff --git a/arbitrator/prover/test-cases/forward/forward.wat b/arbitrator/prover/test-cases/forward/forward.wat new file mode 100644 index 0000000000..ff55953e62 --- /dev/null +++ b/arbitrator/prover/test-cases/forward/forward.wat @@ -0,0 +1,8 @@ + +(module + (import "target" "arbitrator_forward__add" (func $add (param i32 i32) (result i32))) + (import "target" "arbitrator_forward__sub" (func $sub (param i32 i32) (result i32))) + (import "target" "arbitrator_forward__mul" (func $mul (param i32 i32) (result i32))) + (export "forward__add" (func $add)) + (export "forward__sub" (func $sub)) + (export "forward__mul" (func $mul))) diff --git a/arbitrator/prover/test-cases/forward/target.wat b/arbitrator/prover/test-cases/forward/target.wat new file mode 100644 index 0000000000..0779eb753d --- /dev/null +++ b/arbitrator/prover/test-cases/forward/target.wat @@ -0,0 +1,27 @@ + +(module + (import "env" "wavm_caller_load8" (func $load (param i32) (result i32))) + (import "env" "wavm_caller_store8" (func $store (param i32 i32))) + (func (export "target__add") (param i32 i32) (result i32) + call $write_caller + local.get 0 + local.get 1 + i32.add) + (func (export "target__sub") (param i32 i32) (result i32) + call $write_caller + local.get 0 + local.get 1 + i32.sub) + (func (export "target__mul") (param i32 i32) (result i32) + call $write_caller + local.get 0 + local.get 1 + i32.mul) + (func $write_caller (export "write_caller") + ;; increment the value at address 0xa4b in the caller + i32.const 0xa4b + i32.const 0xa4b + call $load + i32.const 1 + i32.add + call $store)) diff --git a/arbitrator/stylus/Cargo.toml b/arbitrator/stylus/Cargo.toml index 4717bd631a..ea1d878ea0 100644 --- a/arbitrator/stylus/Cargo.toml +++ b/arbitrator/stylus/Cargo.toml @@ -21,11 +21,11 @@ thiserror = "1.0.33" bincode = "1.3.3" lazy_static.workspace = true libc = "0.2.108" -lru.workspace = true eyre = "0.6.5" rand = "0.8.5" fnv = "1.0.7" hex = "0.4.3" +clru = "0.6.2" [dev-dependencies] num-bigint = "0.4.4" diff --git a/arbitrator/stylus/src/cache.rs b/arbitrator/stylus/src/cache.rs index fa38d45419..9b788a45db 100644 --- a/arbitrator/stylus/src/cache.rs +++ b/arbitrator/stylus/src/cache.rs @@ -2,18 +2,19 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use arbutil::Bytes32; +use clru::{CLruCache, CLruCacheConfig, WeightScale}; use eyre::Result; use lazy_static::lazy_static; -use lru::LruCache; use parking_lot::Mutex; use prover::programs::config::CompileConfig; +use std::hash::RandomState; use std::{collections::HashMap, num::NonZeroUsize}; use wasmer::{Engine, Module, Store}; use crate::target_cache::target_native; lazy_static! { - static ref INIT_CACHE: Mutex = Mutex::new(InitCache::new(256)); + static ref INIT_CACHE: Mutex = Mutex::new(InitCache::new(256 * 1024 * 1024)); } macro_rules! cache { @@ -22,9 +23,24 @@ macro_rules! cache { }; } +pub struct LruCounters { + pub hits: u32, + pub misses: u32, + pub does_not_fit: u32, +} + +pub struct LongTermCounters { + pub hits: u32, + pub misses: u32, +} + pub struct InitCache { long_term: HashMap, - lru: LruCache, + long_term_size_bytes: usize, + long_term_counters: LongTermCounters, + + lru: CLruCache, + lru_counters: LruCounters, } #[derive(Clone, Copy, Hash, PartialEq, Eq)] @@ -48,11 +64,16 @@ impl CacheKey { struct CacheItem { module: Module, engine: Engine, + entry_size_estimate_bytes: usize, } impl CacheItem { - fn new(module: Module, engine: Engine) -> Self { - Self { module, engine } + fn new(module: Module, engine: Engine, entry_size_estimate_bytes: usize) -> Self { + Self { + module, + engine, + entry_size_estimate_bytes, + } } fn data(&self) -> (Module, Store) { @@ -60,39 +81,121 @@ impl CacheItem { } } +struct CustomWeightScale; +impl WeightScale for CustomWeightScale { + fn weight(&self, _key: &CacheKey, val: &CacheItem) -> usize { + // clru defines that each entry consumes (weight + 1) of the cache capacity. + // We subtract 1 since we only want to use the weight as the size of the entry. + val.entry_size_estimate_bytes.saturating_sub(1) + } +} + +#[repr(C)] +pub struct LruCacheMetrics { + pub size_bytes: u64, + pub count: u32, + pub hits: u32, + pub misses: u32, + pub does_not_fit: u32, +} + +#[repr(C)] +pub struct LongTermCacheMetrics { + pub size_bytes: u64, + pub count: u32, + pub hits: u32, + pub misses: u32, +} + +#[repr(C)] +pub struct CacheMetrics { + pub lru: LruCacheMetrics, + pub long_term: LongTermCacheMetrics, +} + +pub fn deserialize_module( + module: &[u8], + version: u16, + debug: bool, +) -> Result<(Module, Engine, usize)> { + let engine = CompileConfig::version(version, debug).engine(target_native()); + let module = unsafe { Module::deserialize_unchecked(&engine, module)? }; + + let asm_size_estimate_bytes = module.serialize()?.len(); + // add 128 bytes for the cache item overhead + let entry_size_estimate_bytes = asm_size_estimate_bytes + 128; + + Ok((module, engine, entry_size_estimate_bytes)) +} + impl InitCache { // current implementation only has one tag that stores to the long_term // future implementations might have more, but 0 is a reserved tag // that will never modify long_term state const ARBOS_TAG: u32 = 1; - fn new(size: usize) -> Self { + const DOES_NOT_FIT_MSG: &'static str = "Failed to insert into LRU cache, item too large"; + + fn new(size_bytes: usize) -> Self { Self { long_term: HashMap::new(), - lru: LruCache::new(NonZeroUsize::new(size).unwrap()), + long_term_size_bytes: 0, + long_term_counters: LongTermCounters { hits: 0, misses: 0 }, + + lru: CLruCache::with_config( + CLruCacheConfig::new(NonZeroUsize::new(size_bytes).unwrap()) + .with_scale(CustomWeightScale), + ), + lru_counters: LruCounters { + hits: 0, + misses: 0, + does_not_fit: 0, + }, } } - pub fn set_lru_size(size: u32) { + pub fn set_lru_capacity(capacity_bytes: u64) { cache!() .lru - .resize(NonZeroUsize::new(size.try_into().unwrap()).unwrap()) + .resize(NonZeroUsize::new(capacity_bytes.try_into().unwrap()).unwrap()) } /// Retrieves a cached value, updating items as necessary. - pub fn get(module_hash: Bytes32, version: u16, debug: bool) -> Option<(Module, Store)> { - let mut cache = cache!(); + /// If long_term_tag is 1 and the item is only in LRU will insert to long term cache. + pub fn get( + module_hash: Bytes32, + version: u16, + long_term_tag: u32, + debug: bool, + ) -> Option<(Module, Store)> { let key = CacheKey::new(module_hash, version, debug); + let mut cache = cache!(); // See if the item is in the long term cache if let Some(item) = cache.long_term.get(&key) { - return Some(item.data()); + let data = item.data(); + cache.long_term_counters.hits += 1; + return Some(data); + } + if long_term_tag == Self::ARBOS_TAG { + // only count misses only when we can expect to find the item in long term cache + cache.long_term_counters.misses += 1; } // See if the item is in the LRU cache, promoting if so - if let Some(item) = cache.lru.get(&key) { - return Some(item.data()); + if let Some(item) = cache.lru.peek(&key).cloned() { + cache.lru_counters.hits += 1; + if long_term_tag == Self::ARBOS_TAG { + cache.long_term_size_bytes += item.entry_size_estimate_bytes; + cache.long_term.insert(key, item.clone()); + } else { + // only calls get to move the key to the head of the LRU list + cache.lru.get(&key); + } + return Some((item.module, Store::new(item.engine))); } + cache.lru_counters.misses += 1; + None } @@ -115,23 +218,29 @@ impl InitCache { if let Some(item) = cache.lru.peek(&key).cloned() { if long_term_tag == Self::ARBOS_TAG { cache.long_term.insert(key, item.clone()); + cache.long_term_size_bytes += item.entry_size_estimate_bytes; } else { - cache.lru.promote(&key) + // only calls get to move the key to the head of the LRU list + cache.lru.get(&key); } return Ok(item.data()); } drop(cache); - let engine = CompileConfig::version(version, debug).engine(target_native()); - let module = unsafe { Module::deserialize_unchecked(&engine, module)? }; + let (module, engine, entry_size_estimate_bytes) = + deserialize_module(module, version, debug)?; - let item = CacheItem::new(module, engine); + let item = CacheItem::new(module, engine, entry_size_estimate_bytes); let data = item.data(); let mut cache = cache!(); if long_term_tag != Self::ARBOS_TAG { - cache.lru.put(key, item); + if cache.lru.put_with_weight(key, item).is_err() { + cache.lru_counters.does_not_fit += 1; + eprintln!("{}", Self::DOES_NOT_FIT_MSG); + }; } else { cache.long_term.insert(key, item); + cache.long_term_size_bytes += entry_size_estimate_bytes; } Ok(data) } @@ -144,7 +253,10 @@ impl InitCache { let key = CacheKey::new(module_hash, version, debug); let mut cache = cache!(); if let Some(item) = cache.long_term.remove(&key) { - cache.lru.put(key, item); + cache.long_term_size_bytes -= item.entry_size_estimate_bytes; + if cache.lru.put_with_weight(key, item).is_err() { + eprintln!("{}", Self::DOES_NOT_FIT_MSG); + } } } @@ -155,7 +267,49 @@ impl InitCache { let mut cache = cache!(); let cache = &mut *cache; for (key, item) in cache.long_term.drain() { - cache.lru.put(key, item); // not all will fit, just a heuristic + // not all will fit, just a heuristic + if cache.lru.put_with_weight(key, item).is_err() { + eprintln!("{}", Self::DOES_NOT_FIT_MSG); + } } + cache.long_term_size_bytes = 0; + } + + pub fn get_metrics(output: &mut CacheMetrics) { + let mut cache = cache!(); + + let lru_count = cache.lru.len(); + // adds 1 to each entry to account that we subtracted 1 in the weight calculation + output.lru.size_bytes = (cache.lru.weight() + lru_count).try_into().unwrap(); + output.lru.count = lru_count.try_into().unwrap(); + output.lru.hits = cache.lru_counters.hits; + output.lru.misses = cache.lru_counters.misses; + output.lru.does_not_fit = cache.lru_counters.does_not_fit; + + output.long_term.size_bytes = cache.long_term_size_bytes.try_into().unwrap(); + output.long_term.count = cache.long_term.len().try_into().unwrap(); + output.long_term.hits = cache.long_term_counters.hits; + output.long_term.misses = cache.long_term_counters.misses; + + // Empty counters. + // go side, which is the only consumer of this function besides tests, + // will read those counters and increment its own prometheus counters with them. + cache.lru_counters = LruCounters { + hits: 0, + misses: 0, + does_not_fit: 0, + }; + cache.long_term_counters = LongTermCounters { hits: 0, misses: 0 }; + } + + // only used for testing + pub fn clear_lru_cache() { + let mut cache = cache!(); + cache.lru.clear(); + cache.lru_counters = LruCounters { + hits: 0, + misses: 0, + does_not_fit: 0, + }; } } diff --git a/arbitrator/stylus/src/env.rs b/arbitrator/stylus/src/env.rs index 69d542070d..a153fb5bf1 100644 --- a/arbitrator/stylus/src/env.rs +++ b/arbitrator/stylus/src/env.rs @@ -3,7 +3,7 @@ use arbutil::{ evm::{ - api::{DataReader, EvmApi}, + api::{DataReader, EvmApi, Ink}, EvmData, }, pricing, @@ -74,7 +74,7 @@ impl> WasmEnv { pub fn start<'a>( env: &'a mut WasmEnvMut<'_, D, E>, - ink: u64, + ink: Ink, ) -> Result, Escape> { let mut info = Self::program(env)?; info.buy_ink(pricing::HOSTIO_INK + ink)?; @@ -88,7 +88,7 @@ impl> WasmEnv { env, memory, store, - start_ink: 0, + start_ink: Ink(0), }; if info.env.evm_data.tracing { info.start_ink = info.ink_ready()?; @@ -114,16 +114,16 @@ pub struct MeterData { } impl MeterData { - pub fn ink(&self) -> u64 { - unsafe { self.ink_left.as_ref().val.u64 } + pub fn ink(&self) -> Ink { + Ink(unsafe { self.ink_left.as_ref().val.u64 }) } pub fn status(&self) -> u32 { unsafe { self.ink_status.as_ref().val.u32 } } - pub fn set_ink(&mut self, ink: u64) { - unsafe { self.ink_left.as_mut().val = RawValue { u64: ink } } + pub fn set_ink(&mut self, ink: Ink) { + unsafe { self.ink_left.as_mut().val = RawValue { u64: ink.0 } } } pub fn set_status(&mut self, status: u32) { @@ -140,7 +140,7 @@ pub struct HostioInfo<'a, D: DataReader, E: EvmApi> { pub env: &'a mut WasmEnv, pub memory: Memory, pub store: StoreMut<'a>, - pub start_ink: u64, + pub start_ink: Ink, } impl<'a, D: DataReader, E: EvmApi> HostioInfo<'a, D, E> { diff --git a/arbitrator/stylus/src/evm_api.rs b/arbitrator/stylus/src/evm_api.rs index d267372827..0dd27e3f8c 100644 --- a/arbitrator/stylus/src/evm_api.rs +++ b/arbitrator/stylus/src/evm_api.rs @@ -3,7 +3,7 @@ use crate::{GoSliceData, RustSlice}; use arbutil::evm::{ - api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET}, + api::{EvmApiMethod, Gas, EVM_API_METHOD_REQ_OFFSET}, req::RequestHandler, }; @@ -31,7 +31,7 @@ impl RequestHandler for NativeRequestHandler { &mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>, - ) -> (Vec, GoSliceData, u64) { + ) -> (Vec, GoSliceData, Gas) { let mut result = GoSliceData::null(); let mut raw_data = GoSliceData::null(); let mut cost = 0; @@ -45,6 +45,6 @@ impl RequestHandler for NativeRequestHandler { ptr!(raw_data), ) }; - (result.slice().to_vec(), raw_data, cost) + (result.slice().to_vec(), raw_data, Gas(cost)) } } diff --git a/arbitrator/stylus/src/host.rs b/arbitrator/stylus/src/host.rs index fbe5657c5f..c72cafc316 100644 --- a/arbitrator/stylus/src/host.rs +++ b/arbitrator/stylus/src/host.rs @@ -6,13 +6,14 @@ use crate::env::{Escape, HostioInfo, MaybeEscape, WasmEnv, WasmEnvMut}; use arbutil::{ evm::{ - api::{DataReader, EvmApi}, + api::{DataReader, EvmApi, Gas, Ink}, EvmData, }, Color, }; use caller_env::GuestPtr; use eyre::Result; +use prover::value::Value; use std::{ fmt::Display, mem::{self, MaybeUninit}, @@ -81,7 +82,7 @@ where println!("{} {text}", "Stylus says:".yellow()); } - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64) { + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: Ink) { let start_ink = self.start_ink; self.evm_api .capture_hostio(name, args, outs, start_ink, end_ink); @@ -167,7 +168,7 @@ pub(crate) fn call_contract>( ) -> Result { hostio!( env, - call_contract(contract, data, data_len, value, gas, ret_len) + call_contract(contract, data, data_len, value, Gas(gas), ret_len) ) } @@ -181,7 +182,7 @@ pub(crate) fn delegate_call_contract>( ) -> Result { hostio!( env, - delegate_call_contract(contract, data, data_len, gas, ret_len) + delegate_call_contract(contract, data, data_len, Gas(gas), ret_len) ) } @@ -195,7 +196,7 @@ pub(crate) fn static_call_contract>( ) -> Result { hostio!( env, - static_call_contract(contract, data, data_len, gas, ret_len) + static_call_contract(contract, data, data_len, Gas(gas), ret_len) ) } @@ -333,13 +334,13 @@ pub(crate) fn contract_address>( pub(crate) fn evm_gas_left>( mut env: WasmEnvMut, ) -> Result { - hostio!(env, evm_gas_left()) + hostio!(env, evm_gas_left()).map(|g| g.0) } pub(crate) fn evm_ink_left>( mut env: WasmEnvMut, ) -> Result { - hostio!(env, evm_ink_left()) + hostio!(env, evm_ink_left()).map(|i| i.0) } pub(crate) fn math_div>( @@ -440,76 +441,26 @@ pub(crate) fn pay_for_memory_grow>( hostio!(env, pay_for_memory_grow(pages)) } -pub(crate) mod console { - use super::*; - - pub(crate) fn log_txt>( - mut env: WasmEnvMut, - ptr: GuestPtr, - len: u32, - ) -> MaybeEscape { - hostio!(env, console_log_text(ptr, len)) - } - - pub(crate) fn log_i32>( - mut env: WasmEnvMut, - value: u32, - ) -> MaybeEscape { - hostio!(env, console_log(value)) - } - - pub(crate) fn tee_i32>( - mut env: WasmEnvMut, - value: u32, - ) -> Result { - hostio!(env, console_tee(value)) - } - - pub(crate) fn log_i64>( - mut env: WasmEnvMut, - value: u64, - ) -> MaybeEscape { - hostio!(env, console_log(value)) - } - - pub(crate) fn tee_i64>( - mut env: WasmEnvMut, - value: u64, - ) -> Result { - hostio!(env, console_tee(value)) - } - - pub(crate) fn log_f32>( - mut env: WasmEnvMut, - value: f32, - ) -> MaybeEscape { - hostio!(env, console_log(value)) - } - - pub(crate) fn tee_f32>( - mut env: WasmEnvMut, - value: f32, - ) -> Result { - hostio!(env, console_tee(value)) - } - - pub(crate) fn log_f64>( - mut env: WasmEnvMut, - value: f64, - ) -> MaybeEscape { - hostio!(env, console_log(value)) - } - - pub(crate) fn tee_f64>( - mut env: WasmEnvMut, - value: f64, - ) -> Result { - hostio!(env, console_tee(value)) - } +pub(crate) fn console_log_text>( + mut env: WasmEnvMut, + ptr: GuestPtr, + len: u32, +) -> MaybeEscape { + hostio!(env, console_log_text(ptr, len)) } -pub(crate) mod debug { - use super::*; +pub(crate) fn console_log, T: Into>( + mut env: WasmEnvMut, + value: T, +) -> MaybeEscape { + hostio!(env, console_log(value)) +} - pub(crate) fn null_host>(_: WasmEnvMut) {} +pub(crate) fn console_tee, T: Into + Copy>( + mut env: WasmEnvMut, + value: T, +) -> Result { + hostio!(env, console_tee(value)) } + +pub(crate) fn null_host>(_: WasmEnvMut) {} diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index a252b60a01..e7f10c2400 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -3,7 +3,7 @@ use arbutil::{ evm::{ - api::DataReader, + api::{DataReader, Gas, Ink}, req::EvmApiRequestor, user::{UserOutcome, UserOutcomeKind}, EvmData, @@ -11,7 +11,7 @@ use arbutil::{ format::DebugBytes, Bytes32, }; -use cache::InitCache; +use cache::{deserialize_module, CacheMetrics, InitCache}; use evm_api::NativeRequestHandler; use eyre::ErrReport; use native::NativeInstance; @@ -139,7 +139,8 @@ impl RustBytes { pub unsafe extern "C" fn stylus_activate( wasm: GoSliceData, page_limit: u16, - version: u16, + stylus_version: u16, + arbos_version_for_gas: u64, debug: bool, output: *mut RustBytes, codehash: *const Bytes32, @@ -153,7 +154,15 @@ pub unsafe extern "C" fn stylus_activate( let codehash = &*codehash; let gas = &mut *gas; - let (module, info) = match native::activate(wasm, codehash, version, page_limit, debug, gas) { + let (module, info) = match native::activate( + wasm, + codehash, + stylus_version, + arbos_version_for_gas, + page_limit, + debug, + gas, + ) { Ok(val) => val, Err(err) => return output.write_err(err), }; @@ -270,7 +279,7 @@ pub unsafe extern "C" fn stylus_call( let evm_api = EvmApiRequestor::new(req_handler); let pricing = config.pricing; let output = &mut *output; - let ink = pricing.gas_to_ink(*gas); + let ink = pricing.gas_to_ink(Gas(*gas)); // Safety: module came from compile_user_wasm and we've paid for memory expansion let instance = unsafe { @@ -293,17 +302,17 @@ pub unsafe extern "C" fn stylus_call( Ok(outcome) => output.write_outcome(outcome), }; let ink_left = match status { - UserOutcomeKind::OutOfStack => 0, // take all gas when out of stack + UserOutcomeKind::OutOfStack => Ink(0), // take all gas when out of stack _ => instance.ink_left().into(), }; - *gas = pricing.ink_to_gas(ink_left); + *gas = pricing.ink_to_gas(ink_left).0; status } -/// resize lru +/// set lru cache capacity #[no_mangle] -pub extern "C" fn stylus_cache_lru_resize(size: u32) { - InitCache::set_lru_size(size); +pub extern "C" fn stylus_set_cache_lru_capacity(capacity_bytes: u64) { + InitCache::set_lru_capacity(capacity_bytes); } /// Caches an activated user program. @@ -354,3 +363,42 @@ pub unsafe extern "C" fn stylus_drop_vec(vec: RustBytes) { mem::drop(vec.into_vec()) } } + +/// Gets cache metrics. +/// +/// # Safety +/// +/// `output` must not be null. +#[no_mangle] +pub unsafe extern "C" fn stylus_get_cache_metrics(output: *mut CacheMetrics) { + let output = &mut *output; + InitCache::get_metrics(output); +} + +/// Clears lru cache. +/// Only used for testing purposes. +#[no_mangle] +pub extern "C" fn stylus_clear_lru_cache() { + InitCache::clear_lru_cache() +} + +/// Clears long term cache (for arbos_tag = 1) +/// Only used for testing purposes. +#[no_mangle] +pub extern "C" fn stylus_clear_long_term_cache() { + InitCache::clear_long_term(1); +} + +/// Gets entry size in bytes. +/// Only used for testing purposes. +#[no_mangle] +pub extern "C" fn stylus_get_entry_size_estimate_bytes( + module: GoSliceData, + version: u16, + debug: bool, +) -> u64 { + match deserialize_module(module.slice(), version, debug) { + Err(error) => panic!("tried to get invalid asm!: {error}"), + Ok((_, _, entry_size_estimate_bytes)) => entry_size_estimate_bytes.try_into().unwrap(), + } +} diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 7a82314fbc..0fbdb342f3 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -8,7 +8,7 @@ use crate::{ }; use arbutil::{ evm::{ - api::{DataReader, EvmApi}, + api::{DataReader, EvmApi, Ink}, EvmData, }, operator::OperatorCode, @@ -33,7 +33,7 @@ use std::{ ops::{Deref, DerefMut}, }; use wasmer::{ - AsStoreMut, Function, FunctionEnv, Imports, Instance, Memory, Module, Pages, Store, Target, + imports, AsStoreMut, Function, FunctionEnv, Instance, Memory, Module, Pages, Store, Target, TypedFunction, Value, WasmTypeList, }; use wasmer_vm::VMExtern; @@ -121,13 +121,12 @@ impl> NativeInstance { let compile = CompileConfig::version(version, debug); let env = WasmEnv::new(compile, None, evm, evm_data); let module_hash = env.evm_data.module_hash; - - if let Some((module, store)) = InitCache::get(module_hash, version, debug) { - return Self::from_module(module, store, env); - } if !env.evm_data.cached { long_term_tag = 0; } + if let Some((module, store)) = InitCache::get(module_hash, version, long_term_tag, debug) { + return Self::from_module(module, store, env); + } let (module, store) = InitCache::insert(module_hash, module, version, long_term_tag, debug)?; Self::from_module(module, store, env) @@ -151,58 +150,68 @@ impl> NativeInstance { fn from_module(module: Module, mut store: Store, env: WasmEnv) -> Result { let debug_funcs = env.compile.debug.debug_funcs; let func_env = FunctionEnv::new(&mut store, env); - let mut imports = Imports::new(); macro_rules! func { - ($rust_mod:path, $func:ident) => {{ - use $rust_mod as rust_mod; - Function::new_typed_with_env(&mut store, &func_env, rust_mod::$func) - }}; - } - macro_rules! define_imports { - ($($wasm_mod:literal => $rust_mod:path { $( $import:ident ),* $(,)? },)* $(,)?) => { - $( - $( - define_imports!(@@imports $wasm_mod, func!($rust_mod, $import), $import, "arbitrator_forward__"); - )* - )* - }; - (@@imports $wasm_mod:literal, $func:expr, $import:ident, $($p:expr),*) => { - define_imports!(@imports $wasm_mod, $func, $import, $($p),*, ""); - }; - (@imports $wasm_mod:literal, $func:expr, $import:ident, $($p:expr),*) => { - $( - imports.define($wasm_mod, concat!($p, stringify!($import)), $func); - )* + ($func:expr) => { + Function::new_typed_with_env(&mut store, &func_env, $func) }; } - define_imports!( - "vm_hooks" => host { - read_args, write_result, exit_early, - storage_load_bytes32, storage_cache_bytes32, storage_flush_cache, transient_load_bytes32, transient_store_bytes32, - call_contract, delegate_call_contract, static_call_contract, create1, create2, read_return_data, return_data_size, - emit_log, - account_balance, account_code, account_codehash, account_code_size, - evm_gas_left, evm_ink_left, - block_basefee, chainid, block_coinbase, block_gas_limit, block_number, block_timestamp, - contract_address, - math_div, math_mod, math_pow, math_add_mod, math_mul_mod, - msg_reentrant, msg_sender, msg_value, - tx_gas_price, tx_ink_price, tx_origin, - pay_for_memory_grow, - native_keccak256, + let mut imports = imports! { + "vm_hooks" => { + "read_args" => func!(host::read_args), + "write_result" => func!(host::write_result), + "exit_early" => func!(host::exit_early), + "storage_load_bytes32" => func!(host::storage_load_bytes32), + "storage_cache_bytes32" => func!(host::storage_cache_bytes32), + "storage_flush_cache" => func!(host::storage_flush_cache), + "transient_load_bytes32" => func!(host::transient_load_bytes32), + "transient_store_bytes32" => func!(host::transient_store_bytes32), + "call_contract" => func!(host::call_contract), + "delegate_call_contract" => func!(host::delegate_call_contract), + "static_call_contract" => func!(host::static_call_contract), + "create1" => func!(host::create1), + "create2" => func!(host::create2), + "read_return_data" => func!(host::read_return_data), + "return_data_size" => func!(host::return_data_size), + "emit_log" => func!(host::emit_log), + "account_balance" => func!(host::account_balance), + "account_code" => func!(host::account_code), + "account_codehash" => func!(host::account_codehash), + "account_code_size" => func!(host::account_code_size), + "evm_gas_left" => func!(host::evm_gas_left), + "evm_ink_left" => func!(host::evm_ink_left), + "block_basefee" => func!(host::block_basefee), + "chainid" => func!(host::chainid), + "block_coinbase" => func!(host::block_coinbase), + "block_gas_limit" => func!(host::block_gas_limit), + "block_number" => func!(host::block_number), + "block_timestamp" => func!(host::block_timestamp), + "contract_address" => func!(host::contract_address), + "math_div" => func!(host::math_div), + "math_mod" => func!(host::math_mod), + "math_pow" => func!(host::math_pow), + "math_add_mod" => func!(host::math_add_mod), + "math_mul_mod" => func!(host::math_mul_mod), + "msg_reentrant" => func!(host::msg_reentrant), + "msg_sender" => func!(host::msg_sender), + "msg_value" => func!(host::msg_value), + "tx_gas_price" => func!(host::tx_gas_price), + "tx_ink_price" => func!(host::tx_ink_price), + "tx_origin" => func!(host::tx_origin), + "pay_for_memory_grow" => func!(host::pay_for_memory_grow), + "native_keccak256" => func!(host::native_keccak256), }, - ); + }; if debug_funcs { - define_imports!( - "console" => host::console { - log_txt, - log_i32, log_i64, log_f32, log_f64, - tee_i32, tee_i64, tee_f32, tee_f64, - }, - "debug" => host::debug { - null_host, - }, - ); + imports.define("console", "log_txt", func!(host::console_log_text)); + imports.define("console", "log_i32", func!(host::console_log::)); + imports.define("console", "log_i64", func!(host::console_log::)); + imports.define("console", "log_f32", func!(host::console_log::)); + imports.define("console", "log_f64", func!(host::console_log::)); + imports.define("console", "tee_i32", func!(host::console_tee::)); + imports.define("console", "tee_i64", func!(host::console_tee::)); + imports.define("console", "tee_f32", func!(host::console_tee::)); + imports.define("console", "tee_f64", func!(host::console_tee::)); + imports.define("debug", "null_host", func!(host::null_host)); } let instance = Instance::new(&mut store, &module, &imports)?; let exports = &instance.exports; @@ -261,7 +270,7 @@ impl> NativeInstance { global.set(store, value.into()).map_err(ErrReport::msg) } - pub fn call_func(&mut self, func: TypedFunction<(), R>, ink: u64) -> Result + pub fn call_func(&mut self, func: TypedFunction<(), R>, ink: Ink) -> Result where R: WasmTypeList, { @@ -341,8 +350,86 @@ impl> StartlessMachine for NativeInstance { } pub fn module(wasm: &[u8], compile: CompileConfig, target: Target) -> Result> { - let store = compile.store(target); + let mut store = compile.store(target); let module = Module::new(&store, wasm)?; + macro_rules! stub { + (u8 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> u8 { panic!("incomplete import") }) + }; + (u32 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> u32 { panic!("incomplete import") }) + }; + (u64 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> u64 { panic!("incomplete import") }) + }; + (f32 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> f32 { panic!("incomplete import") }) + }; + (f64 <- $($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> f64 { panic!("incomplete import") }) + }; + ($($types:tt)+) => { + Function::new_typed(&mut store, $($types)+ -> () { panic!("incomplete import") }) + }; + } + let mut imports = imports! { + "vm_hooks" => { + "read_args" => stub!(|_: u32|), + "write_result" => stub!(|_: u32, _: u32|), + "exit_early" => stub!(|_: u32|), + "storage_load_bytes32" => stub!(|_: u32, _: u32|), + "storage_cache_bytes32" => stub!(|_: u32, _: u32|), + "storage_flush_cache" => stub!(|_: u32|), + "transient_load_bytes32" => stub!(|_: u32, _: u32|), + "transient_store_bytes32" => stub!(|_: u32, _: u32|), + "call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u32, _: u64, _: u32|), + "delegate_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|), + "static_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|), + "create1" => stub!(|_: u32, _: u32, _: u32, _: u32, _: u32|), + "create2" => stub!(|_: u32, _: u32, _: u32, _: u32, _: u32, _: u32|), + "read_return_data" => stub!(u32 <- |_: u32, _: u32, _: u32|), + "return_data_size" => stub!(u32 <- ||), + "emit_log" => stub!(|_: u32, _: u32, _: u32|), + "account_balance" => stub!(|_: u32, _: u32|), + "account_code" => stub!(u32 <- |_: u32, _: u32, _: u32, _: u32|), + "account_codehash" => stub!(|_: u32, _: u32|), + "account_code_size" => stub!(u32 <- |_: u32|), + "evm_gas_left" => stub!(u64 <- ||), + "evm_ink_left" => stub!(u64 <- ||), + "block_basefee" => stub!(|_: u32|), + "chainid" => stub!(u64 <- ||), + "block_coinbase" => stub!(|_: u32|), + "block_gas_limit" => stub!(u64 <- ||), + "block_number" => stub!(u64 <- ||), + "block_timestamp" => stub!(u64 <- ||), + "contract_address" => stub!(|_: u32|), + "math_div" => stub!(|_: u32, _: u32|), + "math_mod" => stub!(|_: u32, _: u32|), + "math_pow" => stub!(|_: u32, _: u32|), + "math_add_mod" => stub!(|_: u32, _: u32, _: u32|), + "math_mul_mod" => stub!(|_: u32, _: u32, _: u32|), + "msg_reentrant" => stub!(u32 <- ||), + "msg_sender" => stub!(|_: u32|), + "msg_value" => stub!(|_: u32|), + "tx_gas_price" => stub!(|_: u32|), + "tx_ink_price" => stub!(u32 <- ||), + "tx_origin" => stub!(|_: u32|), + "pay_for_memory_grow" => stub!(|_: u16|), + "native_keccak256" => stub!(|_: u32, _: u32, _: u32|), + }, + }; + if compile.debug.debug_funcs { + imports.define("console", "log_txt", stub!(|_: u32, _: u32|)); + imports.define("console", "log_i32", stub!(|_: u32|)); + imports.define("console", "log_i64", stub!(|_: u64|)); + imports.define("console", "log_f32", stub!(|_: f32|)); + imports.define("console", "log_f64", stub!(|_: f64|)); + imports.define("console", "tee_i32", stub!(u32 <- |_: u32|)); + imports.define("console", "tee_i64", stub!(u64 <- |_: u64|)); + imports.define("console", "tee_f32", stub!(f32 <- |_: f32|)); + imports.define("console", "tee_f64", stub!(f64 <- |_: f64|)); + imports.define("debug", "null_host", stub!(||)); + } let module = module.serialize()?; Ok(module.to_vec()) @@ -351,13 +438,21 @@ pub fn module(wasm: &[u8], compile: CompileConfig, target: Target) -> Result Result<(ProverModule, StylusData)> { - let (module, stylus_data) = - ProverModule::activate(wasm, codehash, version, page_limit, debug, gas)?; + let (module, stylus_data) = ProverModule::activate( + wasm, + codehash, + stylus_version, + arbos_version_for_gas, + page_limit, + debug, + gas, + )?; Ok((module, stylus_data)) } diff --git a/arbitrator/stylus/src/run.rs b/arbitrator/stylus/src/run.rs index 8e673a25e5..6cbb0cfb42 100644 --- a/arbitrator/stylus/src/run.rs +++ b/arbitrator/stylus/src/run.rs @@ -4,18 +4,18 @@ #![allow(clippy::redundant_closure_call)] use crate::{env::Escape, native::NativeInstance}; -use arbutil::evm::api::{DataReader, EvmApi}; +use arbutil::evm::api::{DataReader, EvmApi, Ink}; use arbutil::evm::user::UserOutcome; use eyre::{eyre, Result}; use prover::machine::Machine; use prover::programs::{prelude::*, STYLUS_ENTRY_POINT}; pub trait RunProgram { - fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result; + fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: Ink) -> Result; } impl RunProgram for Machine { - fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result { + fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: Ink) -> Result { macro_rules! call { ($module:expr, $func:expr, $args:expr) => { call!($module, $func, $args, |error| UserOutcome::Failure(error)) @@ -65,7 +65,7 @@ impl RunProgram for Machine { } impl> RunProgram for NativeInstance { - fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: u64) -> Result { + fn run_main(&mut self, args: &[u8], config: StylusConfig, ink: Ink) -> Result { use UserOutcome::*; self.set_ink(ink); diff --git a/arbitrator/stylus/src/test/api.rs b/arbitrator/stylus/src/test/api.rs index 5d9f625e5e..7a5af6f89f 100644 --- a/arbitrator/stylus/src/test/api.rs +++ b/arbitrator/stylus/src/test/api.rs @@ -4,7 +4,7 @@ use crate::{native, run::RunProgram}; use arbutil::{ evm::{ - api::{EvmApi, VecReader}, + api::{EvmApi, Gas, Ink, VecReader}, user::UserOutcomeKind, EvmData, }, @@ -68,24 +68,24 @@ impl TestEvmApi { } impl EvmApi for TestEvmApi { - fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + fn get_bytes32(&mut self, key: Bytes32, _evm_api_gas_to_use: Gas) -> (Bytes32, Gas) { let storage = &mut self.storage.lock(); let storage = storage.get_mut(&self.program).unwrap(); let value = storage.get(&key).cloned().unwrap_or_default(); - (value, 2100) // pretend worst case + (value, Gas(2100)) // pretend worst case } - fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas { let storage = &mut self.storage.lock(); let storage = storage.get_mut(&self.program).unwrap(); storage.insert(key, value); - 0 + Gas(0) } - fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result { + fn flush_storage_cache(&mut self, _clear: bool, _gas_left: Gas) -> Result { let storage = &mut self.storage.lock(); let storage = storage.get_mut(&self.program).unwrap(); - Ok(22100 * storage.len() as u64) // pretend worst case + Ok(Gas(22100) * storage.len() as u64) // pretend worst case } fn get_transient_bytes32(&mut self, _key: Bytes32) -> Bytes32 { @@ -102,10 +102,10 @@ impl EvmApi for TestEvmApi { &mut self, contract: Bytes20, calldata: &[u8], - _gas_left: u64, - gas_req: u64, + _gas_left: Gas, + gas_req: Gas, _value: Bytes32, - ) -> (u32, u64, UserOutcomeKind) { + ) -> (u32, Gas, UserOutcomeKind) { let compile = self.compile.clone(); let evm_data = self.evm_data; let config = *self.configs.lock().get(&contract).unwrap(); @@ -122,7 +122,7 @@ impl EvmApi for TestEvmApi { let (status, outs) = outcome.into_data(); let outs_len = outs.len() as u32; - let ink_left: u64 = native.ink_left().into(); + let ink_left: Ink = native.ink_left().into(); let gas_left = config.pricing.ink_to_gas(ink_left); *self.write_result.lock() = outs; (outs_len, gas - gas_left, status) @@ -132,9 +132,9 @@ impl EvmApi for TestEvmApi { &mut self, _contract: Bytes20, _calldata: &[u8], - _gas_left: u64, - _gas_req: u64, - ) -> (u32, u64, UserOutcomeKind) { + _gas_left: Gas, + _gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind) { todo!("delegate call not yet supported") } @@ -142,9 +142,9 @@ impl EvmApi for TestEvmApi { &mut self, contract: Bytes20, calldata: &[u8], - gas_left: u64, - gas_req: u64, - ) -> (u32, u64, UserOutcomeKind) { + gas_left: Gas, + gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind) { println!("note: overriding static call with call"); self.contract_call(contract, calldata, gas_left, gas_req, Bytes32::default()) } @@ -153,8 +153,8 @@ impl EvmApi for TestEvmApi { &mut self, _code: Vec, _endowment: Bytes32, - _gas: u64, - ) -> (Result, u32, u64) { + _gas: Gas, + ) -> (Result, u32, Gas) { unimplemented!("create1 not supported") } @@ -163,8 +163,8 @@ impl EvmApi for TestEvmApi { _code: Vec, _endowment: Bytes32, _salt: Bytes32, - _gas: u64, - ) -> (Result, u32, u64) { + _gas: Gas, + ) -> (Result, u32, Gas) { unimplemented!("create2 not supported") } @@ -176,19 +176,19 @@ impl EvmApi for TestEvmApi { Ok(()) // pretend a log was emitted } - fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, u64) { + fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, Gas) { unimplemented!() } - fn account_code(&mut self, _address: Bytes20, _gas_left: u64) -> (VecReader, u64) { + fn account_code(&mut self, _address: Bytes20, _gas_left: Gas) -> (VecReader, Gas) { unimplemented!() } - fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) { + fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, Gas) { unimplemented!() } - fn add_pages(&mut self, new: u16) -> u64 { + fn add_pages(&mut self, new: u16) -> Gas { let model = MemoryModel::new(2, 1000); let (open, ever) = *self.pages.lock(); @@ -203,8 +203,8 @@ impl EvmApi for TestEvmApi { _name: &str, _args: &[u8], _outs: &[u8], - _start_ink: u64, - _end_ink: u64, + _start_ink: Ink, + _end_ink: Ink, ) { unimplemented!() } diff --git a/arbitrator/stylus/src/test/mod.rs b/arbitrator/stylus/src/test/mod.rs index 42c9446564..3fd0faede8 100644 --- a/arbitrator/stylus/src/test/mod.rs +++ b/arbitrator/stylus/src/test/mod.rs @@ -3,7 +3,10 @@ use crate::{env::WasmEnv, native::NativeInstance, run::RunProgram, test::api::TestEvmApi}; use arbutil::{ - evm::{api::VecReader, user::UserOutcome}, + evm::{ + api::{Ink, VecReader}, + user::UserOutcome, + }, Bytes20, Bytes32, Color, }; use eyre::{bail, Result}; @@ -41,7 +44,7 @@ impl TestInstance { }; let mut native = Self::new_from_store(path, store, imports)?; native.set_meter_data(); - native.set_ink(u64::MAX); + native.set_ink(Ink(u64::MAX)); native.set_stack(u32::MAX); Ok(native) } @@ -107,8 +110,8 @@ fn expensive_add(op: &Operator, _tys: &SigMap) -> u64 { } } -pub fn random_ink(min: u64) -> u64 { - rand::thread_rng().gen_range(min..=u64::MAX) +pub fn random_ink(min: u64) -> Ink { + Ink(rand::thread_rng().gen_range(min..=u64::MAX)) } pub fn random_bytes20() -> Bytes20 { @@ -135,7 +138,7 @@ fn uniform_cost_config() -> StylusConfig { stylus_config } -fn test_configs() -> (CompileConfig, StylusConfig, u64) { +fn test_configs() -> (CompileConfig, StylusConfig, Ink) { ( test_compile_config(), uniform_cost_config(), @@ -164,14 +167,13 @@ fn new_test_machine(path: &str, compile: &CompileConfig) -> Result { HashMap::default(), Arc::new(|_, _, _| panic!("tried to read preimage")), Some(stylus_data), - false, )?; - mach.set_ink(u64::MAX); + mach.set_ink(Ink(u64::MAX)); mach.set_stack(u32::MAX); Ok(mach) } -fn run_native(native: &mut TestInstance, args: &[u8], ink: u64) -> Result> { +fn run_native(native: &mut TestInstance, args: &[u8], ink: Ink) -> Result> { let config = native.env().config.expect("no config"); match native.run_main(args, config, ink)? { UserOutcome::Success(output) => Ok(output), @@ -183,7 +185,7 @@ fn run_machine( machine: &mut Machine, args: &[u8], config: StylusConfig, - ink: u64, + ink: Ink, ) -> Result> { match machine.run_main(args, config, ink)? { UserOutcome::Success(output) => Ok(output), diff --git a/arbitrator/stylus/src/test/native.rs b/arbitrator/stylus/src/test/native.rs index 503e5875fe..672bdd179c 100644 --- a/arbitrator/stylus/src/test/native.rs +++ b/arbitrator/stylus/src/test/native.rs @@ -16,7 +16,7 @@ use crate::{ use arbutil::{ crypto, evm::{ - api::EvmApi, + api::{EvmApi, Gas, Ink}, user::{UserOutcome, UserOutcomeKind}, }, format, Bytes20, Bytes32, Color, @@ -48,8 +48,8 @@ fn test_ink() -> Result<()> { macro_rules! exhaust { ($ink:expr) => { - native.set_ink($ink); - assert_eq!(native.ink_left(), MachineMeter::Ready($ink)); + native.set_ink(Ink($ink)); + assert_eq!(native.ink_left(), MachineMeter::Ready(Ink($ink))); assert!(add_one.call(&mut native.store, 32).is_err()); assert_eq!(native.ink_left(), MachineMeter::Exhausted); }; @@ -59,12 +59,12 @@ fn test_ink() -> Result<()> { exhaust!(50); exhaust!(99); - let mut ink_left = 500; + let mut ink_left = Ink(500); native.set_ink(ink_left); - while ink_left > 0 { + while ink_left > Ink(0) { assert_eq!(native.ink_left(), MachineMeter::Ready(ink_left)); assert_eq!(add_one.call(&mut native.store, 64)?, 65); - ink_left -= 100; + ink_left -= Ink(100); } assert!(add_one.call(&mut native.store, 32).is_err()); assert_eq!(native.ink_left(), MachineMeter::Exhausted); @@ -198,7 +198,7 @@ fn test_import_export_safety() -> Result<()> { let mut bin = bin?; assert!(bin.clone().instrument(&compile, codehash).is_err()); compile.debug.debug_info = false; - assert!(bin.instrument(&compile, &codehash).is_err()); + assert!(bin.instrument(&compile, codehash).is_err()); if both { assert!(TestInstance::new_test(file, compile).is_err()); @@ -268,7 +268,7 @@ fn test_heap() -> Result<()> { assert_eq!(pages, 128); let used = config.pricing.ink_to_gas(ink - native.ink_ready()?); - ensure!((used as i64 - 32_000_000).abs() < 3_000, "wrong ink"); + ensure!((used.0 as i64 - 32_000_000).abs() < 3_000, "wrong ink"); assert_eq!(native.memory_size(), Pages(128)); if step == extra { @@ -283,7 +283,7 @@ fn test_heap() -> Result<()> { // the cost should exceed a maximum u32, consuming more gas than can ever be bought let (mut native, _) = TestInstance::new_with_evm("tests/memory2.wat", &compile, config)?; - let outcome = native.run_main(&[], config, config.pricing.ink_to_gas(u32::MAX.into()))?; + let outcome = native.run_main(&[], config, config.pricing.gas_to_ink(Gas(u32::MAX.into())))?; assert_eq!(outcome.kind(), UserOutcomeKind::OutOfInk); // ensure we reject programs with excessive footprints @@ -381,7 +381,7 @@ fn test_storage() -> Result<()> { let (mut native, mut evm) = TestInstance::new_with_evm(filename, &compile, config)?; run_native(&mut native, &store_args, ink)?; - assert_eq!(evm.get_bytes32(key.into()).0, Bytes32(value)); + assert_eq!(evm.get_bytes32(key.into(), Gas(0)).0, Bytes32(value)); assert_eq!(run_native(&mut native, &load_args, ink)?, value); let mut machine = Machine::from_user_path(Path::new(filename), &compile)?; @@ -465,7 +465,7 @@ fn test_calls() -> Result<()> { run_native(&mut native, &args, ink)?; for (key, value) in slots { - assert_eq!(evm.get_bytes32(key).0, value); + assert_eq!(evm.get_bytes32(key, Gas(0)).0, value); } Ok(()) } diff --git a/arbitrator/stylus/src/test/wavm.rs b/arbitrator/stylus/src/test/wavm.rs index e707cf490a..729cfebf2f 100644 --- a/arbitrator/stylus/src/test/wavm.rs +++ b/arbitrator/stylus/src/test/wavm.rs @@ -2,6 +2,7 @@ // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::test::{new_test_machine, test_compile_config}; +use arbutil::evm::api::Ink; use eyre::Result; use prover::{programs::prelude::*, Machine}; @@ -15,8 +16,8 @@ fn test_ink() -> Result<()> { macro_rules! exhaust { ($ink:expr) => { - machine.set_ink($ink); - assert_eq!(machine.ink_left(), MachineMeter::Ready($ink)); + machine.set_ink(Ink($ink)); + assert_eq!(machine.ink_left(), MachineMeter::Ready(Ink($ink))); assert!(call(machine, 32).is_err()); assert_eq!(machine.ink_left(), MachineMeter::Exhausted); }; @@ -26,12 +27,12 @@ fn test_ink() -> Result<()> { exhaust!(50); exhaust!(99); - let mut ink_left = 500; + let mut ink_left = Ink(500); machine.set_ink(ink_left); - while ink_left > 0 { + while ink_left > Ink(0) { assert_eq!(machine.ink_left(), MachineMeter::Ready(ink_left)); assert_eq!(call(machine, 64)?, vec![65_u32.into()]); - ink_left -= 100; + ink_left -= Ink(100); } assert!(call(machine, 32).is_err()); assert_eq!(machine.ink_left(), MachineMeter::Exhausted); diff --git a/arbitrator/stylus/tests/hostio-test/Cargo.lock b/arbitrator/stylus/tests/hostio-test/Cargo.lock new file mode 100644 index 0000000000..1e726910b1 --- /dev/null +++ b/arbitrator/stylus/tests/hostio-test/Cargo.lock @@ -0,0 +1,636 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloy-primitives" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa", + "ruint", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74ceeffdacf9dd0910404d743d07273776fd17b85f9cb17b49a97e5c6055ce9" +dependencies = [ + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.77", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-types" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f347cb6bb307b3802ec455ef43ce00f5e590e0ceca3d2f3b070f5ee367e235" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-hex" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex", + "proptest", + "serde", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.77", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hostio-test" +version = "0.1.0" +dependencies = [ + "mini-alloc", + "stylus-sdk", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bitflags", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "unarray", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "ruint" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +dependencies = [ + "proptest", + "rand", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "stylus-proc" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "convert_case 0.6.0", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "sha3", + "syn 1.0.109", + "syn-solidity", +] + +[[package]] +name = "stylus-sdk" +version = "0.4.2" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "cfg-if 1.0.0", + "derivative", + "hex", + "keccak-const", + "lazy_static", + "stylus-proc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f995d2140b0f751dbe94365be2591edbf3d1b75dcfaeac14183abbd2ff07bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/arbitrator/stylus/tests/hostio-test/Cargo.toml b/arbitrator/stylus/tests/hostio-test/Cargo.toml new file mode 100644 index 0000000000..da7bbce7a3 --- /dev/null +++ b/arbitrator/stylus/tests/hostio-test/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hostio-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["debug", "hostio"] } +mini-alloc.path = "../../../langs/rust/mini-alloc" + +[profile.release] +codegen-units = 1 +strip = true +lto = true +panic = "abort" +opt-level = "s" + +[workspace] diff --git a/arbitrator/stylus/tests/hostio-test/src/main.rs b/arbitrator/stylus/tests/hostio-test/src/main.rs new file mode 100644 index 0000000000..17a5d10266 --- /dev/null +++ b/arbitrator/stylus/tests/hostio-test/src/main.rs @@ -0,0 +1,207 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_main] + +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{Address, B256, U256}, + block, console, contract, evm, hostio, msg, + prelude::*, + stylus_proc::entrypoint, + tx, + types::AddressVM, +}; +extern crate alloc; + +#[cfg(target_arch = "wasm32")] +#[global_allocator] +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; + +sol_storage! { + #[entrypoint] + pub struct HostioTest { + } +} + +type Result = std::result::Result>; + +// These are not available as hostios in the sdk, so we import them directly. +#[link(wasm_import_module = "vm_hooks")] +extern "C" { + fn math_div(value: *mut u8, divisor: *const u8); + fn math_mod(value: *mut u8, modulus: *const u8); + fn math_pow(value: *mut u8, exponent: *const u8); + fn math_add_mod(value: *mut u8, addend: *const u8, modulus: *const u8); + fn math_mul_mod(value: *mut u8, multiplier: *const u8, modulus: *const u8); + fn transient_load_bytes32(key: *const u8, dest: *mut u8); + fn transient_store_bytes32(key: *const u8, value: *const u8); + fn exit_early(status: u32); +} + +#[external] +impl HostioTest { + fn exit_early() -> Result<()> { + unsafe { + exit_early(0); + } + Ok(()) + } + + fn transient_load_bytes32(key: B256) -> Result { + let mut result = B256::ZERO; + unsafe { + transient_load_bytes32(key.as_ptr(), result.as_mut_ptr()); + } + Ok(result) + } + + fn transient_store_bytes32(key: B256, value: B256) { + unsafe { + transient_store_bytes32(key.as_ptr(), value.as_ptr()); + } + } + + fn return_data_size() -> Result { + unsafe { Ok(hostio::return_data_size().try_into().unwrap()) } + } + + fn emit_log(data: Bytes, n: i8, t1: B256, t2: B256, t3: B256, t4: B256) -> Result<()> { + let topics = &[t1, t2, t3, t4]; + evm::raw_log(&topics[0..n as usize], data.as_slice())?; + Ok(()) + } + + fn account_balance(account: Address) -> Result { + Ok(account.balance()) + } + + fn account_code(account: Address) -> Result> { + let mut size = 10000; + let mut code = vec![0; size]; + unsafe { + size = hostio::account_code(account.as_ptr(), 0, size, code.as_mut_ptr()); + } + code.resize(size, 0); + Ok(code) + } + + fn account_code_size(account: Address) -> Result { + Ok(account.code_size().try_into().unwrap()) + } + + fn account_codehash(account: Address) -> Result { + Ok(account.codehash()) + } + + fn evm_gas_left() -> Result { + Ok(evm::gas_left().try_into().unwrap()) + } + + fn evm_ink_left() -> Result { + Ok(tx::ink_to_gas(evm::ink_left()).try_into().unwrap()) + } + + fn block_basefee() -> Result { + Ok(block::basefee()) + } + + fn chainid() -> Result { + Ok(block::chainid().try_into().unwrap()) + } + + fn block_coinbase() -> Result
{ + Ok(block::coinbase()) + } + + fn block_gas_limit() -> Result { + Ok(block::gas_limit().try_into().unwrap()) + } + + fn block_number() -> Result { + Ok(block::number().try_into().unwrap()) + } + + fn block_timestamp() -> Result { + Ok(block::timestamp().try_into().unwrap()) + } + + fn contract_address() -> Result
{ + Ok(contract::address()) + } + + fn math_div(a: U256, b: U256) -> Result { + let mut a_bytes: B256 = a.into(); + let b_bytes: B256 = b.into(); + unsafe { + math_div(a_bytes.as_mut_ptr(), b_bytes.as_ptr()); + } + Ok(a_bytes.into()) + } + + fn math_mod(a: U256, b: U256) -> Result { + let mut a_bytes: B256 = a.into(); + let b_bytes: B256 = b.into(); + unsafe { + math_mod(a_bytes.as_mut_ptr(), b_bytes.as_ptr()); + } + Ok(a_bytes.into()) + } + + fn math_pow(a: U256, b: U256) -> Result { + let mut a_bytes: B256 = a.into(); + let b_bytes: B256 = b.into(); + unsafe { + math_pow(a_bytes.as_mut_ptr(), b_bytes.as_ptr()); + } + Ok(a_bytes.into()) + } + + fn math_add_mod(a: U256, b: U256, c: U256) -> Result { + let mut a_bytes: B256 = a.into(); + let b_bytes: B256 = b.into(); + let c_bytes: B256 = c.into(); + unsafe { + math_add_mod(a_bytes.as_mut_ptr(), b_bytes.as_ptr(), c_bytes.as_ptr()); + } + Ok(a_bytes.into()) + } + + fn math_mul_mod(a: U256, b: U256, c: U256) -> Result { + let mut a_bytes: B256 = a.into(); + let b_bytes: B256 = b.into(); + let c_bytes: B256 = c.into(); + unsafe { + math_mul_mod(a_bytes.as_mut_ptr(), b_bytes.as_ptr(), c_bytes.as_ptr()); + } + Ok(a_bytes.into()) + } + + fn msg_sender() -> Result
{ + Ok(msg::sender()) + } + + fn msg_value() -> Result { + Ok(msg::value()) + } + + fn keccak(preimage: Bytes) -> Result { + let mut result = B256::ZERO; + unsafe { + hostio::native_keccak256(preimage.as_ptr(), preimage.len(), result.as_mut_ptr()); + } + Ok(result) + } + + fn tx_gas_price() -> Result { + Ok(tx::gas_price()) + } + + fn tx_ink_price() -> Result { + Ok(tx::ink_to_gas(tx::ink_price().into()).try_into().unwrap()) + } + + fn tx_origin() -> Result
{ + Ok(tx::origin()) + } +} diff --git a/arbitrator/stylus/tests/write-result-len.wat b/arbitrator/stylus/tests/write-result-len.wat new file mode 100644 index 0000000000..4c9ad35087 --- /dev/null +++ b/arbitrator/stylus/tests/write-result-len.wat @@ -0,0 +1,24 @@ +;; Copyright 2024, Offchain Labs, Inc. +;; For license information, see https://github.com/nitro/blob/master/LICENSE + +(module + (import "vm_hooks" "read_args" (func $read_args (param i32))) + (import "vm_hooks" "write_result" (func $write_result (param i32 i32))) + (memory (export "memory") 2 2) + (func $main (export "user_entrypoint") (param $args_len i32) (result i32) + (local $len i32) + + ;; write args to 0x0 + (call $read_args (i32.const 0)) + + ;; treat first 4 bytes as size to write + (i32.load (i32.const 0)) + local.set $len + + ;; call write + (call $write_result (i32.const 0) (local.get $len)) + + ;; return success + i32.const 0 + ) +) diff --git a/arbitrator/tools/module_roots/src/main.rs b/arbitrator/tools/module_roots/src/main.rs index fc22bed5fa..32e4764847 100644 --- a/arbitrator/tools/module_roots/src/main.rs +++ b/arbitrator/tools/module_roots/src/main.rs @@ -39,7 +39,6 @@ fn main() -> Result<()> { GlobalState::default(), HashMap::default(), Arc::new(|_, _, _| panic!("tried to read preimage")), - false, )?; let mut stylus = vec![]; diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index b234424f69..a5a066e5c9 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli 0.29.0", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "ahash" version = "0.7.8" @@ -46,6 +31,21 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -107,19 +107,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] -name = "backtrace" -version = "0.3.73" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" @@ -217,12 +208,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.7.0" @@ -241,9 +226,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" dependencies = [ "shlex", ] @@ -260,6 +245,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + [[package]] name = "clap" version = "2.34.0" @@ -282,17 +280,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "corosensei" -version = "0.1.4" +name = "core-foundation-sys" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80128832c58ea9cbd041d2a759ec449224487b2c1e400453d99d244eead87a8e" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "libc", - "scopeguard", - "windows-sys 0.33.0", -] +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" @@ -303,123 +294,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2ab4512dfd3a6f4be184403a195f76e81a8a9f9e6c898e19d2dc3ce20e0115" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b022ed2a5913a38839dfbafe6cf135342661293b08049843362df4301261dc" -dependencies = [ - "arrayvec", - "bumpalo", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-egraph", - "cranelift-entity", - "cranelift-isle", - "gimli 0.26.2", - "log", - "regalloc2", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "639307b45434ad112a98f8300c0f0ab085cbefcd767efcdef9ef19d4c0756e74" -dependencies = [ - "cranelift-codegen-shared", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278e52e29c53fcf32431ef08406c295699a70306d05a0715c5b1bf50e33a9ab7" - -[[package]] -name = "cranelift-egraph" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624b54323b06e675293939311943ba82d323bb340468ce1889be5da7932c8d73" -dependencies = [ - "cranelift-entity", - "fxhash", - "hashbrown 0.12.3", - "indexmap 1.9.3", - "log", - "smallvec", -] - -[[package]] -name = "cranelift-entity" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a59bcbca89c3f1b70b93ab3cbba5e5e0cbf3e63dadb23c7525cb142e21a9d4c" - -[[package]] -name = "cranelift-frontend" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d70abacb8cfef3dc8ff7e8836e9c1d70f7967dfdac824a4cd5e30223415aca6" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-isle" -version = "0.91.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "393bc73c451830ff8dbb3a07f61843d6cb41a084f9996319917c0b291ed785bb" - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - [[package]] name = "crunchy" version = "0.2.2" @@ -436,38 +310,14 @@ dependencies = [ "typenum", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -480,42 +330,29 @@ dependencies = [ "ident_case", "proc-macro2", "quote", + "strsim 0.11.1", "syn 2.0.72", ] -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core 0.13.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.10", + "darling_core", "quote", "syn 2.0.72", ] [[package]] -name = "dashmap" -version = "5.5.3" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "powerfmt", + "serde", ] [[package]] @@ -622,7 +459,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" dependencies = [ - "darling 0.20.10", + "darling", "proc-macro2", "quote", "syn 2.0.72", @@ -644,33 +481,26 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "forward" +version = "0.1.0" +dependencies = [ + "eyre", + "structopt", +] + [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -692,23 +522,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "gimli" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -dependencies = [ - "fallible-iterator", - "indexmap 1.9.3", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - [[package]] name = "hashbrown" version = "0.12.3" @@ -760,6 +573,29 @@ dependencies = [ "caller-env", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -780,6 +616,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -790,6 +627,7 @@ checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown 0.14.5", + "serde", ] [[package]] @@ -868,57 +706,12 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d28bba84adfe6646737845bc5ebbfa2c08424eb1c37e94a1fd2a82adb56a872" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "memory_units" version = "0.4.0" @@ -931,15 +724,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "more-asserts" version = "0.2.2" @@ -1000,6 +784,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.2" @@ -1072,15 +862,6 @@ dependencies = [ "syn 2.0.72", ] -[[package]] -name = "object" -version = "0.36.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -1123,10 +904,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro-crate" @@ -1208,7 +989,6 @@ dependencies = [ "smallvec", "static_assertions", "structopt", - "wasmer", "wasmer-types", "wasmparser", "wat", @@ -1273,26 +1053,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.3" @@ -1302,30 +1062,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "regalloc2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" -dependencies = [ - "fxhash", - "log", - "slice-group-by", - "smallvec", -] - -[[package]] -name = "region" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" -dependencies = [ - "bitflags 1.3.2", - "libc", - "mach2", - "windows-sys 0.52.0", -] - [[package]] name = "rend" version = "0.4.2" @@ -1416,12 +1152,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "self_cell" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" - [[package]] name = "semver" version = "1.0.23" @@ -1437,17 +1167,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_derive" version = "1.0.204" @@ -1473,24 +1192,32 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.14.0" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.3.0", "serde", + "serde_derive", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ - "darling 0.13.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.72", ] [[package]] @@ -1539,16 +1266,6 @@ dependencies = [ "keccak", ] -[[package]] -name = "shared-buffer" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" -dependencies = [ - "bytes", - "memmap2 0.6.2", -] - [[package]] name = "shlex" version = "1.3.0" @@ -1567,12 +1284,6 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - [[package]] name = "smallvec" version = "1.13.2" @@ -1582,12 +1293,6 @@ dependencies = [ "serde", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -1602,9 +1307,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "structopt" @@ -1693,6 +1398,37 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -1734,37 +1470,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.72", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - [[package]] name = "typenum" version = "1.17.0" @@ -1919,90 +1624,11 @@ checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-encoder" -version = "0.32.0" +version = "0.215.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasmer" -version = "4.2.8" -dependencies = [ - "bytes", - "cfg-if 1.0.0", - "derivative", - "indexmap 1.9.3", - "js-sys", - "more-asserts", - "rustc-demangle", - "serde", - "serde-wasm-bindgen", - "shared-buffer", - "target-lexicon", - "thiserror", - "tracing", - "wasm-bindgen", - "wasmer-compiler", - "wasmer-compiler-cranelift", - "wasmer-derive", - "wasmer-types", - "wasmer-vm", - "wat", - "winapi", -] - -[[package]] -name = "wasmer-compiler" -version = "4.2.8" +checksum = "4fb56df3e06b8e6b77e37d2969a50ba51281029a9aeb3855e76b7f49b6418847" dependencies = [ - "backtrace", - "bytes", - "cfg-if 1.0.0", - "enum-iterator 0.7.0", - "enumset", - "lazy_static", "leb128", - "memmap2 0.5.10", - "more-asserts", - "region", - "rkyv", - "self_cell", - "shared-buffer", - "smallvec", - "thiserror", - "wasmer-types", - "wasmer-vm", - "wasmparser", - "winapi", -] - -[[package]] -name = "wasmer-compiler-cranelift" -version = "4.2.8" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "gimli 0.26.2", - "more-asserts", - "rayon", - "smallvec", - "target-lexicon", - "tracing", - "wasmer-compiler", - "wasmer-types", -] - -[[package]] -name = "wasmer-derive" -version = "4.2.8" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", ] [[package]] @@ -2019,32 +1645,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "wasmer-vm" -version = "4.2.8" -dependencies = [ - "backtrace", - "cc", - "cfg-if 1.0.0", - "corosensei", - "crossbeam-queue", - "dashmap", - "derivative", - "enum-iterator 0.7.0", - "fnv", - "indexmap 1.9.3", - "lazy_static", - "libc", - "mach", - "memoffset", - "more-asserts", - "region", - "scopeguard", - "thiserror", - "wasmer-types", - "winapi", -] - [[package]] name = "wasmparser" version = "0.121.2" @@ -2058,10 +1658,11 @@ dependencies = [ [[package]] name = "wast" -version = "64.0.0" +version = "215.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" +checksum = "1ff1d00d893593249e60720be04a7c1f42f1c4dc3806a2869f4e66ab61eb54cb" dependencies = [ + "bumpalo", "leb128", "memchr", "unicode-width", @@ -2070,9 +1671,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.71" +version = "1.215.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" +checksum = "670bf4d9c8cf76ae242d70ded47c546525b6dafaa6871f9bcb065344bf2b4e3d" dependencies = [ "wast", ] @@ -2112,23 +1713,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" -dependencies = [ - "windows_aarch64_msvc 0.33.0", - "windows_i686_gnu 0.33.0", - "windows_i686_msvc 0.33.0", - "windows_x86_64_gnu 0.33.0", - "windows_x86_64_msvc 0.33.0", -] - -[[package]] -name = "windows-sys" +name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets", ] @@ -2140,13 +1728,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.52.6", + "windows_x86_64_msvc", ] [[package]] @@ -2155,24 +1743,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2185,24 +1761,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2215,12 +1779,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/arbitrator/wasm-libraries/Cargo.toml b/arbitrator/wasm-libraries/Cargo.toml index 2fd585ee7d..837df8f4da 100644 --- a/arbitrator/wasm-libraries/Cargo.toml +++ b/arbitrator/wasm-libraries/Cargo.toml @@ -7,5 +7,6 @@ members = [ "user-host-trait", "user-test", "program-exec", + "forward", ] resolver = "2" diff --git a/arbitrator/wasm-libraries/forward/.gitignore b/arbitrator/wasm-libraries/forward/.gitignore new file mode 100644 index 0000000000..40da2042b7 --- /dev/null +++ b/arbitrator/wasm-libraries/forward/.gitignore @@ -0,0 +1 @@ +**.wat diff --git a/arbitrator/wasm-libraries/forward/Cargo.toml b/arbitrator/wasm-libraries/forward/Cargo.toml new file mode 100644 index 0000000000..73ed9d8827 --- /dev/null +++ b/arbitrator/wasm-libraries/forward/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "forward" +version = "0.1.0" +edition = "2021" + +[dependencies] +eyre = "0.6.5" +structopt = "0.3.26" diff --git a/arbitrator/wasm-libraries/forward/src/main.rs b/arbitrator/wasm-libraries/forward/src/main.rs new file mode 100644 index 0000000000..f978a8723b --- /dev/null +++ b/arbitrator/wasm-libraries/forward/src/main.rs @@ -0,0 +1,207 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use eyre::Result; +use std::{fs::File, io::Write, path::PathBuf}; +use structopt::StructOpt; + +/// order matters! +const HOSTIOS: [[&str; 3]; 42] = [ + ["read_args", "i32", ""], + ["write_result", "i32 i32", ""], + ["exit_early", "i32", ""], + ["storage_load_bytes32", "i32 i32", ""], + ["storage_cache_bytes32", "i32 i32", ""], + ["storage_flush_cache", "i32", ""], + ["transient_load_bytes32", "i32 i32", ""], + ["transient_store_bytes32", "i32 i32", ""], + ["call_contract", "i32 i32 i32 i32 i64 i32", "i32"], + ["delegate_call_contract", "i32 i32 i32 i64 i32", "i32"], + ["static_call_contract", "i32 i32 i32 i64 i32", "i32"], + ["create1", "i32 i32 i32 i32 i32", ""], + ["create2", "i32 i32 i32 i32 i32 i32", ""], + ["read_return_data", "i32 i32 i32", "i32"], + ["return_data_size", "", "i32"], + ["emit_log", "i32 i32 i32", ""], + ["account_balance", "i32 i32", ""], + ["account_code", "i32 i32 i32 i32", "i32"], + ["account_code_size", "i32", "i32"], + ["account_codehash", "i32 i32", ""], + ["evm_gas_left", "", "i64"], + ["evm_ink_left", "", "i64"], + ["block_basefee", "i32", ""], + ["chainid", "", "i64"], + ["block_coinbase", "i32", ""], + ["block_gas_limit", "", "i64"], + ["block_number", "", "i64"], + ["block_timestamp", "", "i64"], + ["contract_address", "i32", ""], + ["math_div", "i32 i32", ""], + ["math_mod", "i32 i32", ""], + ["math_pow", "i32 i32", ""], + ["math_add_mod", "i32 i32 i32", ""], + ["math_mul_mod", "i32 i32 i32", ""], + ["msg_reentrant", "", "i32"], + ["msg_sender", "i32", ""], + ["msg_value", "i32", ""], + ["native_keccak256", "i32 i32 i32", ""], + ["tx_gas_price", "i32", ""], + ["tx_ink_price", "", "i32"], + ["tx_origin", "i32", ""], + ["pay_for_memory_grow", "i32", ""], +]; + +#[derive(StructOpt)] +#[structopt(name = "arbitrator-prover")] +struct Opts { + #[structopt(long)] + path: PathBuf, + #[structopt(long)] + stub: bool, +} + +fn main() -> Result<()> { + let opts = Opts::from_args(); + let file = &mut File::options() + .create(true) + .write(true) + .truncate(true) + .open(opts.path)?; + + match opts.stub { + true => forward_stub(file), + false => forward(file), + } +} + +fn forward(file: &mut File) -> Result<()> { + macro_rules! wln { + ($($text:tt)*) => { + writeln!(file, $($text)*)?; + }; + } + let s = " "; + + wln!( + ";; Copyright 2022-2023, Offchain Labs, Inc.\n\ + ;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE\n\ + ;; This file is auto-generated.\n\ + \n\ + (module" + ); + + macro_rules! group { + ($list:expr, $kind:expr) => { + (!$list.is_empty()) + .then(|| format!(" ({} {})", $kind, $list)) + .unwrap_or_default() + }; + } + + wln!("{s};; symbols to re-export"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!( + r#"{s}(import "user_host" "arbitrator_forward__{name}" (func ${name}{params}{result}))"# + ); + } + wln!(); + + wln!("{s};; reserved offsets for future user_host imports"); + for i in HOSTIOS.len()..512 { + wln!("{s}(func $reserved_{i} unreachable)"); + } + wln!(); + + wln!( + "{s};; allows user_host to request a trap\n\ + {s}(global $trap (mut i32) (i32.const 0))\n\ + {s}(func $check\n\ + {s}{s}global.get $trap ;; see if set\n\ + {s}{s}(global.set $trap (i32.const 0)) ;; reset the flag\n\ + {s}{s}(if (then (unreachable)))\n\ + {s})\n\ + {s}(func (export \"forward__set_trap\")\n\ + {s}{s}(global.set $trap (i32.const 1))\n\ + {s})\n" + ); + + wln!("{s};; user linkage"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!("{s}(func (export \"vm_hooks__{name}\"){params}{result}"); + + let gets = (1 + ins.len()) / 4; + for i in 0..gets { + wln!("{s}{s}local.get {i}"); + } + + wln!( + "{s}{s}call ${name}\n\ + {s}{s}call $check\n\ + {s})" + ); + } + + wln!(")"); + Ok(()) +} + +fn forward_stub(file: &mut File) -> Result<()> { + macro_rules! wln { + ($($text:tt)*) => { + writeln!(file, $($text)*)?; + }; + } + let s = " "; + + wln!( + ";; Copyright 2022-2023, Offchain Labs, Inc.\n\ + ;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE\n\ + ;; This file is auto-generated.\n\ + \n\ + (module" + ); + + macro_rules! group { + ($list:expr, $kind:expr) => { + (!$list.is_empty()) + .then(|| format!(" ({} {})", $kind, $list)) + .unwrap_or_default() + }; + } + + wln!("{s};; stubs for the symbols we re-export"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!("{s}(func ${name}{params}{result} unreachable)"); + } + wln!(); + + wln!("{s};; reserved offsets for future user_host imports"); + for i in HOSTIOS.len()..512 { + wln!("{s}(func $reserved_{i} unreachable)"); + } + wln!(); + + wln!( + "{s};; allows user_host to request a trap\n\ + {s}(global $trap (mut i32) (i32.const 0))\n\ + {s}(func $check unreachable)\n\ + {s};; stub for the forward__set_trap function\n\ + {s}(func $forward__set_trap unreachable)" + ); + + wln!("{s};; user linkage"); + for [name, ins, outs] in HOSTIOS { + let params = group!(ins, "param"); + let result = group!(outs, "result"); + wln!("{s}(func (export \"vm_hooks__{name}\"){params}{result} unreachable)"); + } + + wln!(")"); + Ok(()) +} diff --git a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs index 37af85c382..35a4a31347 100644 --- a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs +++ b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs @@ -5,10 +5,10 @@ use arbutil::{ crypto, evm::{ self, - api::{DataReader, EvmApi}, + api::{DataReader, EvmApi, Gas, Ink}, storage::StorageCache, user::UserOutcomeKind, - EvmData, + EvmData, ARBOS_VERSION_STYLUS_CHARGING_FIXES, }, pricing::{self, EVM_API_INK, HOSTIO_INK, PTR_INK}, Bytes20, Bytes32, @@ -88,7 +88,7 @@ pub trait UserHost: GasMeteredMachine { } fn say(&self, text: D); - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64); + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: Ink); fn write_bytes20(&self, ptr: GuestPtr, src: Bytes20) -> Result<(), Self::MemoryErr> { self.write_slice(ptr, &src.0) @@ -143,11 +143,20 @@ pub trait UserHost: GasMeteredMachine { /// [`SLOAD`]: https://www.evm.codes/#54 fn storage_load_bytes32(&mut self, key: GuestPtr, dest: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + 2 * PTR_INK)?; - self.require_gas(evm::COLD_SLOAD_GAS + EVM_API_INK + StorageCache::REQUIRED_ACCESS_GAS)?; // cache-miss case + let arbos_version = self.evm_data().arbos_version; + // require for cache-miss case, preserve wrong behavior for old arbos + let evm_api_gas_to_use = if arbos_version < ARBOS_VERSION_STYLUS_CHARGING_FIXES { + Gas(EVM_API_INK.0) + } else { + self.pricing().ink_to_gas(EVM_API_INK) + }; + self.require_gas( + evm::COLD_SLOAD_GAS + StorageCache::REQUIRED_ACCESS_GAS + evm_api_gas_to_use, + )?; let key = self.read_bytes32(key)?; - let (value, gas_cost) = self.evm_api().get_bytes32(key); + let (value, gas_cost) = self.evm_api().get_bytes32(key, evm_api_gas_to_use); self.buy_gas(gas_cost)?; self.write_bytes32(dest, value)?; trace!("storage_load_bytes32", self, key, value) @@ -185,7 +194,10 @@ pub trait UserHost: GasMeteredMachine { self.require_gas(evm::SSTORE_SENTRY_GAS)?; // see operations_acl_arbitrum.go let gas_left = self.gas_left()?; - self.evm_api().flush_storage_cache(clear, gas_left)?; + let gas_cost = self.evm_api().flush_storage_cache(clear, gas_left)?; + if self.evm_data().arbos_version >= ARBOS_VERSION_STYLUS_CHARGING_FIXES { + self.buy_gas(gas_cost)?; + } trace!("storage_flush_cache", self, [be!(clear as u8)], &[]) } @@ -241,7 +253,7 @@ pub trait UserHost: GasMeteredMachine { data: GuestPtr, data_len: u32, value: GuestPtr, - gas: u64, + gas: Gas, ret_len: GuestPtr, ) -> Result { let value = Some(value); @@ -270,7 +282,7 @@ pub trait UserHost: GasMeteredMachine { contract: GuestPtr, data: GuestPtr, data_len: u32, - gas: u64, + gas: Gas, ret_len: GuestPtr, ) -> Result { let call = |api: &mut Self::A, contract, data: &_, left, req, _| { @@ -300,7 +312,7 @@ pub trait UserHost: GasMeteredMachine { contract: GuestPtr, data: GuestPtr, data_len: u32, - gas: u64, + gas: Gas, ret_len: GuestPtr, ) -> Result { let call = |api: &mut Self::A, contract, data: &_, left, req, _| { @@ -317,7 +329,7 @@ pub trait UserHost: GasMeteredMachine { calldata: GuestPtr, calldata_len: u32, value: Option, - gas: u64, + gas: Gas, return_data_len: GuestPtr, call: F, name: &str, @@ -327,10 +339,10 @@ pub trait UserHost: GasMeteredMachine { &mut Self::A, Address, &[u8], - u64, - u64, + Gas, + Gas, Option, - ) -> (u32, u64, UserOutcomeKind), + ) -> (u32, Gas, UserOutcomeKind), { self.buy_ink(HOSTIO_INK + 3 * PTR_INK + EVM_API_INK)?; self.pay_for_read(calldata_len)?; @@ -453,12 +465,12 @@ pub trait UserHost: GasMeteredMachine { salt: Option, contract: GuestPtr, revert_data_len: GuestPtr, - cost: u64, + cost: Ink, call: F, name: &str, ) -> Result<(), Self::Err> where - F: FnOnce(&mut Self::A, Vec, Bytes32, Option, u64) -> (Result
, u32, u64), + F: FnOnce(&mut Self::A, Vec, Bytes32, Option, Gas) -> (Result
, u32, Gas), { self.buy_ink(HOSTIO_INK + cost)?; self.pay_for_read(code_len)?; @@ -733,7 +745,7 @@ pub trait UserHost: GasMeteredMachine { /// equivalent to that of the EVM's [`GAS`] opcode. /// /// [`GAS`]: https://www.evm.codes/#5a - fn evm_gas_left(&mut self) -> Result { + fn evm_gas_left(&mut self) -> Result { self.buy_ink(HOSTIO_INK)?; let gas = self.gas_left()?; trace!("evm_gas_left", self, &[], be!(gas), gas) @@ -745,7 +757,7 @@ pub trait UserHost: GasMeteredMachine { /// /// [`GAS`]: https://www.evm.codes/#5a /// [`Ink and Gas`]: https://developer.arbitrum.io/TODO - fn evm_ink_left(&mut self) -> Result { + fn evm_ink_left(&mut self) -> Result { self.buy_ink(HOSTIO_INK)?; let ink = self.ink_ready()?; trace!("evm_ink_left", self, &[], be!(ink), ink) diff --git a/arbitrator/wasm-libraries/user-host/src/host.rs b/arbitrator/wasm-libraries/user-host/src/host.rs index abe55b8c12..5ec2ece2c3 100644 --- a/arbitrator/wasm-libraries/user-host/src/host.rs +++ b/arbitrator/wasm-libraries/user-host/src/host.rs @@ -2,7 +2,7 @@ // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::program::Program; -use arbutil::evm::user::UserOutcomeKind; +use arbutil::evm::{api::Gas, user::UserOutcomeKind}; use caller_env::GuestPtr; use user_host_trait::UserHost; @@ -77,7 +77,14 @@ pub unsafe extern "C" fn user_host__call_contract( gas: u64, ret_len: GuestPtr, ) -> u8 { - hostio!(call_contract(contract, data, data_len, value, gas, ret_len)) + hostio!(call_contract( + contract, + data, + data_len, + value, + Gas(gas), + ret_len + )) } #[no_mangle] @@ -89,7 +96,11 @@ pub unsafe extern "C" fn user_host__delegate_call_contract( ret_len: GuestPtr, ) -> u8 { hostio!(delegate_call_contract( - contract, data, data_len, gas, ret_len + contract, + data, + data_len, + Gas(gas), + ret_len )) } @@ -101,7 +112,13 @@ pub unsafe extern "C" fn user_host__static_call_contract( gas: u64, ret_len: GuestPtr, ) -> u8 { - hostio!(static_call_contract(contract, data, data_len, gas, ret_len)) + hostio!(static_call_contract( + contract, + data, + data_len, + Gas(gas), + ret_len + )) } #[no_mangle] @@ -207,12 +224,12 @@ pub unsafe extern "C" fn user_host__contract_address(ptr: GuestPtr) { #[no_mangle] pub unsafe extern "C" fn user_host__evm_gas_left() -> u64 { - hostio!(evm_gas_left()) + hostio!(evm_gas_left()).0 } #[no_mangle] pub unsafe extern "C" fn user_host__evm_ink_left() -> u64 { - hostio!(evm_ink_left()) + hostio!(evm_ink_left()).0 } #[no_mangle] diff --git a/arbitrator/wasm-libraries/user-host/src/ink.rs b/arbitrator/wasm-libraries/user-host/src/ink.rs index e01e616e07..bde7cfc1c0 100644 --- a/arbitrator/wasm-libraries/user-host/src/ink.rs +++ b/arbitrator/wasm-libraries/user-host/src/ink.rs @@ -2,6 +2,7 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::program::Program; +use arbutil::evm::api::Ink; use prover::programs::{ config::PricingParams, prelude::{GasMeteredMachine, MachineMeter, MeteredMachine}, @@ -18,7 +19,7 @@ impl MeteredMachine for Program { fn ink_left(&self) -> MachineMeter { unsafe { match user_ink_status() { - 0 => MachineMeter::Ready(user_ink_left()), + 0 => MachineMeter::Ready(Ink(user_ink_left())), _ => MachineMeter::Exhausted, } } @@ -26,7 +27,7 @@ impl MeteredMachine for Program { fn set_meter(&mut self, meter: MachineMeter) { unsafe { - user_set_ink(meter.ink(), meter.status()); + user_set_ink(meter.ink().0, meter.status()); } } } diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs index 428611167d..cb9f046cdb 100644 --- a/arbitrator/wasm-libraries/user-host/src/link.rs +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -3,7 +3,11 @@ use crate::program::Program; use arbutil::{ - evm::{user::UserOutcomeKind, EvmData}, + evm::{ + api::{Gas, Ink}, + user::UserOutcomeKind, + EvmData, + }, format::DebugBytes, heapify, Bytes20, Bytes32, }; @@ -37,14 +41,15 @@ struct MemoryLeaf([u8; 32]); /// /// pages_ptr: starts pointing to max allowed pages, returns number of pages used #[no_mangle] -pub unsafe extern "C" fn programs__activate( +pub unsafe extern "C" fn programs__activate_v2( wasm_ptr: GuestPtr, wasm_size: usize, pages_ptr: GuestPtr, asm_estimate_ptr: GuestPtr, init_cost_ptr: GuestPtr, cached_init_cost_ptr: GuestPtr, - version: u16, + stylus_version: u16, + arbos_version_for_gas: u64, debug: u32, codehash: GuestPtr, module_hash_ptr: GuestPtr, @@ -58,7 +63,15 @@ pub unsafe extern "C" fn programs__activate( let page_limit = STATIC_MEM.read_u16(pages_ptr); let gas_left = &mut STATIC_MEM.read_u64(gas_ptr); - match Module::activate(&wasm, codehash, version, page_limit, debug, gas_left) { + match Module::activate( + &wasm, + codehash, + stylus_version, + arbos_version_for_gas, + page_limit, + debug, + gas_left, + ) { Ok((module, data)) => { STATIC_MEM.write_u64(gas_ptr, *gas_left); STATIC_MEM.write_u16(pages_ptr, data.footprint); @@ -111,11 +124,11 @@ pub unsafe extern "C" fn programs__new_program( // buy ink let pricing = config.pricing; - let ink = pricing.gas_to_ink(gas); + let ink = pricing.gas_to_ink(Gas(gas)); // link the program and ready its instrumentation let module = wavm_link_module(&MemoryLeaf(*module_hash)); - program_set_ink(module, ink); + program_set_ink(module, ink.0); program_set_stack(module, config.max_depth); // provide arguments @@ -166,7 +179,7 @@ pub unsafe extern "C" fn programs__set_response( id, STATIC_MEM.read_slice(result_ptr, result_len), STATIC_MEM.read_slice(raw_data_ptr, raw_data_len), - gas, + Gas(gas), ); } @@ -198,7 +211,7 @@ pub unsafe extern "C" fn program_internal__set_done(mut status: UserOutcomeKind) let program = Program::current(); let module = program.module; let mut outs = program.outs.as_slice(); - let mut ink_left = program_ink_left(module); + let mut ink_left = Ink(program_ink_left(module)); // apply any early exit codes if let Some(early) = program.early_exit { @@ -209,12 +222,12 @@ pub unsafe extern "C" fn program_internal__set_done(mut status: UserOutcomeKind) if program_ink_status(module) != 0 { status = OutOfInk; outs = &[]; - ink_left = 0; + ink_left = Ink(0); } if program_stack_left(module) == 0 { status = OutOfStack; outs = &[]; - ink_left = 0; + ink_left = Ink(0); } let gas_left = program.config.pricing.ink_to_gas(ink_left); @@ -242,7 +255,8 @@ pub unsafe extern "C" fn programs__create_stylus_config( /// Creates an `EvmData` handler from its component parts. /// #[no_mangle] -pub unsafe extern "C" fn programs__create_evm_data( +pub unsafe extern "C" fn programs__create_evm_data_v2( + arbos_version: u64, block_basefee_ptr: GuestPtr, chainid: u64, block_coinbase_ptr: GuestPtr, @@ -259,6 +273,7 @@ pub unsafe extern "C" fn programs__create_evm_data( reentrant: u32, ) -> u64 { let evm_data = EvmData { + arbos_version, block_basefee: read_bytes32(block_basefee_ptr), cached: cached != 0, chainid, diff --git a/arbitrator/wasm-libraries/user-host/src/program.rs b/arbitrator/wasm-libraries/user-host/src/program.rs index 4199a691f7..7b3782b2e5 100644 --- a/arbitrator/wasm-libraries/user-host/src/program.rs +++ b/arbitrator/wasm-libraries/user-host/src/program.rs @@ -3,7 +3,7 @@ use arbutil::{ evm::{ - api::{EvmApiMethod, VecReader, EVM_API_METHOD_REQ_OFFSET}, + api::{EvmApiMethod, Gas, Ink, VecReader, EVM_API_METHOD_REQ_OFFSET}, req::{EvmApiRequestor, RequestHandler}, user::UserOutcomeKind, EvmData, @@ -49,7 +49,7 @@ static mut LAST_REQUEST_ID: u32 = 0x10000; #[derive(Clone)] pub(crate) struct UserHostRequester { data: Option>, - answer: Option<(Vec, VecReader, u64)>, + answer: Option<(Vec, VecReader, Gas)>, req_type: u32, id: u32, } @@ -95,7 +95,7 @@ impl UserHostRequester { req_id: u32, result: Vec, raw_data: Vec, - gas: u64, + gas: Gas, ) { self.answer = Some((result, VecReader::new(raw_data), gas)); if req_id != self.id { @@ -130,7 +130,7 @@ impl UserHostRequester { } #[no_mangle] - unsafe fn send_request(&mut self, req_type: u32, data: Vec) -> (Vec, VecReader, u64) { + unsafe fn send_request(&mut self, req_type: u32, data: Vec) -> (Vec, VecReader, Gas) { let req_id = self.set_request(req_type, &data); compiler_fence(Ordering::SeqCst); @@ -149,7 +149,7 @@ impl RequestHandler for UserHostRequester { &mut self, req_type: EvmApiMethod, req_data: impl AsRef<[u8]>, - ) -> (Vec, VecReader, u64) { + ) -> (Vec, VecReader, Gas) { unsafe { self.send_request( req_type as u32 + EVM_API_METHOD_REQ_OFFSET, @@ -265,7 +265,7 @@ impl UserHost for Program { println!("{} {text}", "Stylus says:".yellow()); } - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: u64) { + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: Ink) { let args = hex::encode(args); let outs = hex::encode(outs); println!("Error: unexpected hostio tracing info for {name} while proving: {args}, {outs}"); diff --git a/arbitrator/wasm-libraries/user-test/src/host.rs b/arbitrator/wasm-libraries/user-test/src/host.rs index f2912eaae3..f1b4506414 100644 --- a/arbitrator/wasm-libraries/user-test/src/host.rs +++ b/arbitrator/wasm-libraries/user-test/src/host.rs @@ -2,6 +2,7 @@ // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::program::Program; +use arbutil::evm::api::Gas; use caller_env::GuestPtr; use user_host_trait::UserHost; @@ -63,7 +64,14 @@ pub unsafe extern "C" fn vm_hooks__call_contract( gas: u64, ret_len: GuestPtr, ) -> u8 { - hostio!(call_contract(contract, data, data_len, value, gas, ret_len)) + hostio!(call_contract( + contract, + data, + data_len, + value, + Gas(gas), + ret_len + )) } #[no_mangle] @@ -75,7 +83,11 @@ pub unsafe extern "C" fn vm_hooks__delegate_call_contract( ret_len: GuestPtr, ) -> u8 { hostio!(delegate_call_contract( - contract, data, data_len, gas, ret_len + contract, + data, + data_len, + Gas(gas), + ret_len )) } @@ -87,7 +99,13 @@ pub unsafe extern "C" fn vm_hooks__static_call_contract( gas: u64, ret_len: GuestPtr, ) -> u8 { - hostio!(static_call_contract(contract, data, data_len, gas, ret_len)) + hostio!(static_call_contract( + contract, + data, + data_len, + Gas(gas), + ret_len + )) } #[no_mangle] @@ -189,12 +207,12 @@ pub unsafe extern "C" fn vm_hooks__contract_address(ptr: GuestPtr) { #[no_mangle] pub unsafe extern "C" fn vm_hooks__evm_gas_left() -> u64 { - hostio!(evm_gas_left()) + hostio!(evm_gas_left()).0 } #[no_mangle] pub unsafe extern "C" fn vm_hooks__evm_ink_left() -> u64 { - hostio!(evm_ink_left()) + hostio!(evm_ink_left()).0 } #[no_mangle] diff --git a/arbitrator/wasm-libraries/user-test/src/ink.rs b/arbitrator/wasm-libraries/user-test/src/ink.rs index fca658e59b..72ecfadd96 100644 --- a/arbitrator/wasm-libraries/user-test/src/ink.rs +++ b/arbitrator/wasm-libraries/user-test/src/ink.rs @@ -2,6 +2,7 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{program::Program, CONFIG}; +use arbutil::evm::api::Ink; use prover::programs::{ config::PricingParams, prelude::{GasMeteredMachine, MachineMeter, MeteredMachine}, @@ -18,7 +19,7 @@ impl MeteredMachine for Program { fn ink_left(&self) -> MachineMeter { unsafe { match user_ink_status() { - 0 => MachineMeter::Ready(user_ink_left()), + 0 => MachineMeter::Ready(Ink(user_ink_left())), _ => MachineMeter::Exhausted, } } @@ -26,7 +27,7 @@ impl MeteredMachine for Program { fn set_meter(&mut self, meter: MachineMeter) { unsafe { - user_set_ink(meter.ink(), meter.status()); + user_set_ink(meter.ink().0, meter.status()); } } } diff --git a/arbitrator/wasm-libraries/user-test/src/program.rs b/arbitrator/wasm-libraries/user-test/src/program.rs index c56ea52ad0..299fca08c3 100644 --- a/arbitrator/wasm-libraries/user-test/src/program.rs +++ b/arbitrator/wasm-libraries/user-test/src/program.rs @@ -4,7 +4,7 @@ use crate::{ARGS, EVER_PAGES, EVM_DATA, KEYS, LOGS, OPEN_PAGES, OUTS}; use arbutil::{ evm::{ - api::{EvmApi, VecReader}, + api::{EvmApi, Gas, Ink, VecReader}, user::UserOutcomeKind, EvmData, }, @@ -80,7 +80,7 @@ impl UserHost for Program { println!("{} {text}", "Stylus says:".yellow()); } - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: u64) { + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: Ink) { let args = hex::encode(args); let outs = hex::encode(outs); println!("Error: unexpected hostio tracing info for {name} while proving: {args}, {outs}"); @@ -102,18 +102,18 @@ impl Program { pub struct MockEvmApi; impl EvmApi for MockEvmApi { - fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + fn get_bytes32(&mut self, key: Bytes32, _evm_api_gas_to_use: Gas) -> (Bytes32, Gas) { let value = KEYS.lock().get(&key).cloned().unwrap_or_default(); - (value, 2100) // pretend worst case + (value, Gas(2100)) // pretend worst case } - fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Gas { KEYS.lock().insert(key, value); - 0 + Gas(0) } - fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result { - Ok(22100 * KEYS.lock().len() as u64) // pretend worst case + fn flush_storage_cache(&mut self, _clear: bool, _gas_left: Gas) -> Result { + Ok(Gas(22100) * KEYS.lock().len() as u64) // pretend worst case } fn get_transient_bytes32(&mut self, _key: Bytes32) -> Bytes32 { @@ -130,10 +130,10 @@ impl EvmApi for MockEvmApi { &mut self, _contract: Bytes20, _calldata: &[u8], - _gas_left: u64, - _gas_req: u64, + _gas_left: Gas, + _gas_req: Gas, _value: Bytes32, - ) -> (u32, u64, UserOutcomeKind) { + ) -> (u32, Gas, UserOutcomeKind) { unimplemented!() } @@ -141,9 +141,9 @@ impl EvmApi for MockEvmApi { &mut self, _contract: Bytes20, _calldata: &[u8], - _gas_left: u64, - _gas_req: u64, - ) -> (u32, u64, UserOutcomeKind) { + _gas_left: Gas, + _gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind) { unimplemented!() } @@ -151,9 +151,9 @@ impl EvmApi for MockEvmApi { &mut self, _contract: Bytes20, _calldata: &[u8], - _gas_left: u64, - _gas_req: u64, - ) -> (u32, u64, UserOutcomeKind) { + _gas_left: Gas, + _gas_req: Gas, + ) -> (u32, Gas, UserOutcomeKind) { unimplemented!() } @@ -161,8 +161,8 @@ impl EvmApi for MockEvmApi { &mut self, _code: Vec, _endowment: Bytes32, - _gas: u64, - ) -> (Result, u32, u64) { + _gas: Gas, + ) -> (Result, u32, Gas) { unimplemented!() } @@ -171,8 +171,8 @@ impl EvmApi for MockEvmApi { _code: Vec, _endowment: Bytes32, _salt: Bytes32, - _gas: u64, - ) -> (Result, u32, u64) { + _gas: Gas, + ) -> (Result, u32, Gas) { unimplemented!() } @@ -185,19 +185,19 @@ impl EvmApi for MockEvmApi { Ok(()) } - fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, u64) { + fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, Gas) { unimplemented!() } - fn account_code(&mut self, _address: Bytes20, _gas_left: u64) -> (VecReader, u64) { + fn account_code(&mut self, _address: Bytes20, _gas_left: Gas) -> (VecReader, Gas) { unimplemented!() } - fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) { + fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, Gas) { unimplemented!() } - fn add_pages(&mut self, pages: u16) -> u64 { + fn add_pages(&mut self, pages: u16) -> Gas { let model = MemoryModel::new(2, 1000); unsafe { let (open, ever) = (OPEN_PAGES, EVER_PAGES); @@ -212,8 +212,8 @@ impl EvmApi for MockEvmApi { _name: &str, _args: &[u8], _outs: &[u8], - _start_ink: u64, - _end_ink: u64, + _start_ink: Ink, + _end_ink: Ink, ) { unimplemented!() } diff --git a/arbitrator/wasm-testsuite/src/main.rs b/arbitrator/wasm-testsuite/src/main.rs index cb861e3a35..2144dcf992 100644 --- a/arbitrator/wasm-testsuite/src/main.rs +++ b/arbitrator/wasm-testsuite/src/main.rs @@ -343,7 +343,6 @@ fn main() -> eyre::Result<()> { GlobalState::default(), HashMap::default(), machine::get_empty_preimage_resolver(), - false, ); if let Err(error) = &mech { @@ -430,7 +429,6 @@ fn main() -> eyre::Result<()> { GlobalState::default(), HashMap::default(), machine::get_empty_preimage_resolver(), - false, ) .expect_err(&format!("failed to reject invalid module {}", filename)); } diff --git a/arbnode/api.go b/arbnode/api.go index 228ad51cf8..2dabd41bff 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -7,9 +7,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethdb" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_api" ) type BlockValidatorAPI struct { @@ -54,3 +56,8 @@ func (a *BlockValidatorDebugAPI) ValidateMessageNumber( result.Valid = valid return result, err } + +func (a *BlockValidatorDebugAPI) ValidationInputsAt(ctx context.Context, msgNum hexutil.Uint64, target ethdb.WasmTarget, +) (server_api.InputJSON, error) { + return a.val.ValidationInputsAt(ctx, arbutil.MessageIndex(msgNum), target) +} diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 44b360e76e..d534c39c5e 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -98,7 +98,6 @@ type BatchPoster struct { arbOSVersionGetter execution.FullExecutionClient config BatchPosterConfigFetcher seqInbox *bridgegen.SequencerInbox - bridge *bridgegen.Bridge syncMonitor *SyncMonitor seqInboxABI *abi.ABI seqInboxAddr common.Address @@ -309,10 +308,7 @@ func NewBatchPoster(ctx context.Context, opts *BatchPosterOpts) (*BatchPoster, e if err != nil { return nil, err } - bridge, err := bridgegen.NewBridge(opts.DeployInfo.Bridge, opts.L1Reader.Client()) - if err != nil { - return nil, err - } + if err = opts.Config().Validate(); err != nil { return nil, err } @@ -340,7 +336,6 @@ func NewBatchPoster(ctx context.Context, opts *BatchPosterOpts) (*BatchPoster, e arbOSVersionGetter: opts.VersionGetter, syncMonitor: opts.SyncMonitor, config: opts.Config, - bridge: bridge, seqInbox: seqInbox, seqInboxABI: seqInboxABI, seqInboxAddr: opts.DeployInfo.SequencerInbox, @@ -716,12 +711,14 @@ type batchSegments struct { } type buildingBatch struct { - segments *batchSegments - startMsgCount arbutil.MessageIndex - msgCount arbutil.MessageIndex - haveUsefulMessage bool - use4844 bool - muxBackend *simulatedMuxBackend + segments *batchSegments + startMsgCount arbutil.MessageIndex + msgCount arbutil.MessageIndex + haveUsefulMessage bool + use4844 bool + muxBackend *simulatedMuxBackend + firstNonDelayedMsg *arbostypes.MessageWithMetadata + firstUsefulMsg *arbostypes.MessageWithMetadata } func newBatchSegments(firstDelayed uint64, config *BatchPosterConfig, backlog uint64, use4844 bool) *batchSegments { @@ -1176,12 +1173,6 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) // There's nothing after the newest batch, therefore batch posting was not required return false, nil } - firstMsg, err := b.streamer.GetMessage(batchPosition.MessageCount) - if err != nil { - return false, err - } - // #nosec G115 - firstMsgTime := time.Unix(int64(firstMsg.Message.Header.Timestamp), 0) lastPotentialMsg, err := b.streamer.GetMessage(msgCount - 1) if err != nil { @@ -1189,7 +1180,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } config := b.config() - forcePostBatch := config.MaxDelay <= 0 || time.Since(firstMsgTime) >= config.MaxDelay + forcePostBatch := config.MaxDelay <= 0 var l1BoundMaxBlockNumber uint64 = math.MaxUint64 var l1BoundMaxTimestamp uint64 = math.MaxUint64 @@ -1301,6 +1292,9 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) forcePostBatch = true } b.building.haveUsefulMessage = true + if b.building.firstUsefulMsg == nil { + b.building.firstUsefulMsg = msg + } break } if config.CheckBatchCorrectness { @@ -1311,13 +1305,28 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } if msg.Message.Header.Kind != arbostypes.L1MessageType_BatchPostingReport { b.building.haveUsefulMessage = true + if b.building.firstUsefulMsg == nil { + b.building.firstUsefulMsg = msg + } + } + if !isDelayed && b.building.firstNonDelayedMsg == nil { + b.building.firstNonDelayedMsg = msg } b.building.msgCount++ } - if hasL1Bound && config.ReorgResistanceMargin > 0 { - firstMsgBlockNumber := firstMsg.Message.Header.BlockNumber - firstMsgTimeStamp := firstMsg.Message.Header.Timestamp + firstUsefulMsgTime := time.Now() + if b.building.firstUsefulMsg != nil { + // #nosec G115 + firstUsefulMsgTime = time.Unix(int64(b.building.firstUsefulMsg.Message.Header.Timestamp), 0) + if time.Since(firstUsefulMsgTime) >= config.MaxDelay { + forcePostBatch = true + } + } + + if b.building.firstNonDelayedMsg != nil && hasL1Bound && config.ReorgResistanceMargin > 0 { + firstMsgBlockNumber := b.building.firstNonDelayedMsg.Message.Header.BlockNumber + firstMsgTimeStamp := b.building.firstNonDelayedMsg.Message.Header.Timestamp // #nosec G115 batchNearL1BoundMinBlockNumber := firstMsgBlockNumber <= arbmath.SaturatingUAdd(l1BoundMinBlockNumber, uint64(config.ReorgResistanceMargin/ethPosBlockTime)) // #nosec G115 @@ -1468,7 +1477,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) } tx, err := b.dataPoster.PostTransaction(ctx, - firstMsgTime, + firstUsefulMsgTime, nonce, newMeta, b.seqInboxAddr, diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 31b9a983db..373d247696 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -33,19 +34,18 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/signer/core/apitypes" - "github.com/go-redis/redis/v8" "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbnode/dataposter/dbstorage" "github.com/offchainlabs/nitro/arbnode/dataposter/noop" "github.com/offchainlabs/nitro/arbnode/dataposter/slice" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/blobs" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/util/signature" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/redis/go-redis/v9" "github.com/spf13/pflag" redisstorage "github.com/offchainlabs/nitro/arbnode/dataposter/redis" @@ -69,7 +69,7 @@ var ( type DataPoster struct { stopwaiter.StopWaiter headerReader *headerreader.HeaderReader - client arbutil.L1Interface + client *ethclient.Client auth *bind.TransactOpts signer signerFn config ConfigFetcher diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index d2c49427be..7bf0f86e6f 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -2,17 +2,18 @@ package dataposter import ( "context" + "errors" "fmt" "math/big" "testing" "time" "github.com/Knetic/govaluate" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -152,46 +153,36 @@ func TestMaxFeeCapFormulaCalculation(t *testing.T) { } } -type stubL1Client struct { +type stubL1ClientInner struct { senderNonce uint64 suggestedGasTipCap *big.Int - - // Define most of the required methods that aren't used by feeAndTipCaps - backends.SimulatedBackend -} - -func (c *stubL1Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { - return c.senderNonce, nil -} - -func (c *stubL1Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return c.suggestedGasTipCap, nil -} - -// Not used but we need to define -func (c *stubL1Client) BlockNumber(ctx context.Context) (uint64, error) { - return 0, nil -} - -func (c *stubL1Client) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) { - return []byte{}, nil } -func (c *stubL1Client) CodeAtHash(ctx context.Context, address common.Address, blockHash common.Hash) ([]byte, error) { - return []byte{}, nil +func (c *stubL1ClientInner) CallContext(ctx_in context.Context, result interface{}, method string, args ...interface{}) error { + switch method { + case "eth_getTransactionCount": + ptr, ok := result.(*hexutil.Uint64) + if !ok { + return errors.New("result is not a *hexutil.Uint64") + } + *ptr = hexutil.Uint64(c.senderNonce) + case "eth_maxPriorityFeePerGas": + ptr, ok := result.(*hexutil.Big) + if !ok { + return errors.New("result is not a *hexutil.Big") + } + *ptr = hexutil.Big(*c.suggestedGasTipCap) + } + return nil } -func (c *stubL1Client) ChainID(ctx context.Context) (*big.Int, error) { +func (c *stubL1ClientInner) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*rpc.ClientSubscription, error) { return nil, nil } - -func (c *stubL1Client) Client() rpc.ClientInterface { +func (c *stubL1ClientInner) BatchCallContext(ctx context.Context, b []rpc.BatchElem) error { return nil } - -func (c *stubL1Client) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { - return common.Address{}, nil -} +func (c *stubL1ClientInner) Close() {} func TestFeeAndTipCaps_EnoughBalance_NoBacklog_NoUnconfirmed_BlobTx(t *testing.T) { conf := func() *DataPosterConfig { @@ -223,10 +214,10 @@ func TestFeeAndTipCaps_EnoughBalance_NoBacklog_NoUnconfirmed_BlobTx(t *testing.T extraBacklog: func() uint64 { return 0 }, balance: big.NewInt(0).Mul(big.NewInt(params.Ether), big.NewInt(10)), usingNoOpStorage: false, - client: &stubL1Client{ + client: ethclient.NewClient(&stubL1ClientInner{ senderNonce: 1, suggestedGasTipCap: big.NewInt(2 * params.GWei), - }, + }), auth: &bind.TransactOpts{ From: common.Address{}, }, @@ -354,10 +345,10 @@ func TestFeeAndTipCaps_RBF_RisingBlobFee_FallingBaseFee(t *testing.T) { extraBacklog: func() uint64 { return 0 }, balance: big.NewInt(0).Mul(big.NewInt(params.Ether), big.NewInt(10)), usingNoOpStorage: false, - client: &stubL1Client{ + client: ethclient.NewClient(&stubL1ClientInner{ senderNonce: 1, suggestedGasTipCap: big.NewInt(2 * params.GWei), - }, + }), auth: &bind.TransactOpts{ From: common.Address{}, }, diff --git a/arbnode/dataposter/redis/redisstorage.go b/arbnode/dataposter/redis/redisstorage.go index 8b6dcf65ac..b54abf618b 100644 --- a/arbnode/dataposter/redis/redisstorage.go +++ b/arbnode/dataposter/redis/redisstorage.go @@ -9,9 +9,9 @@ import ( "errors" "fmt" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/util/signature" + "github.com/redis/go-redis/v9" ) // Storage implements redis sorted set backed storage. It does not support @@ -196,7 +196,7 @@ func (s *Storage) Put(ctx context.Context, index uint64, prev, new *storage.Queu if err != nil { return err } - if err := pipe.ZAdd(ctx, s.key, &redis.Z{ + if err := pipe.ZAdd(ctx, s.key, redis.Z{ Score: float64(index), Member: string(signedItem), }).Err(); err != nil { diff --git a/arbnode/dataposter/testdata/client.crt b/arbnode/dataposter/testdata/client.crt index 3d494be820..9171094ba5 100644 --- a/arbnode/dataposter/testdata/client.crt +++ b/arbnode/dataposter/testdata/client.crt @@ -1,28 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIE0jCCA7qgAwIBAgIUPaBB3/hHMpZfGB3VOw1+mHG4LnUwDQYJKoZIhvcNAQEL +MIIEIjCCAwqgAwIBAgIUV1axsouzA9h1Vgr2cPv17AvUrKswDQYJKoZIhvcNAQEL BQAwgYMxCzAJBgNVBAYTAkNIMQswCQYDVQQIDAJaSDEPMA0GA1UEBwwGWnVyaWNo MRYwFAYDVQQKDA1PZmZjaGFpbiBMYWJzMRIwEAYDVQQDDAlsb2NhbGhvc3QxKjAo -BgkqhkiG9w0BCQEWG25vdGFiaWdkZWFsQG9mZmNoYWlubGFicy5jaDAeFw0yMzEw -MTYxNDU2MjhaFw0yNDEwMTUxNDU2MjhaMIGDMQswCQYDVQQGEwJDSDELMAkGA1UE -CAwCWkgxDzANBgNVBAcMBlp1cmljaDEWMBQGA1UECgwNT2ZmY2hhaW4gTGFiczES -MBAGA1UEAwwJbG9jYWxob3N0MSowKAYJKoZIhvcNAQkBFhtub3RhYmlnZGVhbEBv -ZmZjaGFpbmxhYnMuY2gwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1 -1asfUzv07QTVwlM4o3g51ilIFEApPkpdQej/GIItLEVRQW+GI9jYuEM07wdwMhSH -JPFNbZB3dmBuqDLx13hY03ufyeY+nab0/sO6x13kXChvIqgPRyJtkEAoYkMM3W0D -S6HeL/6DFoTQ2xAlZb/7i/9deuUwDL3MNVSjPCm9PjFzSOFgAQQud2uUT7aENGuG -Whw3oXz9gU/8gv3keLzcIa2PHyEW5M7jeGSYMjfW3wr0d+Z5mSNRc/U6kncKi06c -QrMKrgFfF7a5kHgxUL7bRCGgCMemXe7VfrW6oKT11JcLWDKhe+uo6bNXUptek55H -HfQi6x8cbM46/h3riZA3AgMBAAGjggE6MIIBNjAdBgNVHQ4EFgQUQD2BOems0+JQ -br234cW5noMmXRIwga0GA1UdIwSBpTCBoqGBiaSBhjCBgzELMAkGA1UEBhMCQ0gx -CzAJBgNVBAgMAlpIMQ8wDQYDVQQHDAZadXJpY2gxFjAUBgNVBAoMDU9mZmNoYWlu -IExhYnMxEjAQBgNVBAMMCWxvY2FsaG9zdDEqMCgGCSqGSIb3DQEJARYbbm90YWJp -Z2RlYWxAb2ZmY2hhaW5sYWJzLmNoghQ9oEHf+Ecyll8YHdU7DX6YcbgudTAJBgNV -HRMEAjAAMAsGA1UdDwQEAwIFoDAfBgNVHREEGDAWgglsb2NhbGhvc3SCCTEyNy4w -LjAuMTAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh -dGUwDQYJKoZIhvcNAQELBQADggEBAF4EVkOZZeMIvv0JViP7NsmIl2ke/935x6Hd -hQiLUw13XHYXzMa5/8Y5fnKjttBODpFoQlwjgI18vzuYzItYMBc2cabQJcpfG+Wq -M3m/wl1TC2XOuHj1E4RA/nU3tslntahtXG+vkks9RN+f9irHUhDRR6AGSnSB2Gi/ -B2OGmXn7S4Qge8+fGHAjN+tlu+tOoEWP6R3if/a9UIe5EGM8QTe4zw6lr+iPrOhC -M94pK5IEWn5IIGhr3zJIYkm/Dp+rFqhV1sqPOjjFLVCA7KJ3jVVVHlcm4Xa/+fyk -CIm7/VAmnbeUNlMbkXNOfQMeku8Iwsu80pvf3kjhU/PgO/5oojk= +BgkqhkiG9w0BCQEWG25vdGFiaWdkZWFsQG9mZmNoYWlubGFicy5jaDAgFw0yNDEw +MTYwMzI1NDdaGA8yMTI0MDkyMjAzMjU0N1owgYMxCzAJBgNVBAYTAkNIMQswCQYD +VQQIDAJaSDEPMA0GA1UEBwwGWnVyaWNoMRYwFAYDVQQKDA1PZmZjaGFpbiBMYWJz +MRIwEAYDVQQDDAlsb2NhbGhvc3QxKjAoBgkqhkiG9w0BCQEWG25vdGFiaWdkZWFs +QG9mZmNoYWlubGFicy5jaDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AKU5q5iwYV4/gPeWcyys561pTGV4pk+sRY2q0znsZFOYcxrjaXEjj2HGNkvH1rKy +8Cv1ZoFW+1ejQZeLtd0qL9v5fDkdLsmCZaIYI5Bvo2CfY6KLUZ5c1q2K2GZgQk8i +eSbqBXq+F/EwziDfheXkhDoAE05hOg684titb21eJ0ZK7f7Koam7cmbQI0lqUCrt +MLp0cJzWnfW0SpCzahnCZ5h31BZeZIRLOxsTvg5N1wOivrdWLXGVbprNCGGhVg0E +ZxhwI00pU/E/K4mcKjtPy/5fqe71jH7/iLYNmhRp6PrA78GilxTT79rro8ooantD +GyQbm+Qkk2tMHHum3GOcjuECAwEAAaOBiTCBhjAdBgNVHQ4EFgQUIUFF6jA0NkRA +1kZhJKH0W/9zJWIwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHwYDVR0RBBgwFoIJ +bG9jYWxob3N0ggkxMjcuMC4wLjEwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu +ZXJhdGVkIENlcnRpZmljYXRlMA0GCSqGSIb3DQEBCwUAA4IBAQCkfknujeFa0yf4 +YX/3ltP9itq4hLtAYnQF7M/uC86QdyDPsrNqhvj54qC0BnR5wGeZP3c144J2mAUr +4j4Y/ztgFVBR4rLyatHgm0/tL/fy/UgjeSmpY4UOr1QnpNP3fIzL7hxacS4uO8v4 +wcc5KlG/xjHRcrzJaaWLldCogBMb8vlModcbeKrkvQ4hUF+zf138RtpRfcRf1X5c +EaAtUZk+BxVYS79qL7YyESRD8YYMhIImLuiyPt2V3HQRhrjqa3mzODBLhUbNRWPX +8/CH2UZ6TD9Hy4FVX0VZzLoDZjfi4KCTgXI3WGrDoL4FF26cSiK8HVx0qJzAWw4a +tkkj5jtd -----END CERTIFICATE----- diff --git a/arbnode/dataposter/testdata/client.key b/arbnode/dataposter/testdata/client.key index b14941dd9f..4313d0f124 100644 --- a/arbnode/dataposter/testdata/client.key +++ b/arbnode/dataposter/testdata/client.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC11asfUzv07QTV -wlM4o3g51ilIFEApPkpdQej/GIItLEVRQW+GI9jYuEM07wdwMhSHJPFNbZB3dmBu -qDLx13hY03ufyeY+nab0/sO6x13kXChvIqgPRyJtkEAoYkMM3W0DS6HeL/6DFoTQ -2xAlZb/7i/9deuUwDL3MNVSjPCm9PjFzSOFgAQQud2uUT7aENGuGWhw3oXz9gU/8 -gv3keLzcIa2PHyEW5M7jeGSYMjfW3wr0d+Z5mSNRc/U6kncKi06cQrMKrgFfF7a5 -kHgxUL7bRCGgCMemXe7VfrW6oKT11JcLWDKhe+uo6bNXUptek55HHfQi6x8cbM46 -/h3riZA3AgMBAAECggEADUboCYMCpm+LqIhzNCtqswQD6QsiSwCmqs8nuKZGk9ue -+hmZj5IpgMJZLrgvWY4s+PGfgiRR/28QCBrVXkETiZ5zirQFN4tvLlKcSK4xZf29 -FBRUCiPxck36NhiqrBNOi1Mn8BKedl4cESkvSu1cvcmeOh100HPcHfLDVqHx3qsl -D/5yMkT2+zdhtLa+X3nkAa+3aibOvgtyfkV679e20CG6h89N9GBKkTXO8ioLZZVm -84ksnd4FcpTo7ebJJxElEB+ZA4akPHbF6ArUmcpqtGso5GtwqqO2ZlguSn2XQT0d -jqvOG4DwfSXk6SpE/dpWvU92fmxWAxZvGrZNgDyJ2QKBgQDyQ8NN4b80Yza/YXar -LWx8A6B0eMc1dXgt9m3UUI+titt45jEcaXhCX01FRFTznWGmWFtJmcWBoaQVPVel -IcDYQSxEuBUrCeI75ocv/IQtENaiX3TK7Nlz5RHfpQpfDVJq45lpiD38CGkYkAif -9pSzC8aup4W3WR0JJZ1AOHUZaQKBgQDAJNJnaSNzB+eDWTKCIN5V9X3QMkmjsuir -Nf2lBXHYARnlYWAbtYFG12wLJQMTNX5ewVQQrWtsdPkGPpCnPLelUTxMssrsXjej -JlLzYUfzRBqEXMI3AA9bVdiauxId2RTcp2F81SM1keCMcuHYxrzVkBSOC9u3wCnb -Whb6+feInwKBgQCbzgC5AcoaQwReqKvNAvWV/C8hONvFAbs8tBOGTBlbHsZvRnun -Lh1tciUbuwp3cmvuszxiZUakS/RexIitZrvDWIbD2y+h8kVRCL1Am0HWSdH/syxF -pXVkF5obHuVApCyxGZb8S+axRCdy6I7jcY3IaHZqtMpGVEVcMJilSKnmoQKBgQCC -tEmgaMfhhx34nqOaG4vDA4T7LEolnh1h4g9RwztnCZC5FZ1QHA79xqrLhfjqhzgY -cwChe6aYl5WSptq1uLrgLTuMnQ8m7QyB4h8JSkKse8ZiBctjqJnJssLutpSjUzk6 -xG2vgjk6RqpuP/PcB40K5cDlw7FJ9OFEQqthPMsi1wKBgQC0/vv5bY3DQ+wV6gUy -nFoSa/XNHaa8y7jmmlCnWJqs6DAAQQ3VW0tPX03GYL/NDcI+PwzYDHDkSB6Qa/o8 -VzVGK1/kr/+bveNvqmi0vNb54fMFLveGgsY4Cu1cffiw8m6nYJ/V4eCsHfpF1B5L -5HDnt5rFKt1Mi9WsUSRtxipxBA== +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQClOauYsGFeP4D3 +lnMsrOetaUxleKZPrEWNqtM57GRTmHMa42lxI49hxjZLx9aysvAr9WaBVvtXo0GX +i7XdKi/b+Xw5HS7JgmWiGCOQb6Ngn2Oii1GeXNatithmYEJPInkm6gV6vhfxMM4g +34Xl5IQ6ABNOYToOvOLYrW9tXidGSu3+yqGpu3Jm0CNJalAq7TC6dHCc1p31tEqQ +s2oZwmeYd9QWXmSESzsbE74OTdcDor63Vi1xlW6azQhhoVYNBGcYcCNNKVPxPyuJ +nCo7T8v+X6nu9Yx+/4i2DZoUaej6wO/BopcU0+/a66PKKGp7QxskG5vkJJNrTBx7 +ptxjnI7hAgMBAAECggEAAagHWVuDTl+SmmjOtMby96ETm/zOpgPTGq14up7tDo17 +sexPtUum91L2XmIde+MhVz95jJhjoqhHUw6afyIaIrlojmYFfw2omSxmxt7no2NV +q158Lfs+R7UZoEUcxRBSaJp1/ZoEQW2800WKYRieXrp7dxCwdU9dctCiSlVkTWcU +w+f9xr084dUIKgtICbLWRdGDvGFmr99MBZXzHg5+x8MiAVtpiNcggRfQKIg2QYQv +xdUtfxrKHuRcbeo4QOgSR6fb772F5eO6hPfwgl0AqmSX9XyRaPox/vKcq531S95S +JvGeAdS47Qo8Elh9rIlC/pxVdJ/Gz4sbmlYCfcXDRQKBgQDbSFUrEaOnH5ITEDy/ +SDTCbdQ3bP1FmHVoLdCRoBohb0xHZrJoOn6cmxyyHWR6dwpv+rvi1bJCScDdphJM +zV8W5sG94PaM/dwCws4CFAwaAlMakrsNUXtMgIeub27mzX5OMTss8vjKdKbVDyAv +XCT4idJY1EOdLA3R+JTLSszxJwKBgQDA5CSKLn4HpZI6qmR5g7HRZ5g249BbX/q8 +oszAUIFfY0ME5aujWYRmTfdWno0Y2yG9x4g9QDVNK9fUH3Ii6pNRFGc/yF3HkbsP +kT6UW6rw9CeyyYPKjrFx7M+2kBWJ16+5noVvzWhLScMCt7IcVCKaqiJFapOkS75t +zBYH1IX0twKBgCtFBqlM/cIIlMZ2OcZ09RQ4n9ugAgotn11DTRivQvi+AYtFVIcE +o987LFppOl6ABus5ysFj8Zzq+MfD8XB+Rfk655gUQBJqNXPGBOicFBc9xjBEK+zg +2zepVRyymGuquPWs+URRXY51nkYEihFOWW1BpOQqXn0xKDj6mEHVLMOZAoGAc7Ol +k1ll8ZJIT3ZLxHPRYqmALVSjc1v0G9iPdsATijMRTUuyk84rU+5qcYOzYPh4mcyp +FQyBrGOjF7MxFG6epSDW+fRnBEGO8jyOTBFcTSI2+dBUhFjpaUvCIGD2+nLtDitf +IPwWFisNlYC4jrOM+jcZTYgrPX7NoDCt+k5pd6sCgYB7NMYEC9XjoXV8S3n2yni5 +y0oGLox39hh31LJ90yQ/l+oFJWn1BPnzTI6xeJTTzJjQf7padyvfw45b+3BywZHM +TI5LWcIaA6L+tiBlgBjYa7gE3CCxh0AdV+pUa8L/R6OWAK6+lg2zNt1/ommZ2sKg +LcbNAqMiMWH1a1el7idoBA== -----END PRIVATE KEY----- diff --git a/arbnode/dataposter/testdata/localhost.crt b/arbnode/dataposter/testdata/localhost.crt index ca33dfc8cc..8b7fb02c9a 100644 --- a/arbnode/dataposter/testdata/localhost.crt +++ b/arbnode/dataposter/testdata/localhost.crt @@ -1,28 +1,24 @@ -----BEGIN CERTIFICATE----- -MIIEwzCCA6ugAwIBAgIUHx3SdpCP5jXZE7USUqX5uRNFKPIwDQYJKoZIhvcNAQEL -BQAwfzELMAkGA1UEBhMCQ0gxCzAJBgNVBAgMAlpIMQ8wDQYDVQQHDAZadXJpY2gx -FjAUBgNVBAoMDU9mZmNoYWluIExhYnMxEjAQBgNVBAMMCWxvY2FsaG9zdDEmMCQG -CSqGSIb3DQEJARYXYmlnZGVhbEBvZmZjaGFpbmxhYnMuY2gwHhcNMjMxMDE2MTQ0 -MDA1WhcNMjQxMDE1MTQ0MDA1WjB/MQswCQYDVQQGEwJDSDELMAkGA1UECAwCWkgx -DzANBgNVBAcMBlp1cmljaDEWMBQGA1UECgwNT2ZmY2hhaW4gTGFiczESMBAGA1UE -AwwJbG9jYWxob3N0MSYwJAYJKoZIhvcNAQkBFhdiaWdkZWFsQG9mZmNoYWlubGFi -cy5jaDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALg7XwaIh4l2Fp8a -MfNMdTQSMPMR0zpnicVTn/eiozWsqlAKaxmQM3PxJ0oVWW3iJ89p4rv5m+UjK6Dr -vsUQOzl8isgyGCTMnkLtxFlyallDNRDawRcuTPuNI9NkdJm+Zz7HooLzFeBDeS13 -iRPEXr1T/4af9MjOxqFvbw5xBY9k4tc2hPp6q00948gPWKIB9Mz4thoB2Hl2rQBY -X/WhjSnre9o9qoyBO0XAsG0mssBs1vPa9/aEp7C5cDY0HCuM1RIjhXnRpb8lC9VQ -aC+FozDffmm23EGVpLmyPs590UOtVJdTUd6Q0TAT6d7fjCRUJ12DendQf2uMFV90 -u6Yj0zUCAwEAAaOCATUwggExMB0GA1UdDgQWBBT2B3FTGFQ49JyBgDGLoZREOIGD -DTCBqAYDVR0jBIGgMIGdoYGEpIGBMH8xCzAJBgNVBAYTAkNIMQswCQYDVQQIDAJa -SDEPMA0GA1UEBwwGWnVyaWNoMRYwFAYDVQQKDA1PZmZjaGFpbiBMYWJzMRIwEAYD -VQQDDAlsb2NhbGhvc3QxJjAkBgkqhkiG9w0BCQEWF2JpZ2RlYWxAb2ZmY2hhaW5s -YWJzLmNoghQfHdJ2kI/mNdkTtRJSpfm5E0Uo8jAJBgNVHRMEAjAAMAsGA1UdDwQE -AwIFoDAfBgNVHREEGDAWgglsb2NhbGhvc3SCCTEyNy4wLjAuMTAsBglghkgBhvhC -AQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQEL -BQADggEBAIkhBcnLeeNwUwb+sSG4Qm8JdeplHPMeViNfFIflUfIIYS00JA2q9w8W -+6Nh8s6Dn20lQETUnesYj97BdqzLjFuJYAlblhE+zP8g/3Mkpu+wZAGvQjUIRyGT -C17BEtQQgAnv5pD22jr9hpLl2KowN6Oo1gzilCA+AtMkNZFIGDOxzuIv2u8rSD89 -R/V6UEDMCgusFJnZ/GzKkUNbsrAfNUezNUal+KzMhHGHBwg4jfCNhnAAB43eRtJA -0pSRMMLcUEQnVotXDXYC3DhJmkYp1uXOH/tWs6z9xForOkWFxNMVj+zUWBi7n3Jw -N2BXlb64D96uor13U0dmvQJ72ooJc+A= +MIIEFzCCAv+gAwIBAgITSiI3ITH8yVNHC4Bh412fjdSR2TANBgkqhkiG9w0BAQsF +ADB/MQswCQYDVQQGEwJDSDELMAkGA1UECAwCWkgxDzANBgNVBAcMBlp1cmljaDEW +MBQGA1UECgwNT2ZmY2hhaW4gTGFiczESMBAGA1UEAwwJbG9jYWxob3N0MSYwJAYJ +KoZIhvcNAQkBFhdiaWdkZWFsQG9mZmNoYWlubGFicy5jaDAgFw0yNDEwMTYwMzI1 +NDdaGA8yMTI0MDkyMjAzMjU0N1owfzELMAkGA1UEBhMCQ0gxCzAJBgNVBAgMAlpI +MQ8wDQYDVQQHDAZadXJpY2gxFjAUBgNVBAoMDU9mZmNoYWluIExhYnMxEjAQBgNV +BAMMCWxvY2FsaG9zdDEmMCQGCSqGSIb3DQEJARYXYmlnZGVhbEBvZmZjaGFpbmxh +YnMuY2gwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDK4BRVZ0nU98/f +l+QC4fF60oNtlMPCC/2R6GZoWz7VyhrrXBuol+F9vboAePxLgxeIr/pwHAbFWlW2 +ueAsN9dorC2waf5PDhfOE0gI6w7LysTkO5n7oMFf1KYPSpPJ15WxlobZR8qWeroR +we7z44tQ2F+es+HaqBrrk7jm0GS9AqaledN/ay9SP4CBu029F6nWDnK+VpNWuoN4 +A/pnwGFWrxDf0ftN7BxnxzzdsWs64+kYfz91Mojce2UKGuDTuk/oqOnHhX34bFDc +/e9KGAQqP1I+RuCJmQXW5b55+3WgpvT3u3Mp7478C+AK8GthPjja7go48nHp3uby +drNpTw+bAgMBAAGjgYkwgYYwHQYDVR0OBBYEFG09BO7OJcjB3fRFhPCsjQ6ICb2E +MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMB8GA1UdEQQYMBaCCWxvY2FsaG9zdIIJ +MTI3LjAuMC4xMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0 +aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEAJHG/5WOmpO7wg3BtTt4b0DoqDJjh +eQb9Woq5xbvliZ1RCp8E6m6BmOr66i4qu5r+31DEeAeQ2M9pG2nJKoayCVi2ygaQ +RAulxIH7o5+JUcZtX6FbRBsS7Go+SLmtkkJ89YVSIF40+2CAQs7loqQjHNeo9/iO +rVKt1Fa6rQhXmv4ItVOwRaMBvXRVw4gc3ObmH0ZBYZrvsE7uQkKX5f6sVKXOX3mm +ofyB+22QMYmx3XvEEQm8ELnjIr5Q8LxqQxHqjLFjyrcrXYVi4+3/PfjIdRr5+qes +H8JWJlAbF/SNncdXRb1jtkdxit56Qo7/Mz/c4Yuh1WLiYcQGJeBpr53dmg== -----END CERTIFICATE----- diff --git a/arbnode/dataposter/testdata/localhost.key b/arbnode/dataposter/testdata/localhost.key index aad9b40b3d..f56aef1e77 100644 --- a/arbnode/dataposter/testdata/localhost.key +++ b/arbnode/dataposter/testdata/localhost.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4O18GiIeJdhaf -GjHzTHU0EjDzEdM6Z4nFU5/3oqM1rKpQCmsZkDNz8SdKFVlt4ifPaeK7+ZvlIyug -677FEDs5fIrIMhgkzJ5C7cRZcmpZQzUQ2sEXLkz7jSPTZHSZvmc+x6KC8xXgQ3kt -d4kTxF69U/+Gn/TIzsahb28OcQWPZOLXNoT6eqtNPePID1iiAfTM+LYaAdh5dq0A -WF/1oY0p63vaPaqMgTtFwLBtJrLAbNbz2vf2hKewuXA2NBwrjNUSI4V50aW/JQvV -UGgvhaMw335pttxBlaS5sj7OfdFDrVSXU1HekNEwE+ne34wkVCddg3p3UH9rjBVf -dLumI9M1AgMBAAECggEAHuc8oyKrQ5xmooUZHGP2pAeqJNfYXAtqoYpLwtUJ9hKy -1e7NdNIKw3fP/J4UrHk7btAm65us8hSCeMGatEErAhNZT0gR4zhcksMCBPQLkVIT -+HINYjdOzAJqoEbRRUnaVT5VDQy8HmyLCtyqhoGR18XbjshNnhKLYKCJ2z0Lrvf2 -3rU7bbt7/rvLitVhxVL8SIe2jWSfIgcEmEAZMigB9WAnUyQ/tAfbPy1I764LLfzD -nLXn7E2OH7GrxkLjOsH9kfERlur7V7IhC9NE/wI0q+rnILRa7Q3+ifRu8qla3bo1 -iyHl1ZmsYJ8Jnzbu9exzZaQmk42OoFPcMFm0mRe+2QKBgQDvRv0Q5JhBuVurkU98 -lzATwEO0uYmeWDMnHzrFSWAKr/x4LNQ9ytSCfe1aLxgOkZq6dQ3TyZiCYzpmwGz9 -K7/gghxmsVDKeCqiGVZOgFAWy7AhQyF6zM60oqqwSvJHhmGTsA/B5LPUiYe9lITW -ZSLVYkOzha7Coa++U8vPzI5VaQKBgQDFG4reFT79j8RKEm9jie6PdRdYMzOSDWty -Gjj5N9Jnlp1k/6RzCxjmp7w7yIorq/7fWZsQtt0UqgayOn25+I8dZeGC0BradUSB -tZbGElxPsF8Jg00ZvvK3G5mpZYDrJCud8Q05EaUZPXv9GuZhozEsTQgylVecVzsN -wyEK8VuZ7QKBgQChx9adUGIdtgzkILiknbh08j8U94mz1SCo5/WdpLHaKAlE29KZ -AQXUQP51Rng2iX4bab9yndCPADZheON3/debHX3EdUkRzFPPC+CN7TW5Y/jvVGtT -kxyDh6Ru1A2iDJr290iAKXjpUB/GL5/tMa5upiTuQYnasOWZgyC/nCf0WQKBgEwn -pRLDMLA1IMjhsInL3BEvU1KvjahLaQ0P1p1rlO6TAcLpBrewPPG5MwACLmhLLtFK -xJ/Dl02Jl8a61KLKxzi7iVLKZuWq00ouR8/FfkcHxOBfC6X74bkff9I0NogjVHrU -jKBVEe3blJEpGIP20mPka1tn2g68oUNi9dxNfm/NAoGAWj/Q0pgnNq0MQ8Lj6m99 -1baaXSo8biks3E3A3cqhHQm/j3SRnkf0lueQW8+r9yR9IWdYFXz5Waq13qK+lopE -KDmww0xr8dyMUYTP1vde7np2XKa/OX3iejDzbI3RcZN/DEV+dCBY8pqHHfaAaESu -fwBWvfD8wtwCZzB3lOZEi80= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDK4BRVZ0nU98/f +l+QC4fF60oNtlMPCC/2R6GZoWz7VyhrrXBuol+F9vboAePxLgxeIr/pwHAbFWlW2 +ueAsN9dorC2waf5PDhfOE0gI6w7LysTkO5n7oMFf1KYPSpPJ15WxlobZR8qWeroR +we7z44tQ2F+es+HaqBrrk7jm0GS9AqaledN/ay9SP4CBu029F6nWDnK+VpNWuoN4 +A/pnwGFWrxDf0ftN7BxnxzzdsWs64+kYfz91Mojce2UKGuDTuk/oqOnHhX34bFDc +/e9KGAQqP1I+RuCJmQXW5b55+3WgpvT3u3Mp7478C+AK8GthPjja7go48nHp3uby +drNpTw+bAgMBAAECggEANE2Q8HOwlTdOYFbIcfXOS9v6BkZUMbLlrLg9rqnXiUaR +qhwVBWIiwEgpq/WFFfK2HodACacwF7EyZ+mD4eKDpni9Tr4E0lzPxlEyQRpYtjGQ +kUbMbBMFx68LIOYZM/Bgp2gnW90mXaVGU02sTTRctnsSK9g0Yir0xcdP5DHVxuRy +cReMIDjHu7/PN5X12oHpBxT1w7TKYZk3M2ZbmRcGfRSv1UIg06hXjxY4Tonqxv5b +Yv/R/r+RYsHE6cO3hLV7FE/ypaRFGt/qcHhqwmHKlpc+8/FNEpb5truXCbf5LhjM +YD2eMOpiBaiyufs+2BpUy/iDKToYz+fdusT5N3FgAQKBgQDuemOauY/ytbETUabB +Vt11fCvXvsx4L+45vqtVcRWl6jcy73rMnyA80D/ndzyxkuw2NRAllIUvsdVG26vM +8nWAxrsY7rrZ4kdRAAKAPYmTtT2O4mvhvJOYHA/ueEUamrKqZMgyLik1Wl1y2mt2 +Iak0zNB0GWoHsgDF20TTbxKTkwKBgQDZyAa/r3qhokKfvg/7LhwXH7vdiJd35zp1 +K1KZVeZf97FeReL4DLfTHZ92yFyVhWepp97Icd2WtQZ8jVVcesTP9C+mVoFEGXZf +nWx5y2WD92dBP/kKYNayXuBFQnRfS4AC7ALK8fKrU/Fzc7OctnkvHHWZfaKQ2sQ5 +xydj6Rso2QKBgBpgvT2zAsIU6MY7RNej1REWr/7IIvO0UYRfm7HytTNJ6dsfdBTI +ERfI7RicLsFxf+ErE2Mkv2qcH/wbdjBQLUEWOkGyvkY1ajACcURgCiSlam6wisBI +TIcJq5V0BijALbz9MsuiIXq+SRHYKQTDCmVFtlTxLrI1NTKtYzqD0akzAoGAB5fW +zGYk42/R3Nn2mq5f4lqD5VR224JfYmhxR9Fb5+qt73iGUlm3KxA0WCLiP4BYPe0R +cnGt5SxInp0a5c+N/yYnZyhK94Hfw7OsbY6u6mv82KSPXVJFChEOxrtrbUsnmnJ6 +InNPH7QcjgbxszwVe5QFcaWUvnIyN0V/VRdyj/kCgYEA0kPCIPR7t7OFEnQsw50W +AclrTlGRB0TzmCYZN4DTKC+1ZAV80XQUR4umJpHeyArdRZpFv+HBLF7fHsBnkmiU +ixBdbWgW4mxjAhyZkLMjcCkoiLmRPDUvELyFs4xpM+IEKoAk60PPbGi4CeIMHb5k +E47NZaw1bdG1tv2ya1FlQ/k= -----END PRIVATE KEY----- diff --git a/arbnode/dataposter/testdata/regenerate-certs.sh b/arbnode/dataposter/testdata/regenerate-certs.sh new file mode 100755 index 0000000000..6bcbd27b8e --- /dev/null +++ b/arbnode/dataposter/testdata/regenerate-certs.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -eu +cd "$(dirname "$0")" +for name in localhost client; do + openssl genrsa -out "$name.key" 2048 + csr="$(openssl req -new -key "$name.key" -config "$name.cnf" -batch)" + openssl x509 -req -signkey "$name.key" -out "$name.crt" -days 36500 -extensions req_ext -extfile "$name.cnf" <<< "$csr" +done diff --git a/arbnode/delayed.go b/arbnode/delayed.go index 082f0ecf9d..354fa671b3 100644 --- a/arbnode/delayed.go +++ b/arbnode/delayed.go @@ -19,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbutil" @@ -58,11 +59,11 @@ type DelayedBridge struct { con *bridgegen.IBridge address common.Address fromBlock uint64 - client arbutil.L1Interface + client *ethclient.Client messageProviders map[common.Address]*bridgegen.IDelayedMessageProvider } -func NewDelayedBridge(client arbutil.L1Interface, addr common.Address, fromBlock uint64) (*DelayedBridge, error) { +func NewDelayedBridge(client *ethclient.Client, addr common.Address, fromBlock uint64) (*DelayedBridge, error) { con, err := bridgegen.NewIBridge(addr, client) if err != nil { return nil, err diff --git a/arbnode/inbox_reader.go b/arbnode/inbox_reader.go index 816d6d7a31..14ca83ab13 100644 --- a/arbnode/inbox_reader.go +++ b/arbnode/inbox_reader.go @@ -14,6 +14,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" flag "github.com/spf13/pflag" @@ -93,7 +94,7 @@ type InboxReader struct { delayedBridge *DelayedBridge sequencerInbox *SequencerInbox caughtUpChan chan struct{} - client arbutil.L1Interface + client *ethclient.Client l1Reader *headerreader.HeaderReader // Atomic @@ -101,7 +102,7 @@ type InboxReader struct { lastReadBatchCount atomic.Uint64 } -func NewInboxReader(tracker *InboxTracker, client arbutil.L1Interface, l1Reader *headerreader.HeaderReader, firstMessageBlock *big.Int, delayedBridge *DelayedBridge, sequencerInbox *SequencerInbox, config InboxReaderConfigFetcher) (*InboxReader, error) { +func NewInboxReader(tracker *InboxTracker, client *ethclient.Client, l1Reader *headerreader.HeaderReader, firstMessageBlock *big.Int, delayedBridge *DelayedBridge, sequencerInbox *SequencerInbox, config InboxReaderConfigFetcher) (*InboxReader, error) { err := config().Validate() if err != nil { return nil, err diff --git a/arbnode/inbox_test.go b/arbnode/inbox_test.go index d579b7c278..e588ef399b 100644 --- a/arbnode/inbox_test.go +++ b/arbnode/inbox_test.go @@ -74,7 +74,7 @@ func NewTransactionStreamerForTest(t *testing.T, ownerAddress common.Address) (* } stylusTargetConfig := &gethexec.DefaultStylusTargetConfig Require(t, stylusTargetConfig.Validate()) // pre-processes config (i.a. parses wasmTargets) - if err := execEngine.Initialize(gethexec.DefaultCachingConfig.StylusLRUCache, stylusTargetConfig); err != nil { + if err := execEngine.Initialize(gethexec.DefaultCachingConfig.StylusLRUCacheCapacity, &gethexec.DefaultStylusTargetConfig); err != nil { Fail(t, err) } execSeq := &execClientWrapper{execEngine, t} diff --git a/arbnode/inbox_tracker.go b/arbnode/inbox_tracker.go index d2ab5ef1f5..d5afa142d8 100644 --- a/arbnode/inbox_tracker.go +++ b/arbnode/inbox_tracker.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -599,7 +600,7 @@ type multiplexerBackend struct { positionWithinMessage uint64 ctx context.Context - client arbutil.L1Interface + client *ethclient.Client inbox *InboxTracker } @@ -639,7 +640,7 @@ func (b *multiplexerBackend) ReadDelayedInbox(seqNum uint64) (*arbostypes.L1Inco var delayedMessagesMismatch = errors.New("sequencer batch delayed messages missing or different") -func (t *InboxTracker) AddSequencerBatches(ctx context.Context, client arbutil.L1Interface, batches []*SequencerInboxBatch) error { +func (t *InboxTracker) AddSequencerBatches(ctx context.Context, client *ethclient.Client, batches []*SequencerInboxBatch) error { var nextAcc common.Hash var prevbatchmeta BatchMetadata sequenceNumberToKeep := uint64(0) diff --git a/arbnode/maintenance.go b/arbnode/maintenance.go index 53d038a0f9..7397229c2e 100644 --- a/arbnode/maintenance.go +++ b/arbnode/maintenance.go @@ -101,7 +101,7 @@ func NewMaintenanceRunner(config MaintenanceConfigFetcher, seqCoordinator *SeqCo if seqCoordinator != nil { c := func() *redislock.SimpleCfg { return &cfg.Lock } r := func() bool { return true } // always ready to lock - rl, err := redislock.NewSimple(seqCoordinator.Client, c, r) + rl, err := redislock.NewSimple(seqCoordinator.RedisCoordinator().Client, c, r) if err != nil { return nil, fmt.Errorf("creating new simple redis lock: %w", err) } diff --git a/arbnode/node.go b/arbnode/node.go index a9da4ea24b..c5b3bbe071 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -18,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -407,7 +408,7 @@ func createNodeImpl( arbDb ethdb.Database, configFetcher ConfigFetcher, l2Config *params.ChainConfig, - l1client arbutil.L1Interface, + l1client *ethclient.Client, deployInfo *chaininfo.RollupAddresses, txOptsValidator *bind.TransactOpts, txOptsBatchPoster *bind.TransactOpts, @@ -781,7 +782,7 @@ func CreateNode( arbDb ethdb.Database, configFetcher ConfigFetcher, l2Config *params.ChainConfig, - l1client arbutil.L1Interface, + l1client *ethclient.Client, deployInfo *chaininfo.RollupAddresses, txOptsValidator *bind.TransactOpts, txOptsBatchPoster *bind.TransactOpts, diff --git a/arbnode/redislock/redis.go b/arbnode/redislock/redis.go index 7e26010cae..de9508323a 100644 --- a/arbnode/redislock/redis.go +++ b/arbnode/redislock/redis.go @@ -12,8 +12,8 @@ import ( "time" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/redis/go-redis/v9" flag "github.com/spf13/pflag" ) diff --git a/arbnode/seq_coordinator.go b/arbnode/seq_coordinator.go index a582b64ffa..5987801d5f 100644 --- a/arbnode/seq_coordinator.go +++ b/arbnode/seq_coordinator.go @@ -14,7 +14,7 @@ import ( "sync/atomic" "time" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" flag "github.com/spf13/pflag" "github.com/ethereum/go-ethereum/log" @@ -37,7 +37,10 @@ var ( type SeqCoordinator struct { stopwaiter.StopWaiter - redisutil.RedisCoordinator + redisCoordinatorMutex sync.RWMutex + redisCoordinator redisutil.RedisCoordinator + prevRedisCoordinator *redisutil.RedisCoordinator + prevRedisMessageCount arbutil.MessageIndex sync *SyncMonitor streamer *TransactionStreamer @@ -61,6 +64,7 @@ type SeqCoordinatorConfig struct { Enable bool `koanf:"enable"` ChosenHealthcheckAddr string `koanf:"chosen-healthcheck-addr"` RedisUrl string `koanf:"redis-url"` + NewRedisUrl string `koanf:"new-redis-url"` LockoutDuration time.Duration `koanf:"lockout-duration"` LockoutSpare time.Duration `koanf:"lockout-spare"` SeqNumDuration time.Duration `koanf:"seq-num-duration"` @@ -86,6 +90,7 @@ func (c *SeqCoordinatorConfig) Url() string { func SeqCoordinatorConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultSeqCoordinatorConfig.Enable, "enable sequence coordinator") f.String(prefix+".redis-url", DefaultSeqCoordinatorConfig.RedisUrl, "the Redis URL to coordinate via") + f.String(prefix+".new-redis-url", DefaultSeqCoordinatorConfig.NewRedisUrl, "switch to the new Redis URL to coordinate via") f.String(prefix+".chosen-healthcheck-addr", DefaultSeqCoordinatorConfig.ChosenHealthcheckAddr, "if non-empty, launch an HTTP service binding to this address that returns status code 200 when chosen and 503 otherwise") f.Duration(prefix+".lockout-duration", DefaultSeqCoordinatorConfig.LockoutDuration, "") f.Duration(prefix+".lockout-spare", DefaultSeqCoordinatorConfig.LockoutSpare, "") @@ -105,6 +110,7 @@ var DefaultSeqCoordinatorConfig = SeqCoordinatorConfig{ Enable: false, ChosenHealthcheckAddr: "", RedisUrl: "", + NewRedisUrl: "", LockoutDuration: time.Minute, LockoutSpare: 30 * time.Second, SeqNumDuration: 10 * 24 * time.Hour, @@ -122,6 +128,7 @@ var DefaultSeqCoordinatorConfig = SeqCoordinatorConfig{ var TestSeqCoordinatorConfig = SeqCoordinatorConfig{ Enable: false, RedisUrl: "", + NewRedisUrl: "", LockoutDuration: time.Second * 2, LockoutSpare: time.Millisecond * 10, SeqNumDuration: time.Minute * 10, @@ -153,7 +160,7 @@ func NewSeqCoordinator( return nil, err } coordinator := &SeqCoordinator{ - RedisCoordinator: *redisCoordinator, + redisCoordinator: *redisCoordinator, sync: sync, streamer: streamer, sequencer: sequencer, @@ -174,6 +181,19 @@ func (c *SeqCoordinator) SetDelayedSequencer(delayedSequencer *DelayedSequencer) c.delayedSequencer = delayedSequencer } +func (c *SeqCoordinator) RedisCoordinator() *redisutil.RedisCoordinator { + c.redisCoordinatorMutex.RLock() + defer c.redisCoordinatorMutex.RUnlock() + return &c.redisCoordinator +} + +func (c *SeqCoordinator) setRedisCoordinator(redisCoordinator *redisutil.RedisCoordinator) { + c.redisCoordinatorMutex.Lock() + defer c.redisCoordinatorMutex.Unlock() + c.prevRedisCoordinator = &c.redisCoordinator + c.redisCoordinator = *redisCoordinator +} + func StandaloneSeqCoordinatorInvalidateMsgIndex(ctx context.Context, redisClient redis.UniversalClient, keyConfig string, msgIndex arbutil.MessageIndex) error { signerConfig := signature.EmptySimpleHmacConfig if keyConfig == "" { @@ -276,7 +296,7 @@ func (c *SeqCoordinator) acquireLockoutAndWriteMessage(ctx context.Context, msgC defer c.wantsLockoutMutex.Unlock() setWantsLockout := c.avoidLockout <= 0 lockoutUntil := time.Now().Add(c.config.LockoutDuration) - err = c.Client.Watch(ctx, func(tx *redis.Tx) error { + err = c.RedisCoordinator().Client.Watch(ctx, func(tx *redis.Tx) error { current, err := tx.Get(ctx, redisutil.CHOSENSEQ_KEY).Result() var wasEmpty bool if errors.Is(err, redis.Nil) { @@ -345,7 +365,7 @@ func (c *SeqCoordinator) acquireLockoutAndWriteMessage(ctx context.Context, msgC } func (c *SeqCoordinator) getRemoteFinalizedMsgCount(ctx context.Context) (arbutil.MessageIndex, error) { - resStr, err := c.Client.Get(ctx, redisutil.FINALIZED_MSG_COUNT_KEY).Result() + resStr, err := c.RedisCoordinator().Client.Get(ctx, redisutil.FINALIZED_MSG_COUNT_KEY).Result() if err != nil { return 0, err } @@ -364,23 +384,23 @@ func (c *SeqCoordinator) getRemoteMsgCountImpl(ctx context.Context, r redis.Cmda } func (c *SeqCoordinator) GetRemoteMsgCount() (arbutil.MessageIndex, error) { - return c.getRemoteMsgCountImpl(c.GetContext(), c.Client) + return c.getRemoteMsgCountImpl(c.GetContext(), c.RedisCoordinator().Client) } -func (c *SeqCoordinator) wantsLockoutUpdate(ctx context.Context) error { +func (c *SeqCoordinator) wantsLockoutUpdate(ctx context.Context, client redis.UniversalClient) error { c.wantsLockoutMutex.Lock() defer c.wantsLockoutMutex.Unlock() - return c.wantsLockoutUpdateWithMutex(ctx) + return c.wantsLockoutUpdateWithMutex(ctx, client) } // Requires the caller hold the wantsLockoutMutex -func (c *SeqCoordinator) wantsLockoutUpdateWithMutex(ctx context.Context) error { +func (c *SeqCoordinator) wantsLockoutUpdateWithMutex(ctx context.Context, client redis.UniversalClient) error { if c.avoidLockout > 0 { return nil } myWantsLockoutKey := redisutil.WantsLockoutKeyFor(c.config.Url()) wantsLockoutUntil := time.Now().Add(c.config.LockoutDuration) - pipe := c.Client.TxPipeline() + pipe := client.TxPipeline() initialDuration := c.config.LockoutDuration if initialDuration < 2*time.Second { initialDuration = 2 * time.Second @@ -398,7 +418,7 @@ func (c *SeqCoordinator) wantsLockoutUpdateWithMutex(ctx context.Context) error func (c *SeqCoordinator) chosenOneRelease(ctx context.Context) error { atomicTimeWrite(&c.lockoutUntil, time.Time{}) isActiveSequencer.Update(0) - releaseErr := c.Client.Watch(ctx, func(tx *redis.Tx) error { + releaseErr := c.RedisCoordinator().Client.Watch(ctx, func(tx *redis.Tx) error { current, err := tx.Get(ctx, redisutil.CHOSENSEQ_KEY).Result() if errors.Is(err, redis.Nil) { return nil @@ -421,7 +441,7 @@ func (c *SeqCoordinator) chosenOneRelease(ctx context.Context) error { return nil } // got error - was it still released? - current, readErr := c.Client.Get(ctx, redisutil.CHOSENSEQ_KEY).Result() + current, readErr := c.RedisCoordinator().Client.Get(ctx, redisutil.CHOSENSEQ_KEY).Result() if errors.Is(readErr, redis.Nil) { return nil } @@ -438,10 +458,10 @@ func (c *SeqCoordinator) wantsLockoutRelease(ctx context.Context) error { return nil } myWantsLockoutKey := redisutil.WantsLockoutKeyFor(c.config.Url()) - releaseErr := c.Client.Del(ctx, myWantsLockoutKey).Err() + releaseErr := c.RedisCoordinator().Client.Del(ctx, myWantsLockoutKey).Err() if releaseErr != nil { // got error - was it still deleted? - readErr := c.Client.Get(ctx, myWantsLockoutKey).Err() + readErr := c.RedisCoordinator().Client.Get(ctx, myWantsLockoutKey).Err() if !errors.Is(readErr, redis.Nil) { return releaseErr } @@ -491,7 +511,7 @@ func (c *SeqCoordinator) updateWithLockout(ctx context.Context, nextChosen strin // Before proceeding, first try deleting finalized messages from redis and setting the finalizedMsgCount key finalized, err := c.sync.GetFinalizedMsgCount(ctx) if err != nil { - log.Warn("Error getting finalizedMessageCount from syncMonitor: %w", err) + log.Warn("Error getting finalizedMessageCount from syncMonitor", "err", err) } else if finalized == 0 { log.Warn("SyncMonitor returned zero finalizedMessageCount") } else if err := c.deleteFinalizedMsgsFromRedis(ctx, finalized); err != nil { @@ -525,7 +545,7 @@ func (c *SeqCoordinator) deleteFinalizedMsgsFromRedis(ctx context.Context, final // In non-init cases it doesn't matter how we delete as we always try to delete from prevFinalized to finalized batchDeleteCount := 1000 for i := len(keys); i > 0; i -= batchDeleteCount { - if err := c.Client.Del(ctx, keys[max(0, i-batchDeleteCount):i]...).Err(); err != nil { + if err := c.RedisCoordinator().Client.Del(ctx, keys[max(0, i-batchDeleteCount):i]...).Err(); err != nil { return fmt.Errorf("error deleting finalized messages and their signatures from redis: %w", err) } } @@ -534,7 +554,7 @@ func (c *SeqCoordinator) deleteFinalizedMsgsFromRedis(ctx context.Context, final if err != nil { return err } - if err = c.Client.Set(ctx, redisutil.FINALIZED_MSG_COUNT_KEY, finalizedBytes, c.config.SeqNumDuration).Err(); err != nil { + if err = c.RedisCoordinator().Client.Set(ctx, redisutil.FINALIZED_MSG_COUNT_KEY, finalizedBytes, c.config.SeqNumDuration).Err(); err != nil { return fmt.Errorf("couldn't set %s key to current finalizedMsgCount in redis: %w", redisutil.FINALIZED_MSG_COUNT_KEY, err) } return nil @@ -543,7 +563,7 @@ func (c *SeqCoordinator) deleteFinalizedMsgsFromRedis(ctx context.Context, final if errors.Is(err, redis.Nil) { var keys []string for msg := finalized - 1; msg > 0; msg-- { - exists, err := c.Client.Exists(ctx, redisutil.MessageKeyFor(msg), redisutil.MessageSigKeyFor(msg)).Result() + exists, err := c.RedisCoordinator().Client.Exists(ctx, redisutil.MessageKeyFor(msg), redisutil.MessageSigKeyFor(msg)).Result() if err != nil { // If there is an error deleting finalized messages during init, we retry later either from this sequencer or from another return err @@ -558,7 +578,7 @@ func (c *SeqCoordinator) deleteFinalizedMsgsFromRedis(ctx context.Context, final } else if err != nil { return fmt.Errorf("error getting finalizedMsgCount value from redis: %w", err) } - remoteMsgCount, err := c.getRemoteMsgCountImpl(ctx, c.Client) + remoteMsgCount, err := c.getRemoteMsgCountImpl(ctx, c.RedisCoordinator().Client) if err != nil { return fmt.Errorf("cannot get remote message count: %w", err) } @@ -574,7 +594,7 @@ func (c *SeqCoordinator) deleteFinalizedMsgsFromRedis(ctx context.Context, final } func (c *SeqCoordinator) update(ctx context.Context) time.Duration { - chosenSeq, err := c.RecommendSequencerWantingLockout(ctx) + chosenSeq, err := c.RedisCoordinator().RecommendSequencerWantingLockout(ctx) if err != nil { log.Warn("coordinator failed finding sequencer wanting lockout", "err", err) return c.retryAfterRedisError() @@ -603,6 +623,15 @@ func (c *SeqCoordinator) update(ctx context.Context) time.Duration { log.Error("cannot read message count", "err", err) return c.config.UpdateInterval } + // Cache the previous redis coordinator's message count + if c.prevRedisCoordinator != nil && c.prevRedisMessageCount == 0 { + prevRemoteMsgCount, err := c.getRemoteMsgCountImpl(ctx, c.prevRedisCoordinator.Client) + if err != nil { + log.Warn("cannot get remote message count", "err", err) + return c.retryAfterRedisError() + } + c.prevRedisMessageCount = prevRemoteMsgCount + } remoteFinalizedMsgCount, err := c.getRemoteFinalizedMsgCount(ctx) if err != nil { loglevel := log.Error @@ -617,12 +646,22 @@ func (c *SeqCoordinator) update(ctx context.Context) time.Duration { return c.retryAfterRedisError() } readUntil := min(localMsgCount+c.config.MsgPerPoll, remoteMsgCount) + client := c.RedisCoordinator().Client + // If we have a previous redis coordinator, + // we can read from it until the local message count catches up to the prev coordinator's message count + if c.prevRedisMessageCount > localMsgCount { + readUntil = min(readUntil, c.prevRedisMessageCount) + client = c.prevRedisCoordinator.Client + } + if c.prevRedisMessageCount != 0 && localMsgCount >= c.prevRedisMessageCount { + log.Info("coordinator caught up to prev redis coordinator", "msgcount", localMsgCount, "prevMsgCount", c.prevRedisMessageCount) + } var messages []arbostypes.MessageWithMetadata msgToRead := localMsgCount var msgReadErr error for msgToRead < readUntil && localMsgCount >= remoteFinalizedMsgCount { var resString string - resString, msgReadErr = c.Client.Get(ctx, redisutil.MessageKeyFor(msgToRead)).Result() + resString, msgReadErr = client.Get(ctx, redisutil.MessageKeyFor(msgToRead)).Result() if msgReadErr != nil { log.Warn("coordinator failed reading message", "pos", msgToRead, "err", msgReadErr) break @@ -631,7 +670,7 @@ func (c *SeqCoordinator) update(ctx context.Context) time.Duration { var sigString string var sigBytes []byte sigSeparateKey := true - sigString, msgReadErr = c.Client.Get(ctx, redisutil.MessageSigKeyFor(msgToRead)).Result() + sigString, msgReadErr = client.Get(ctx, redisutil.MessageSigKeyFor(msgToRead)).Result() if errors.Is(msgReadErr, redis.Nil) { // no separate signature. Try reading old-style sig if len(rsBytes) < 32 { @@ -722,7 +761,7 @@ func (c *SeqCoordinator) update(ctx context.Context) time.Duration { // this could be just new messages we didn't get yet - even then, we should retry soon log.Info("sequencer failed to become chosen", "err", err, "msgcount", localMsgCount) // make sure we're marked as wanting the lockout - if err := c.wantsLockoutUpdate(ctx); err != nil { + if err := c.wantsLockoutUpdate(ctx, c.RedisCoordinator().Client); err != nil { log.Warn("failed to update wants lockout key", "err", err) } c.prevChosenSequencer = "" @@ -750,7 +789,7 @@ func (c *SeqCoordinator) update(ctx context.Context) time.Duration { // update wanting the lockout var wantsLockoutErr error if synced && !c.AvoidingLockout() { - wantsLockoutErr = c.wantsLockoutUpdate(ctx) + wantsLockoutErr = c.wantsLockoutUpdate(ctx, c.RedisCoordinator().Client) } else { wantsLockoutErr = c.wantsLockoutRelease(ctx) } @@ -817,12 +856,59 @@ func (c *SeqCoordinator) launchHealthcheckServer(ctx context.Context) { func (c *SeqCoordinator) Start(ctxIn context.Context) { c.StopWaiter.Start(ctxIn, c) - c.CallIteratively(c.update) + var newRedisCoordinator *redisutil.RedisCoordinator + if c.config.NewRedisUrl != "" { + var err error + newRedisCoordinator, err = redisutil.NewRedisCoordinator(c.config.NewRedisUrl) + if err != nil { + log.Warn("failed to create new redis coordinator", "err", + err, "newRedisUrl", c.config.NewRedisUrl) + } + } + c.CallIteratively(func(ctx context.Context) time.Duration { return c.chooseRedisAndUpdate(ctx, newRedisCoordinator) }) if c.config.ChosenHealthcheckAddr != "" { c.StopWaiter.LaunchThread(c.launchHealthcheckServer) } } +func (c *SeqCoordinator) chooseRedisAndUpdate(ctx context.Context, newRedisCoordinator *redisutil.RedisCoordinator) time.Duration { + // If we have a new redis coordinator, and we haven't switched to it yet, try to switch. + if c.config.NewRedisUrl != "" && c.prevRedisCoordinator == nil { + // If we fail to try to switch, we'll retry soon. + if err := c.trySwitchingRedis(ctx, newRedisCoordinator); err != nil { + log.Warn("error while trying to switch redis coordinator", "err", err) + return c.retryAfterRedisError() + } + } + return c.update(ctx) +} + +func (c *SeqCoordinator) trySwitchingRedis(ctx context.Context, newRedisCoordinator *redisutil.RedisCoordinator) error { + err := c.wantsLockoutUpdate(ctx, newRedisCoordinator.Client) + if err != nil { + return err + } + current, err := c.RedisCoordinator().Client.Get(ctx, redisutil.CHOSENSEQ_KEY).Result() + var wasEmpty bool + if errors.Is(err, redis.Nil) { + wasEmpty = true + err = nil + } + if err != nil { + log.Warn("failed to get current chosen sequencer", "err", err) + return err + } + // If the chosen key is set to switch, we need to switch to the new redis coordinator. + if !wasEmpty && (current == redisutil.SWITCHED_REDIS) { + err = c.wantsLockoutUpdate(ctx, c.RedisCoordinator().Client) + if err != nil { + return err + } + c.setRedisCoordinator(newRedisCoordinator) + } + return nil +} + // Calls check() every c.config.RetryInterval until it returns true, or the context times out. func (c *SeqCoordinator) waitFor(ctx context.Context, check func() bool) bool { for { @@ -872,7 +958,7 @@ func (c *SeqCoordinator) StopAndWait() { time.Sleep(c.retryAfterRedisError()) } } - _ = c.Client.Close() + _ = c.RedisCoordinator().Client.Close() } func (c *SeqCoordinator) CurrentlyChosen() bool { @@ -914,7 +1000,7 @@ func (c *SeqCoordinator) TryToHandoffChosenOne(ctx context.Context) bool { return !c.CurrentlyChosen() }) if success { - wantsLockout, err := c.RecommendSequencerWantingLockout(ctx) + wantsLockout, err := c.RedisCoordinator().RecommendSequencerWantingLockout(ctx) if err == nil { log.Info("released chosen one status; a new sequencer hopefully wants to acquire it", "delay", c.config.SafeShutdownDelay, "wantsLockout", wantsLockout) } else { @@ -936,7 +1022,7 @@ func (c *SeqCoordinator) SeekLockout(ctx context.Context) { log.Info("seeking lockout", "myUrl", c.config.Url()) if c.sequencer.Synced() { // Even if this errors we still internally marked ourselves as wanting the lockout - err := c.wantsLockoutUpdateWithMutex(ctx) + err := c.wantsLockoutUpdateWithMutex(ctx, c.RedisCoordinator().Client) if err != nil { log.Warn("failed to set wants lockout key in redis after seeking lockout again", "err", err) } diff --git a/arbnode/seq_coordinator_test.go b/arbnode/seq_coordinator_test.go index 6498543f3a..3f35011c20 100644 --- a/arbnode/seq_coordinator_test.go +++ b/arbnode/seq_coordinator_test.go @@ -125,7 +125,7 @@ func TestRedisSeqCoordinatorAtomic(t *testing.T) { redisCoordinator, err := redisutil.NewRedisCoordinator(config.RedisUrl) Require(t, err) coordinator := &SeqCoordinator{ - RedisCoordinator: *redisCoordinator, + redisCoordinator: *redisCoordinator, config: config, signer: nullSigner, } @@ -181,7 +181,7 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { redisCoordinator, err := redisutil.NewRedisCoordinator(config.RedisUrl) Require(t, err) coordinator := &SeqCoordinator{ - RedisCoordinator: *redisCoordinator, + redisCoordinator: *redisCoordinator, config: config, signer: nullSigner, } @@ -191,18 +191,18 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { msgBytes, err := coordinator.msgCountToSignedBytes(0) Require(t, err) for i := arbutil.MessageIndex(1); i <= 10; i++ { - err = coordinator.Client.Set(ctx, redisutil.MessageKeyFor(i), msgBytes, time.Hour).Err() + err = coordinator.RedisCoordinator().Client.Set(ctx, redisutil.MessageKeyFor(i), msgBytes, time.Hour).Err() Require(t, err) - err = coordinator.Client.Set(ctx, redisutil.MessageSigKeyFor(i), msgBytes, time.Hour).Err() + err = coordinator.RedisCoordinator().Client.Set(ctx, redisutil.MessageSigKeyFor(i), msgBytes, time.Hour).Err() Require(t, err) keys = append(keys, redisutil.MessageKeyFor(i), redisutil.MessageSigKeyFor(i)) } // Set msgCount key msgCountBytes, err := coordinator.msgCountToSignedBytes(11) Require(t, err) - err = coordinator.Client.Set(ctx, redisutil.MSG_COUNT_KEY, msgCountBytes, time.Hour).Err() + err = coordinator.RedisCoordinator().Client.Set(ctx, redisutil.MSG_COUNT_KEY, msgCountBytes, time.Hour).Err() Require(t, err) - exists, err := coordinator.Client.Exists(ctx, keys...).Result() + exists, err := coordinator.RedisCoordinator().Client.Exists(ctx, keys...).Result() Require(t, err) if exists != 20 { t.Fatal("couldn't find all messages and signatures in redis") @@ -213,7 +213,7 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { Require(t, err) // Check if messages and signatures were deleted successfully - exists, err = coordinator.Client.Exists(ctx, keys[:8]...).Result() + exists, err = coordinator.RedisCoordinator().Client.Exists(ctx, keys[:8]...).Result() Require(t, err) if exists != 0 { t.Fatal("finalized messages and signatures in range 1 to 4 were not deleted") @@ -229,7 +229,7 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { // Try deleting finalized messages when theres already a finalizedMsgCount err = coordinator.deleteFinalizedMsgsFromRedis(ctx, 7) Require(t, err) - exists, err = coordinator.Client.Exists(ctx, keys[8:12]...).Result() + exists, err = coordinator.RedisCoordinator().Client.Exists(ctx, keys[8:12]...).Result() Require(t, err) if exists != 0 { t.Fatal("finalized messages and signatures in range 5 to 6 were not deleted") @@ -241,7 +241,7 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { } // Check that non-finalized messages are still available in redis - exists, err = coordinator.Client.Exists(ctx, keys[12:]...).Result() + exists, err = coordinator.RedisCoordinator().Client.Exists(ctx, keys[12:]...).Result() Require(t, err) if exists != 8 { t.Fatal("non-finalized messages and signatures in range 7 to 10 are not fully available") diff --git a/arbnode/sequencer_inbox.go b/arbnode/sequencer_inbox.go index 73e52ded53..81146ed46e 100644 --- a/arbnode/sequencer_inbox.go +++ b/arbnode/sequencer_inbox.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" @@ -52,10 +53,10 @@ type SequencerInbox struct { con *bridgegen.SequencerInbox address common.Address fromBlock int64 - client arbutil.L1Interface + client *ethclient.Client } -func NewSequencerInbox(client arbutil.L1Interface, addr common.Address, fromBlock int64) (*SequencerInbox, error) { +func NewSequencerInbox(client *ethclient.Client, addr common.Address, fromBlock int64) (*SequencerInbox, error) { con, err := bridgegen.NewSequencerInbox(addr, client) if err != nil { return nil, err @@ -111,7 +112,7 @@ type SequencerInboxBatch struct { serialized []byte // nil if serialization isn't cached yet } -func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client arbutil.L1Interface) ([]byte, error) { +func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client *ethclient.Client) ([]byte, error) { switch m.dataLocation { case batchDataTxInput: data, err := arbutil.GetLogEmitterTxData(ctx, client, m.rawLog) @@ -169,7 +170,7 @@ func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client arbut } } -func (m *SequencerInboxBatch) Serialize(ctx context.Context, client arbutil.L1Interface) ([]byte, error) { +func (m *SequencerInboxBatch) Serialize(ctx context.Context, client *ethclient.Client) ([]byte, error) { if m.serialized != nil { return m.serialized, nil } diff --git a/arbnode/transaction_streamer.go b/arbnode/transaction_streamer.go index 24a0564b97..38b1c003db 100644 --- a/arbnode/transaction_streamer.go +++ b/arbnode/transaction_streamer.go @@ -1140,7 +1140,7 @@ func (s *TransactionStreamer) storeResult( // exposed for testing // return value: true if should be called again immediately -func (s *TransactionStreamer) ExecuteNextMsg(ctx context.Context, exec execution.ExecutionSequencer) bool { +func (s *TransactionStreamer) ExecuteNextMsg(ctx context.Context) bool { if ctx.Err() != nil { return false } @@ -1212,7 +1212,7 @@ func (s *TransactionStreamer) ExecuteNextMsg(ctx context.Context, exec execution } func (s *TransactionStreamer) executeMessages(ctx context.Context, ignored struct{}) time.Duration { - if s.ExecuteNextMsg(ctx, s.exec) { + if s.ExecuteNextMsg(ctx) { return 0 } return s.config().ExecuteMessageLoopDelay diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 91c2207aae..f53d9c892a 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -41,28 +41,29 @@ import ( // persisted beyond the end of the test.) type ArbosState struct { - arbosVersion uint64 // version of the ArbOS storage format and semantics - maxArbosVersionSupported uint64 // maximum ArbOS version supported by this code - maxDebugArbosVersionSupported uint64 // maximum ArbOS version supported by this code in debug mode - upgradeVersion storage.StorageBackedUint64 // version we're planning to upgrade to, or 0 if not planning to upgrade - upgradeTimestamp storage.StorageBackedUint64 // when to do the planned upgrade - networkFeeAccount storage.StorageBackedAddress - l1PricingState *l1pricing.L1PricingState - l2PricingState *l2pricing.L2PricingState - retryableState *retryables.RetryableState - addressTable *addressTable.AddressTable - chainOwners *addressSet.AddressSet - sendMerkle *merkleAccumulator.MerkleAccumulator - programs *programs.Programs - blockhashes *blockhash.Blockhashes - chainId storage.StorageBackedBigInt - chainConfig storage.StorageBackedBytes - genesisBlockNum storage.StorageBackedUint64 - infraFeeAccount storage.StorageBackedAddress - brotliCompressionLevel storage.StorageBackedUint64 // brotli compression level used for pricing - backingStorage *storage.Storage - Burner burn.Burner -} + arbosVersion uint64 // version of the ArbOS storage format and semantics + upgradeVersion storage.StorageBackedUint64 // version we're planning to upgrade to, or 0 if not planning to upgrade + upgradeTimestamp storage.StorageBackedUint64 // when to do the planned upgrade + networkFeeAccount storage.StorageBackedAddress + l1PricingState *l1pricing.L1PricingState + l2PricingState *l2pricing.L2PricingState + retryableState *retryables.RetryableState + addressTable *addressTable.AddressTable + chainOwners *addressSet.AddressSet + sendMerkle *merkleAccumulator.MerkleAccumulator + programs *programs.Programs + blockhashes *blockhash.Blockhashes + chainId storage.StorageBackedBigInt + chainConfig storage.StorageBackedBytes + genesisBlockNum storage.StorageBackedUint64 + infraFeeAccount storage.StorageBackedAddress + brotliCompressionLevel storage.StorageBackedUint64 // brotli compression level used for pricing + backingStorage *storage.Storage + Burner burn.Burner +} + +const MaxArbosVersionSupported uint64 = params.ArbosVersion_StylusChargingFixes +const MaxDebugArbosVersionSupported uint64 = params.ArbosVersion_StylusChargingFixes var ErrUninitializedArbOS = errors.New("ArbOS uninitialized") var ErrAlreadyInitialized = errors.New("ArbOS is already initialized") @@ -78,8 +79,6 @@ func OpenArbosState(stateDB vm.StateDB, burner burn.Burner) (*ArbosState, error) } return &ArbosState{ arbosVersion, - 31, - 31, backingStorage.OpenStorageBackedUint64(uint64(upgradeVersionOffset)), backingStorage.OpenStorageBackedUint64(uint64(upgradeTimestampOffset)), backingStorage.OpenStorageBackedAddress(uint64(networkFeeAccountOffset)), @@ -332,6 +331,9 @@ func (state *ArbosState) UpgradeArbosVersion( ensure(params.UpgradeToVersion(2)) ensure(params.Save()) + case 32: + // no change state needed + default: return fmt.Errorf( "the chain is upgrading to unsupported ArbOS version %v, %w", @@ -416,14 +418,6 @@ func (state *ArbosState) RetryableState() *retryables.RetryableState { return state.retryableState } -func (state *ArbosState) MaxArbosVersionSupported() uint64 { - return state.maxArbosVersionSupported -} - -func (state *ArbosState) MaxDebugArbosVersionSupported() uint64 { - return state.maxDebugArbosVersionSupported -} - func (state *ArbosState) L1PricingState() *l1pricing.L1PricingState { return state.l1PricingState } diff --git a/arbos/arbostypes/incomingmessage.go b/arbos/arbostypes/incomingmessage.go index 04ce8ebe2e..c4c2dc037b 100644 --- a/arbos/arbostypes/incomingmessage.go +++ b/arbos/arbostypes/incomingmessage.go @@ -182,6 +182,17 @@ func (msg *L1IncomingMessage) FillInBatchGasCost(batchFetcher FallibleBatchFetch return nil } +func (msg *L1IncomingMessage) PastBatchesRequired() ([]uint64, error) { + if msg.Header.Kind != L1MessageType_BatchPostingReport { + return nil, nil + } + _, _, _, batchNum, _, _, err := ParseBatchPostingReportMessageFields(bytes.NewReader(msg.L2msg)) + if err != nil { + return nil, fmt.Errorf("failed to parse batch posting report: %w", err) + } + return []uint64{batchNum}, nil +} + func ParseIncomingL1Message(rd io.Reader, batchFetcher FallibleBatchFetcher) (*L1IncomingMessage, error) { var kindBuf [1]byte _, err := rd.Read(kindBuf[:]) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index b180405c43..19fc36b351 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -144,6 +144,7 @@ func ProduceBlock( chainContext core.ChainContext, chainConfig *params.ChainConfig, isMsgForPrefetch bool, + runMode core.MessageRunMode, ) (*types.Block, types.Receipts, error) { txes, err := ParseL2Transactions(message, chainConfig.ChainID) if err != nil { @@ -153,7 +154,7 @@ func ProduceBlock( hooks := NoopSequencingHooks() return ProduceBlockAdvanced( - message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, isMsgForPrefetch, + message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, hooks, isMsgForPrefetch, runMode, ) } @@ -168,6 +169,7 @@ func ProduceBlockAdvanced( chainConfig *params.ChainConfig, sequencingHooks *SequencingHooks, isMsgForPrefetch bool, + runMode core.MessageRunMode, ) (*types.Block, types.Receipts, error) { state, err := arbosState.OpenSystemArbosState(statedb, nil, true) @@ -318,6 +320,7 @@ func ProduceBlockAdvanced( tx, &header.GasUsed, vm.Config{}, + runMode, func(result *core.ExecutionResult) error { return hooks.PostTxFilter(header, state, tx, sender, dataGas, result) }, diff --git a/arbos/programs/api.go b/arbos/programs/api.go index 504289322f..3e59031b2d 100644 --- a/arbos/programs/api.go +++ b/arbos/programs/api.go @@ -400,9 +400,9 @@ func newApiClosures( } startInk := takeU64() endInk := takeU64() - nameLen := takeU16() - argsLen := takeU16() - outsLen := takeU16() + nameLen := takeU32() + argsLen := takeU32() + outsLen := takeU32() name := string(takeFixed(int(nameLen))) args := takeFixed(int(argsLen)) outs := takeFixed(int(outsLen)) diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 377e25a31e..725b302ac0 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -7,7 +7,7 @@ package programs /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm #include "arbitrator.h" @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" @@ -45,17 +46,31 @@ type bytes32 = C.Bytes32 type rustBytes = C.RustBytes type rustSlice = C.RustSlice +var ( + stylusLRUCacheSizeBytesGauge = metrics.NewRegisteredGauge("arb/arbos/stylus/cache/lru/size_bytes", nil) + stylusLRUCacheCountGauge = metrics.NewRegisteredGauge("arb/arbos/stylus/cache/lru/count", nil) + stylusLRUCacheHitsCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/lru/hits", nil) + stylusLRUCacheMissesCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/lru/misses", nil) + stylusLRUCacheDoesNotFitCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/lru/does_not_fit", nil) + + stylusLongTermCacheSizeBytesGauge = metrics.NewRegisteredGauge("arb/arbos/stylus/cache/long_term/size_bytes", nil) + stylusLongTermCacheCountGauge = metrics.NewRegisteredGauge("arb/arbos/stylus/cache/long_term/count", nil) + stylusLongTermCacheHitsCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/long_term/hits", nil) + stylusLongTermCacheMissesCounter = metrics.NewRegisteredCounter("arb/arbos/stylus/cache/long_term/misses", nil) +) + func activateProgram( db vm.StateDB, program common.Address, codehash common.Hash, wasm []byte, page_limit uint16, - version uint16, + stylusVersion uint16, + arbosVersionForGas uint64, debug bool, burner burn.Burner, ) (*activationInfo, error) { - info, asmMap, err := activateProgramInternal(db, program, codehash, wasm, page_limit, version, debug, burner.GasLeft()) + info, asmMap, err := activateProgramInternal(db, program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft()) if err != nil { return nil, err } @@ -69,7 +84,8 @@ func activateProgramInternal( codehash common.Hash, wasm []byte, page_limit uint16, - version uint16, + stylusVersion uint16, + arbosVersionForGas uint64, debug bool, gasLeft *uint64, ) (*activationInfo, map[ethdb.WasmTarget][]byte, error) { @@ -81,7 +97,8 @@ func activateProgramInternal( status_mod := userStatus(C.stylus_activate( goSlice(wasm), u16(page_limit), - u16(version), + u16(stylusVersion), + u64(arbosVersionForGas), cbool(debug), output, &codeHash, @@ -100,6 +117,7 @@ func activateProgramInternal( } return nil, nil, err } + hash := moduleHash.toHash() targets := db.Database().WasmTargets() type result struct { target ethdb.WasmTarget @@ -108,15 +126,15 @@ func activateProgramInternal( } results := make(chan result, len(targets)) for _, target := range targets { + target := target if target == rawdb.TargetWavm { results <- result{target, module, nil} } else { - target := target go func() { output := &rustBytes{} status_asm := C.stylus_compile( goSlice(wasm), - u16(version), + u16(stylusVersion), cbool(debug), goSlice([]byte(target)), output, @@ -140,10 +158,17 @@ func activateProgramInternal( } } if err != nil { - return nil, nil, fmt.Errorf("compilation failed for one or more targets: %w", err) + log.Error( + "Compilation failed for one or more targets despite activation succeeding", + "address", addressForLogging, + "codeHash", codeHash, + "moduleHash", hash, + "targets", targets, + "err", err, + ) + panic(fmt.Sprintf("Compilation of %v failed for one or more targets despite activation succeeding: %v", addressForLogging, err)) } - hash := moduleHash.toHash() info := &activationInfo{ moduleHash: hash, initGas: uint16(stylusData.init_cost), @@ -168,9 +193,12 @@ func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging c return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err) } - unlimitedGas := uint64(0xffffffffffff) + // don't charge gas + zeroArbosVersion := uint64(0) + zeroGas := uint64(0) + // we know program is activated, so it must be in correct version and not use too much memory - info, asmMap, err := activateProgramInternal(statedb, addressForLogging, codeHash, wasm, pagelimit, program.version, debugMode, &unlimitedGas) + info, asmMap, err := activateProgramInternal(statedb, addressForLogging, codeHash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas) if err != nil { log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err) return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err) @@ -306,8 +334,74 @@ func init() { } } -func ResizeWasmLruCache(size uint32) { - C.stylus_cache_lru_resize(u32(size)) +func SetWasmLruCacheCapacity(capacityBytes uint64) { + C.stylus_set_cache_lru_capacity(u64(capacityBytes)) +} + +func UpdateWasmCacheMetrics() { + metrics := &C.CacheMetrics{} + C.stylus_get_cache_metrics(metrics) + + stylusLRUCacheSizeBytesGauge.Update(int64(metrics.lru.size_bytes)) + stylusLRUCacheCountGauge.Update(int64(metrics.lru.count)) + stylusLRUCacheHitsCounter.Inc(int64(metrics.lru.hits)) + stylusLRUCacheMissesCounter.Inc(int64(metrics.lru.misses)) + stylusLRUCacheDoesNotFitCounter.Inc(int64(metrics.lru.does_not_fit)) + + stylusLongTermCacheSizeBytesGauge.Update(int64(metrics.long_term.size_bytes)) + stylusLongTermCacheCountGauge.Update(int64(metrics.long_term.count)) + stylusLongTermCacheHitsCounter.Inc(int64(metrics.long_term.hits)) + stylusLongTermCacheMissesCounter.Inc(int64(metrics.long_term.misses)) +} + +// Used for testing +type WasmLruCacheMetrics struct { + SizeBytes uint64 + Count uint32 +} + +// Used for testing +type WasmLongTermCacheMetrics struct { + SizeBytes uint64 + Count uint32 +} + +// Used for testing +type WasmCacheMetrics struct { + Lru WasmLruCacheMetrics + LongTerm WasmLongTermCacheMetrics +} + +// Used for testing +func GetWasmCacheMetrics() *WasmCacheMetrics { + metrics := &C.CacheMetrics{} + C.stylus_get_cache_metrics(metrics) + + return &WasmCacheMetrics{ + Lru: WasmLruCacheMetrics{ + SizeBytes: uint64(metrics.lru.size_bytes), + Count: uint32(metrics.lru.count), + }, + LongTerm: WasmLongTermCacheMetrics{ + SizeBytes: uint64(metrics.long_term.size_bytes), + Count: uint32(metrics.long_term.count), + }, + } +} + +// Used for testing +func ClearWasmLruCache() { + C.stylus_clear_lru_cache() +} + +// Used for testing +func ClearWasmLongTermCache() { + C.stylus_clear_long_term_cache() +} + +// Used for testing +func GetEntrySizeEstimateBytes(module []byte, version uint16, debug bool) uint64 { + return uint64(C.stylus_get_entry_size_estimate_bytes(goSlice(module), u16(version), cbool(debug))) } const DefaultTargetDescriptionArm = "arm64-linux-unknown+neon" @@ -391,6 +485,7 @@ func (params *ProgParams) encode() C.StylusConfig { func (data *EvmData) encode() C.EvmData { return C.EvmData{ + arbos_version: u64(data.arbosVersion), block_basefee: hashToBytes32(data.blockBasefee), chainid: u64(data.chainId), block_coinbase: addressToBytes20(data.blockCoinbase), diff --git a/arbos/programs/native_api.go b/arbos/programs/native_api.go index 6fbb630ef3..6cecb8ef63 100644 --- a/arbos/programs/native_api.go +++ b/arbos/programs/native_api.go @@ -7,7 +7,7 @@ package programs /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm #include "arbitrator.h" diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 12102bac84..06ff4137da 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -82,7 +82,7 @@ func (p Programs) CacheManagers() *addressSet.AddressSet { return p.cacheManagers } -func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode core.MessageRunMode, debugMode bool) ( +func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, arbosVersion uint64, runMode core.MessageRunMode, debugMode bool) ( uint16, common.Hash, common.Hash, *big.Int, bool, error, ) { statedb := evm.StateDB @@ -116,7 +116,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode c // require the program's footprint not exceed the remaining memory budget pageLimit := am.SaturatingUSub(params.PageLimit, statedb.GetStylusPagesOpen()) - info, err := activateProgram(statedb, address, codeHash, wasm, pageLimit, stylusVersion, debugMode, burner) + info, err := activateProgram(statedb, address, codeHash, wasm, pageLimit, stylusVersion, arbosVersion, debugMode, burner) if err != nil { return 0, codeHash, common.Hash{}, nil, true, err } @@ -127,6 +127,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode c if err != nil { return 0, codeHash, common.Hash{}, nil, true, err } + evictProgram(statedb, oldModuleHash, currentVersion, debugMode, runMode, expired) } if err := p.moduleHashes.Set(codeHash, info.moduleHash); err != nil { @@ -222,6 +223,7 @@ func (p Programs) CallProgram( } evmData := &EvmData{ + arbosVersion: evm.Context.ArbOSVersion, blockBasefee: common.BigToHash(evm.Context.BaseFee), chainId: evm.ChainConfig().ChainID.Uint64(), blockCoinbase: evm.Context.Coinbase, @@ -517,6 +519,7 @@ func (p Programs) progParams(version uint16, debug bool, params *StylusParams) * } type EvmData struct { + arbosVersion uint64 blockBasefee common.Hash chainId uint64 blockCoinbase common.Address diff --git a/arbos/programs/testcompile.go b/arbos/programs/testcompile.go index 1daf470620..615b0f3f72 100644 --- a/arbos/programs/testcompile.go +++ b/arbos/programs/testcompile.go @@ -9,7 +9,7 @@ package programs // This file exists because cgo isn't allowed in tests /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #include "arbitrator.h" typedef uint16_t u16; diff --git a/arbos/programs/testconstants.go b/arbos/programs/testconstants.go index 1ab0e6e93b..44f69a52de 100644 --- a/arbos/programs/testconstants.go +++ b/arbos/programs/testconstants.go @@ -9,7 +9,7 @@ package programs // This file exists because cgo isn't allowed in tests /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #include "arbitrator.h" */ import "C" diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index f7191dca8f..12c23a724c 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -36,7 +36,7 @@ type rustConfig byte type rustModule byte type rustEvmData byte -//go:wasmimport programs activate +//go:wasmimport programs activate_v2 func programActivate( wasm_ptr unsafe.Pointer, wasm_size uint32, @@ -44,7 +44,8 @@ func programActivate( asm_estimation_ptr unsafe.Pointer, init_gas_ptr unsafe.Pointer, cached_init_gas_ptr unsafe.Pointer, - version uint32, + stylusVersion uint32, + arbosVersion uint64, debug uint32, codehash unsafe.Pointer, module_hash_ptr unsafe.Pointer, @@ -59,7 +60,8 @@ func activateProgram( codehash common.Hash, wasm []byte, pageLimit u16, - version u16, + stylusVersion u16, + arbosVersion uint64, debug bool, burner burn.Burner, ) (*activationInfo, error) { @@ -79,7 +81,8 @@ func activateProgram( unsafe.Pointer(&asmEstimate), unsafe.Pointer(&initGas), unsafe.Pointer(&cachedInitGas), - uint32(version), + uint32(stylusVersion), + arbosVersion, debugMode, arbutil.SliceToUnsafePointer(codehash[:]), arbutil.SliceToUnsafePointer(moduleHash[:]), @@ -151,6 +154,8 @@ func callProgram( return retData, err } +func GetWasmLruCacheMetrics() {} + func CallProgramLoop( moduleHash common.Hash, calldata []byte, diff --git a/arbos/programs/wasm_api.go b/arbos/programs/wasm_api.go index d7bac056c0..a4ebc1f778 100644 --- a/arbos/programs/wasm_api.go +++ b/arbos/programs/wasm_api.go @@ -20,8 +20,9 @@ func createStylusConfig(version uint32, max_depth uint32, ink_price uint32, debu type evmDataHandler uint64 -//go:wasmimport programs create_evm_data +//go:wasmimport programs create_evm_data_v2 func createEvmData( + arbosVersion uint64, blockBaseFee unsafe.Pointer, chainid uint64, blockCoinbase unsafe.Pointer, @@ -45,6 +46,7 @@ func (params *ProgParams) createHandler() stylusConfigHandler { func (data *EvmData) createHandler() evmDataHandler { return createEvmData( + data.arbosVersion, arbutil.SliceToUnsafePointer(data.blockBasefee[:]), data.chainId, arbutil.SliceToUnsafePointer(data.blockCoinbase[:]), diff --git a/arbos/programs/wasmstorehelper.go b/arbos/programs/wasmstorehelper.go index 434820dd9c..c2d1aa65b0 100644 --- a/arbos/programs/wasmstorehelper.go +++ b/arbos/programs/wasmstorehelper.go @@ -17,12 +17,12 @@ import ( // SaveActiveProgramToWasmStore is used to save active stylus programs to wasm store during rebuilding func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash common.Hash, code []byte, time uint64, debugMode bool, rebuildingStartBlockTime uint64) error { - params, err := p.Params() + progParams, err := p.Params() if err != nil { return err } - program, err := p.getActiveProgram(codeHash, time, params) + program, err := p.getActiveProgram(codeHash, time, progParams) if err != nil { // The program is not active so return early log.Info("program is not active, getActiveProgram returned error, hence do not include in rebuilding", "err", err) @@ -56,10 +56,13 @@ func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err) } - unlimitedGas := uint64(0xffffffffffff) + // don't charge gas + zeroArbosVersion := uint64(0) + zeroGas := uint64(0) + // We know program is activated, so it must be in correct version and not use too much memory // Empty program address is supplied because we dont have access to this during rebuilding of wasm store - info, asmMap, err := activateProgramInternal(statedb, common.Address{}, codeHash, wasm, params.PageLimit, program.version, debugMode, &unlimitedGas) + info, asmMap, err := activateProgramInternal(statedb, common.Address{}, codeHash, wasm, progParams.PageLimit, program.version, zeroArbosVersion, debugMode, &zeroGas) if err != nil { log.Error("failed to reactivate program while rebuilding wasm store", "expected moduleHash", moduleHash, "err", err) return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index b08c7c5d30..d6c35339f6 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -532,6 +532,20 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { refund := func(refundFrom common.Address, amount *big.Int) { const errLog = "fee address doesn't have enough funds to give user refund" + logMissingRefund := func(err error) { + if !errors.Is(err, vm.ErrInsufficientBalance) { + log.Error("unexpected error refunding balance", "err", err, "feeAddress", refundFrom) + return + } + logLevel := log.Error + isContract := p.evm.StateDB.GetCodeSize(refundFrom) > 0 + if isContract { + // It's expected that the balance might not still be in this address if it's a contract. + logLevel = log.Debug + } + logLevel(errLog, "err", err, "feeAddress", refundFrom) + } + // Refund funds to the fee refund address without overdrafting the L1 deposit. toRefundAddr := takeFunds(maxRefund, amount) err = util.TransferBalance(&refundFrom, &inner.RefundTo, toRefundAddr, p.evm, scenario, "refund") @@ -539,13 +553,13 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { // Normally the network fee address should be holding any collected fees. // However, in theory, they could've been transferred out during the redeem attempt. // If the network fee address doesn't have the necessary balance, log an error and don't give a refund. - log.Error(errLog, "err", err, "feeAddress", refundFrom) + logMissingRefund(err) } // Any extra refund can't be given to the fee refund address if it didn't come from the L1 deposit. // Instead, give the refund to the retryable from address. err = util.TransferBalance(&refundFrom, &inner.From, arbmath.BigSub(amount, toRefundAddr), p.evm, scenario, "refund") if err != nil { - log.Error(errLog, "err", err, "feeAddress", refundFrom) + logMissingRefund(err) } } diff --git a/arbos/util/storage_cache.go b/arbos/util/storage_cache.go index bf05a5824d..9573d1ffc7 100644 --- a/arbos/util/storage_cache.go +++ b/arbos/util/storage_cache.go @@ -5,6 +5,7 @@ package util import ( "github.com/ethereum/go-ethereum/common" + "slices" ) type storageCacheEntry struct { @@ -67,6 +68,10 @@ func (s *storageCache) Flush() []storageCacheStores { }) } } + sortFunc := func(a, b storageCacheStores) int { + return a.Key.Cmp(b.Key) + } + slices.SortFunc(stores, sortFunc) return stores } diff --git a/arbos/util/storage_cache_test.go b/arbos/util/storage_cache_test.go index 1cc4ea14ec..9fd452851d 100644 --- a/arbos/util/storage_cache_test.go +++ b/arbos/util/storage_cache_test.go @@ -4,7 +4,6 @@ package util import ( - "bytes" "slices" "testing" @@ -76,7 +75,7 @@ func TestStorageCache(t *testing.T) { {Key: keys[2], Value: values[2]}, } sortFunc := func(a, b storageCacheStores) int { - return bytes.Compare(a.Key.Bytes(), b.Key.Bytes()) + return a.Key.Cmp(b.Key) } slices.SortFunc(stores, sortFunc) slices.SortFunc(expected, sortFunc) diff --git a/arbutil/correspondingl1blocknumber.go b/arbutil/correspondingl1blocknumber.go index d654e471e2..c8770e2034 100644 --- a/arbutil/correspondingl1blocknumber.go +++ b/arbutil/correspondingl1blocknumber.go @@ -19,7 +19,11 @@ func ParentHeaderToL1BlockNumber(header *types.Header) uint64 { return header.Number.Uint64() } -func CorrespondingL1BlockNumber(ctx context.Context, client L1Interface, parentBlockNumber uint64) (uint64, error) { +type ParentHeaderFetcher interface { + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) +} + +func CorrespondingL1BlockNumber(ctx context.Context, client ParentHeaderFetcher, parentBlockNumber uint64) (uint64, error) { // #nosec G115 header, err := client.HeaderByNumber(ctx, big.NewInt(int64(parentBlockNumber))) if err != nil { diff --git a/arbutil/transaction_data.go b/arbutil/transaction_data.go index 8270a628bd..c5728967c7 100644 --- a/arbutil/transaction_data.go +++ b/arbutil/transaction_data.go @@ -8,9 +8,10 @@ import ( "fmt" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" ) -func GetLogTransaction(ctx context.Context, client L1Interface, log types.Log) (*types.Transaction, error) { +func GetLogTransaction(ctx context.Context, client *ethclient.Client, log types.Log) (*types.Transaction, error) { tx, err := client.TransactionInBlock(ctx, log.BlockHash, log.TxIndex) if err != nil { return nil, err @@ -22,7 +23,7 @@ func GetLogTransaction(ctx context.Context, client L1Interface, log types.Log) ( } // GetLogEmitterTxData requires that the tx's data is at least 4 bytes long -func GetLogEmitterTxData(ctx context.Context, client L1Interface, log types.Log) ([]byte, error) { +func GetLogEmitterTxData(ctx context.Context, client *ethclient.Client, log types.Log) ([]byte, error) { tx, err := GetLogTransaction(ctx, client, log) if err != nil { return nil, err diff --git a/arbutil/wait_for_l1.go b/arbutil/wait_for_l1.go index 4b4819156d..80dd356b24 100644 --- a/arbutil/wait_for_l1.go +++ b/arbutil/wait_for_l1.go @@ -10,27 +10,13 @@ import ( "math/big" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/ethclient" ) -type L1Interface interface { - bind.ContractBackend - bind.BlockHashContractCaller - ethereum.ChainReader - ethereum.ChainStateReader - ethereum.TransactionReader - TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) - BlockNumber(ctx context.Context) (uint64, error) - PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) - ChainID(ctx context.Context) (*big.Int, error) - Client() rpc.ClientInterface -} - -func SendTxAsCall(ctx context.Context, client L1Interface, tx *types.Transaction, from common.Address, blockNum *big.Int, unlimitedGas bool) ([]byte, error) { +func SendTxAsCall(ctx context.Context, client *ethclient.Client, tx *types.Transaction, from common.Address, blockNum *big.Int, unlimitedGas bool) ([]byte, error) { var gas uint64 if unlimitedGas { gas = 0 @@ -50,7 +36,7 @@ func SendTxAsCall(ctx context.Context, client L1Interface, tx *types.Transaction return client.CallContract(ctx, callMsg, blockNum) } -func GetPendingCallBlockNumber(ctx context.Context, client L1Interface) (*big.Int, error) { +func GetPendingCallBlockNumber(ctx context.Context, client *ethclient.Client) (*big.Int, error) { msg := ethereum.CallMsg{ // Pretend to be a contract deployment to execute EVM code without calling a contract. To: nil, @@ -70,7 +56,7 @@ func GetPendingCallBlockNumber(ctx context.Context, client L1Interface) (*big.In return new(big.Int).SetBytes(callRes), nil } -func DetailTxError(ctx context.Context, client L1Interface, tx *types.Transaction, txRes *types.Receipt) error { +func DetailTxError(ctx context.Context, client *ethclient.Client, tx *types.Transaction, txRes *types.Receipt) error { // Re-execute the transaction as a call to get a better error if ctx.Err() != nil { return ctx.Err() @@ -96,7 +82,7 @@ func DetailTxError(ctx context.Context, client L1Interface, tx *types.Transactio return fmt.Errorf("SendTxAsCall got: %w for tx hash %v", err, tx.Hash()) } -func DetailTxErrorUsingCallMsg(ctx context.Context, client L1Interface, txHash common.Hash, txRes *types.Receipt, callMsg ethereum.CallMsg) error { +func DetailTxErrorUsingCallMsg(ctx context.Context, client *ethclient.Client, txHash common.Hash, txRes *types.Receipt, callMsg ethereum.CallMsg) error { // Re-execute the transaction as a call to get a better error if ctx.Err() != nil { return ctx.Err() diff --git a/broadcastclient/broadcastclient.go b/broadcastclient/broadcastclient.go index 7d27c57fe9..4e97ca8cd0 100644 --- a/broadcastclient/broadcastclient.go +++ b/broadcastclient/broadcastclient.go @@ -280,6 +280,18 @@ func (bc *BroadcastClient) connect(ctx context.Context, nextSeqNum arbutil.Messa MinVersion: tls.VersionTLS12, }, Extensions: extensions, + NetDial: func(ctx context.Context, network, addr string) (net.Conn, error) { + var netDialer net.Dialer + // For tcp connections, prefer IPv4 over IPv6 to avoid rate limiting issues + if network == "tcp" { + conn, err := netDialer.DialContext(ctx, "tcp4", addr) + if err == nil { + return conn, nil + } + return netDialer.DialContext(ctx, "tcp6", addr) + } + return netDialer.DialContext(ctx, network, addr) + }, } if bc.isShuttingDown() { diff --git a/cmd/chaininfo/arbitrum_chain_info.json b/cmd/chaininfo/arbitrum_chain_info.json index 524433a7b5..f862c6dfbf 100644 --- a/cmd/chaininfo/arbitrum_chain_info.json +++ b/cmd/chaininfo/arbitrum_chain_info.json @@ -164,7 +164,7 @@ "EnableArbOS": true, "AllowDebugPrecompiles": true, "DataAvailabilityCommittee": false, - "InitialArbOSVersion": 31, + "InitialArbOSVersion": 32, "InitialChainOwner": "0x0000000000000000000000000000000000000000", "GenesisBlockNum": 0 } @@ -196,7 +196,7 @@ "EnableArbOS": true, "AllowDebugPrecompiles": true, "DataAvailabilityCommittee": true, - "InitialArbOSVersion": 31, + "InitialArbOSVersion": 32, "InitialChainOwner": "0x0000000000000000000000000000000000000000", "GenesisBlockNum": 0 } diff --git a/cmd/conf/chain.go b/cmd/conf/chain.go index b85f7727b1..28b06aad2b 100644 --- a/cmd/conf/chain.go +++ b/cmd/conf/chain.go @@ -52,23 +52,19 @@ func (c *ParentChainConfig) Validate() error { } type L2Config struct { - ID uint64 `koanf:"id"` - Name string `koanf:"name"` - InfoFiles []string `koanf:"info-files"` - InfoJson string `koanf:"info-json"` - DevWallet genericconf.WalletConfig `koanf:"dev-wallet"` - InfoIpfsUrl string `koanf:"info-ipfs-url"` - InfoIpfsDownloadPath string `koanf:"info-ipfs-download-path"` + ID uint64 `koanf:"id"` + Name string `koanf:"name"` + InfoFiles []string `koanf:"info-files"` + InfoJson string `koanf:"info-json"` + DevWallet genericconf.WalletConfig `koanf:"dev-wallet"` } var L2ConfigDefault = L2Config{ - ID: 0, - Name: "", - InfoFiles: []string{}, // Default file used is chaininfo/arbitrum_chain_info.json, stored in DefaultChainInfo in chain_info.go - InfoJson: "", - DevWallet: genericconf.WalletConfigDefault, - InfoIpfsUrl: "", - InfoIpfsDownloadPath: "/tmp/", + ID: 0, + Name: "", + InfoFiles: []string{}, // Default file used is chaininfo/arbitrum_chain_info.json, stored in DefaultChainInfo in chain_info.go + InfoJson: "", + DevWallet: genericconf.WalletConfigDefault, } func L2ConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -79,9 +75,6 @@ func L2ConfigAddOptions(prefix string, f *flag.FlagSet) { // Dev wallet does not exist unless specified genericconf.WalletConfigAddOptions(prefix+".dev-wallet", f, "") - f.String(prefix+".info-ipfs-url", L2ConfigDefault.InfoIpfsUrl, "url to download chain info file") - f.String(prefix+".info-ipfs-download-path", L2ConfigDefault.InfoIpfsDownloadPath, "path to save temp downloaded file") - } func (c *L2Config) ResolveDirectoryNames(chain string) { diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index e0388e3d4b..c70ceb1d94 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -179,7 +179,7 @@ func main() { defer l1Reader.StopAndWait() nativeToken := common.HexToAddress(*nativeTokenAddressString) - deployedAddresses, err := deploycode.DeployOnL1( + deployedAddresses, err := deploycode.DeployOnParentChain( ctx, l1Reader, l1TransactionOpts, @@ -189,6 +189,7 @@ func main() { arbnode.GenerateRollupConfig(*prod, moduleRoot, ownerAddress, &chainConfig, chainConfigJson, loserEscrowAddress), nativeToken, maxDataSize, + true, ) if err != nil { flag.Usage() diff --git a/cmd/ipfshelper/ipfshelper.bkup_go b/cmd/ipfshelper/ipfshelper.bkup_go deleted file mode 100644 index ccde492ca6..0000000000 --- a/cmd/ipfshelper/ipfshelper.bkup_go +++ /dev/null @@ -1,281 +0,0 @@ -//go:build ipfs -// +build ipfs - -package ipfshelper - -import ( - "context" - "fmt" - "io" - "math/rand" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/ethereum/go-ethereum/log" - "github.com/ipfs/go-libipfs/files" - coreiface "github.com/ipfs/interface-go-ipfs-core" - "github.com/ipfs/interface-go-ipfs-core/options" - "github.com/ipfs/interface-go-ipfs-core/path" - "github.com/ipfs/kubo/config" - "github.com/ipfs/kubo/core" - "github.com/ipfs/kubo/core/coreapi" - "github.com/ipfs/kubo/core/node/libp2p" - "github.com/ipfs/kubo/plugin/loader" - "github.com/ipfs/kubo/repo" - "github.com/ipfs/kubo/repo/fsrepo" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - ma "github.com/multiformats/go-multiaddr" -) - -const DefaultIpfsProfiles = "" - -type IpfsHelper struct { - api coreiface.CoreAPI - node *core.IpfsNode - cfg *config.Config - repoPath string - repo repo.Repo -} - -func (h *IpfsHelper) createRepo(downloadPath string, profiles string) error { - fileInfo, err := os.Stat(downloadPath) - if err != nil { - return fmt.Errorf("failed to stat ipfs repo directory: %w", err) - } - if !fileInfo.IsDir() { - return fmt.Errorf("%s is not a directory", downloadPath) - } - h.repoPath = filepath.Join(downloadPath, "ipfs-repo") - // Create a config with default options and a 2048 bit key - h.cfg, err = config.Init(io.Discard, 2048) - if err != nil { - return err - } - if len(profiles) > 0 { - for _, profile := range strings.Split(profiles, ",") { - transformer, ok := config.Profiles[profile] - if !ok { - return fmt.Errorf("invalid ipfs configuration profile: %s", profile) - } - - if err := transformer.Transform(h.cfg); err != nil { - return err - } - } - } - // Create the repo with the config - // fsrepo.Init initializes new repo only if it's not initialized yet - err = fsrepo.Init(h.repoPath, h.cfg) - if err != nil { - return fmt.Errorf("failed to init ipfs repo: %w", err) - } - h.repo, err = fsrepo.Open(h.repoPath) - if err != nil { - return fmt.Errorf("failed to open ipfs repo: %w", err) - } - return nil -} - -func (h *IpfsHelper) createNode(ctx context.Context, clientOnly bool) error { - var routing libp2p.RoutingOption - if clientOnly { - routing = libp2p.DHTClientOption - } else { - routing = libp2p.DHTOption - } - nodeOptions := &core.BuildCfg{ - Online: true, - Routing: routing, - Repo: h.repo, - } - var err error - h.node, err = core.NewNode(ctx, nodeOptions) - if err != nil { - return err - } - h.api, err = coreapi.NewCoreAPI(h.node) - return err -} - -func (h *IpfsHelper) connectToPeers(ctx context.Context, peers []string) error { - peerInfos := make(map[peer.ID]*peer.AddrInfo, len(peers)) - for _, addressString := range peers { - address, err := ma.NewMultiaddr(addressString) - if err != nil { - return err - } - addressInfo, err := peer.AddrInfoFromP2pAddr(address) - if err != nil { - return err - } - peerInfo, ok := peerInfos[addressInfo.ID] - if !ok { - peerInfo = &peer.AddrInfo{ID: addressInfo.ID} - peerInfos[peerInfo.ID] = peerInfo - } - peerInfo.Addrs = append(peerInfo.Addrs, addressInfo.Addrs...) - } - var wg sync.WaitGroup - wg.Add(len(peerInfos)) - for _, peerInfo := range peerInfos { - go func(peerInfo *peer.AddrInfo) { - defer wg.Done() - err := h.api.Swarm().Connect(ctx, *peerInfo) - if err != nil { - log.Warn("failed to connect to peer", "peerId", peerInfo.ID, "err", err) - return - } - }(peerInfo) - } - wg.Wait() - return nil -} - -func (h *IpfsHelper) GetPeerHostAddresses() ([]string, error) { - addresses, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(h.node.PeerHost)) - if err != nil { - return []string{}, err - } - addressesStrings := make([]string, len(addresses)) - for i, a := range addresses { - addressesStrings[i] = a.String() - } - return addressesStrings, nil -} - -func normalizeCidString(cidString string) string { - if strings.HasPrefix(cidString, "ipfs://") { - return "/ipfs/" + cidString[7:] - } - if strings.HasPrefix(cidString, "ipns://") { - return "/ipns/" + cidString[7:] - } - return cidString -} - -func (h *IpfsHelper) DownloadFile(ctx context.Context, cidString string, destinationDir string) (string, error) { - cidString = normalizeCidString(cidString) - cidPath := path.New(cidString) - resolvedPath, err := h.api.ResolvePath(ctx, cidPath) - if err != nil { - return "", fmt.Errorf("failed to resolve path: %w", err) - } - // first pin the root node, then all its children nodes in random order to improve sharing with peers started at the same time - if err := h.api.Pin().Add(ctx, resolvedPath, options.Pin.Recursive(false)); err != nil { - return "", fmt.Errorf("failed to pin root path: %w", err) - } - links, err := h.api.Object().Links(ctx, resolvedPath) - if err != nil { - return "", fmt.Errorf("failed to get root links: %w", err) - } - log.Info("Pinning ipfs subtrees...") - printProgress := func(done int, all int) { - if all == 0 { - all = 1 // avoid division by 0 - done = 1 - } - fmt.Printf("\033[2K\rPinned %d / %d subtrees (%.2f%%)", done, all, float32(done)/float32(all)*100) - } - permutation := rand.Perm(len(links)) - printProgress(0, len(links)) - for i, j := range permutation { - link := links[j] - if err := h.api.Pin().Add(ctx, path.IpfsPath(link.Cid), options.Pin.Recursive(true)); err != nil { - return "", fmt.Errorf("failed to pin child path: %w", err) - } - printProgress(i+1, len(links)) - } - fmt.Printf("\n") - rootNodeDirectory, err := h.api.Unixfs().Get(ctx, cidPath) - if err != nil { - return "", fmt.Errorf("could not get file with CID: %w", err) - } - log.Info("Writing file...") - outputFilePath := filepath.Join(destinationDir, resolvedPath.Cid().String()) - _ = os.Remove(outputFilePath) - err = files.WriteTo(rootNodeDirectory, outputFilePath) - if err != nil { - return "", fmt.Errorf("could not write out the fetched CID: %w", err) - } - log.Info("Download done.") - return outputFilePath, nil -} - -func (h *IpfsHelper) AddFile(ctx context.Context, filePath string, includeHidden bool) (path.Resolved, error) { - fileInfo, err := os.Stat(filePath) - if err != nil { - return nil, err - } - fileNode, err := files.NewSerialFile(filePath, includeHidden, fileInfo) - if err != nil { - return nil, err - } - return h.api.Unixfs().Add(ctx, fileNode) -} - -func CreateIpfsHelper(ctx context.Context, downloadPath string, clientOnly bool, peerList []string, profiles string) (*IpfsHelper, error) { - return createIpfsHelperImpl(ctx, downloadPath, clientOnly, peerList, profiles) -} - -func (h *IpfsHelper) Close() error { - return h.node.Close() -} - -func setupPlugins() error { - plugins, err := loader.NewPluginLoader("") - if err != nil { - return fmt.Errorf("error loading plugins: %w", err) - } - // Load preloaded and external plugins - if err := plugins.Initialize(); err != nil { - return fmt.Errorf("error initializing plugins: %w", err) - } - if err := plugins.Inject(); err != nil { - return fmt.Errorf("error initializing plugins: %w", err) - } - return nil -} - -var loadPluginsOnce sync.Once - -func createIpfsHelperImpl(ctx context.Context, downloadPath string, clientOnly bool, peerList []string, profiles string) (*IpfsHelper, error) { - var onceErr error - loadPluginsOnce.Do(func() { - onceErr = setupPlugins() - }) - if onceErr != nil { - return nil, onceErr - } - client := IpfsHelper{} - err := client.createRepo(downloadPath, profiles) - if err != nil { - return nil, err - } - err = client.createNode(ctx, clientOnly) - if err != nil { - return nil, err - } - err = client.connectToPeers(ctx, peerList) - if err != nil { - return nil, err - } - return &client, nil -} - -func CanBeIpfsPath(pathString string) bool { - path := path.New(pathString) - return path.IsValid() == nil || - strings.HasPrefix(pathString, "/ipfs/") || - strings.HasPrefix(pathString, "/ipld/") || - strings.HasPrefix(pathString, "/ipns/") || - strings.HasPrefix(pathString, "ipfs://") || - strings.HasPrefix(pathString, "ipns://") -} - -// TODO break abstraction for now til we figure out what fns are needed -func (h *IpfsHelper) GetAPI() coreiface.CoreAPI { - return h.api -} diff --git a/cmd/ipfshelper/ipfshelper_stub.go b/cmd/ipfshelper/ipfshelper_stub.go deleted file mode 100644 index fa6a451927..0000000000 --- a/cmd/ipfshelper/ipfshelper_stub.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !ipfs -// +build !ipfs - -package ipfshelper - -import ( - "context" - "errors" -) - -type IpfsHelper struct{} - -var ErrIpfsNotSupported = errors.New("ipfs not supported") - -var DefaultIpfsProfiles = "default ipfs profiles stub" - -func CanBeIpfsPath(pathString string) bool { - return false -} - -func CreateIpfsHelper(ctx context.Context, downloadPath string, clientOnly bool, peerList []string, profiles string) (*IpfsHelper, error) { - return nil, ErrIpfsNotSupported -} - -func (h *IpfsHelper) DownloadFile(ctx context.Context, cidString string, destinationDir string) (string, error) { - return "", ErrIpfsNotSupported -} - -func (h *IpfsHelper) Close() error { - return ErrIpfsNotSupported -} diff --git a/cmd/ipfshelper/ipfshelper_test.go b/cmd/ipfshelper/ipfshelper_test.go deleted file mode 100644 index 80f10c21f6..0000000000 --- a/cmd/ipfshelper/ipfshelper_test.go +++ /dev/null @@ -1,123 +0,0 @@ -//go:build ipfs -// +build ipfs - -package ipfshelper - -import ( - "bytes" - "context" - "math/rand" - "os" - "path/filepath" - "testing" - "time" - - "github.com/offchainlabs/nitro/util/testhelpers" -) - -func getTempFileWithData(t *testing.T, data []byte) string { - path := filepath.Join(t.TempDir(), "config.json") - err := os.WriteFile(path, []byte(data), 0600) - testhelpers.RequireImpl(t, err) - return path -} - -func fileDataEqual(t *testing.T, path string, expected []byte) bool { - data, err := os.ReadFile(path) - testhelpers.RequireImpl(t, err) - return bytes.Equal(data, expected) -} - -func TestIpfsHelper(t *testing.T) { - ctx := context.Background() - ipfsA, err := createIpfsHelperImpl(ctx, t.TempDir(), false, []string{}, "test") - testhelpers.RequireImpl(t, err) - // add a test file to node A - testData := make([]byte, 1024*1024) - _, err = rand.Read(testData) - testhelpers.RequireImpl(t, err) - testFile := getTempFileWithData(t, testData) - ipfsTestFilePath, err := ipfsA.AddFile(ctx, testFile, false) - testhelpers.RequireImpl(t, err) - testFileCid := ipfsTestFilePath.Cid().String() - addrsA, err := ipfsA.GetPeerHostAddresses() - testhelpers.RequireImpl(t, err) - // create node B connected to node A - ipfsB, err := createIpfsHelperImpl(ctx, t.TempDir(), false, addrsA, "test") - testhelpers.RequireImpl(t, err) - // download the test file with node B - downloadedFile, err := ipfsB.DownloadFile(ctx, testFileCid, t.TempDir()) - testhelpers.RequireImpl(t, err) - if !fileDataEqual(t, downloadedFile, testData) { - testhelpers.FailImpl(t, "Downloaded file does not contain expected data") - } - // clean up node A and test downloading the file from yet another node C - err = ipfsA.Close() - os.RemoveAll(ipfsA.repoPath) - testhelpers.RequireImpl(t, err) - addrsB, err := ipfsB.GetPeerHostAddresses() - testhelpers.RequireImpl(t, err) - ipfsC, err := createIpfsHelperImpl(ctx, t.TempDir(), false, addrsB, "test") - testhelpers.RequireImpl(t, err) - downloadedFile, err = ipfsC.DownloadFile(ctx, testFileCid, t.TempDir()) - testhelpers.RequireImpl(t, err) - if !fileDataEqual(t, downloadedFile, testData) { - testhelpers.FailImpl(t, "Downloaded file does not contain expected data") - } - // make sure closing B and C nodes (A already closed) will make it impossible to download the test file from new node D - ipfsD, err := createIpfsHelperImpl(ctx, t.TempDir(), false, addrsB, "test") - testhelpers.RequireImpl(t, err) - err = ipfsB.Close() - testhelpers.RequireImpl(t, err) - err = ipfsC.Close() - testhelpers.RequireImpl(t, err) - testTimeout := 300 * time.Millisecond - timeoutCtx, cancel := context.WithTimeout(ctx, testTimeout) - defer cancel() - _, err = ipfsD.DownloadFile(timeoutCtx, testFileCid, t.TempDir()) - if err == nil { - testhelpers.FailImpl(t, "Download attempt did not fail as expected") - } - err = ipfsD.Close() - testhelpers.RequireImpl(t, err) -} - -func TestNormalizeCidString(t *testing.T) { - for _, test := range []struct { - input string - expected string - }{ - {"ipfs://QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"}, - {"ipns://k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8", "/ipns/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8"}, - {"ipns://docs.ipfs.tech/introduction/", "/ipns/docs.ipfs.tech/introduction/"}, - {"/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"}, - {"/ipns/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8", "/ipns/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8"}, - {"QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"}, - } { - if res := normalizeCidString(test.input); res != test.expected { - testhelpers.FailImpl(t, "Failed to normalize cid string, input: ", test.input, " got: ", res, " expected: ", test.expected) - } - } -} - -func TestCanBeIpfsPath(t *testing.T) { - correctPaths := []string{ - "QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "/ipns/k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8", - "/ipns/docs.ipfs.tech/introduction/", - "ipfs://QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", - "ipns://k51qzi5uqu5dlvj2baxnqndepeb86cbk3ng7n3i46uzyxzyqj2xjonzllnv0v8", - } - for _, path := range correctPaths { - if !CanBeIpfsPath(path) { - testhelpers.FailImpl(t, "false negative result for path:", path) - } - } - incorrectPaths := []string{"www.ipfs.tech", "https://www.ipfs.tech", "QmIncorrect"} - for _, path := range incorrectPaths { - if CanBeIpfsPath(path) { - testhelpers.FailImpl(t, "false positive result for path:", path) - } - } -} diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index fc59f2d231..f0b303817c 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -37,10 +38,8 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" - "github.com/offchainlabs/nitro/cmd/ipfshelper" "github.com/offchainlabs/nitro/cmd/pruning" "github.com/offchainlabs/nitro/cmd/staterecovery" "github.com/offchainlabs/nitro/execution/gethexec" @@ -58,25 +57,6 @@ func downloadInit(ctx context.Context, initConfig *conf.InitConfig) (string, err if strings.HasPrefix(initConfig.Url, "file:") { return initConfig.Url[5:], nil } - if ipfshelper.CanBeIpfsPath(initConfig.Url) { - ipfsNode, err := ipfshelper.CreateIpfsHelper(ctx, initConfig.DownloadPath, false, []string{}, ipfshelper.DefaultIpfsProfiles) - if err != nil { - return "", err - } - log.Info("Downloading initial database via IPFS", "url", initConfig.Url) - initFile, downloadErr := ipfsNode.DownloadFile(ctx, initConfig.Url, initConfig.DownloadPath) - closeErr := ipfsNode.Close() - if downloadErr != nil { - if closeErr != nil { - log.Error("Failed to close IPFS node after download error", "err", closeErr) - } - return "", fmt.Errorf("Failed to download file from IPFS: %w", downloadErr) - } - if closeErr != nil { - return "", fmt.Errorf("Failed to close IPFS node: %w", err) - } - return initFile, nil - } log.Info("Downloading initial database", "url", initConfig.Url) if !initConfig.ValidateChecksum { file, err := downloadFile(ctx, initConfig, initConfig.Url, nil) @@ -354,12 +334,12 @@ func validateBlockChain(blockChain *core.BlockChain, chainConfig *params.ChainCo } // Make sure we don't allow accidentally downgrading ArbOS if chainConfig.DebugMode() { - if currentArbosState.ArbOSVersion() > currentArbosState.MaxDebugArbosVersionSupported() { - return fmt.Errorf("attempted to launch node in debug mode with ArbOS version %v on ArbOS state with version %v", currentArbosState.MaxDebugArbosVersionSupported(), currentArbosState.ArbOSVersion()) + if currentArbosState.ArbOSVersion() > arbosState.MaxDebugArbosVersionSupported { + return fmt.Errorf("attempted to launch node in debug mode with ArbOS version %v on ArbOS state with version %v", arbosState.MaxDebugArbosVersionSupported, currentArbosState.ArbOSVersion()) } } else { - if currentArbosState.ArbOSVersion() > currentArbosState.MaxArbosVersionSupported() { - return fmt.Errorf("attempted to launch node with ArbOS version %v on ArbOS state with version %v", currentArbosState.MaxArbosVersionSupported(), currentArbosState.ArbOSVersion()) + if currentArbosState.ArbOSVersion() > arbosState.MaxArbosVersionSupported { + return fmt.Errorf("attempted to launch node with ArbOS version %v on ArbOS state with version %v", arbosState.MaxArbosVersionSupported, currentArbosState.ArbOSVersion()) } } @@ -560,7 +540,7 @@ func rebuildLocalWasm(ctx context.Context, config *gethexec.Config, l2BlockChain return chainDb, l2BlockChain, nil } -func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, targetConfig *gethexec.StylusTargetConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) { +func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, targetConfig *gethexec.StylusTargetConfig, persistentConfig *conf.PersistentConfig, l1Client *ethclient.Client, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) { if !config.Init.Force { if readOnlyDb, err := stack.OpenDatabaseWithFreezerWithExtraOptions("l2chaindata", 0, 0, config.Persistent.Ancient, "l2chaindata/", true, persistentConfig.Pebble.ExtraOptions("l2chaindata")); err == nil { if chainConfig := gethexec.TryReadStoredChainConfig(readOnlyDb); chainConfig != nil { @@ -732,8 +712,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err != nil { return chainDb, nil, err } - combinedL2ChainInfoFiles := aggregateL2ChainInfoFiles(ctx, config.Chain.InfoFiles, config.Chain.InfoIpfsUrl, config.Chain.InfoIpfsDownloadPath) - chainConfig, err = chaininfo.GetChainConfig(new(big.Int).SetUint64(config.Chain.ID), config.Chain.Name, genesisBlockNr, combinedL2ChainInfoFiles, config.Chain.InfoJson) + chainConfig, err = chaininfo.GetChainConfig(new(big.Int).SetUint64(config.Chain.ID), config.Chain.Name, genesisBlockNr, config.Chain.InfoFiles, config.Chain.InfoJson) if err != nil { return chainDb, nil, err } diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index a13a92fdc1..076ac66ec4 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -249,7 +249,7 @@ func mainImpl() int { // If sequencer and signing is enabled or batchposter is enabled without // external signing sequencer will need a key. sequencerNeedsKey := (nodeConfig.Node.Sequencer && !nodeConfig.Node.Feed.Output.DisableSigning) || - (nodeConfig.Node.BatchPoster.Enable && nodeConfig.Node.BatchPoster.DataPoster.ExternalSigner.URL == "") + (nodeConfig.Node.BatchPoster.Enable && (nodeConfig.Node.BatchPoster.DataPoster.ExternalSigner.URL == "" || nodeConfig.Node.DataAvailability.Enable)) validatorNeedsKey := nodeConfig.Node.Staker.OnlyCreateWalletContract || (nodeConfig.Node.Staker.Enable && !strings.EqualFold(nodeConfig.Node.Staker.Strategy, "watchtower") && nodeConfig.Node.Staker.DataPoster.ExternalSigner.URL == "") @@ -285,8 +285,6 @@ func mainImpl() int { } } - combinedL2ChainInfoFile := aggregateL2ChainInfoFiles(ctx, nodeConfig.Chain.InfoFiles, nodeConfig.Chain.InfoIpfsUrl, nodeConfig.Chain.InfoIpfsDownloadPath) - if nodeConfig.Node.Staker.Enable { if !nodeConfig.Node.ParentChainReader.Enable { flag.Usage() @@ -335,7 +333,7 @@ func mainImpl() int { log.Info("connected to l1 chain", "l1url", nodeConfig.ParentChain.Connection.URL, "l1chainid", nodeConfig.ParentChain.ID) - rollupAddrs, err = chaininfo.GetRollupAddressesConfig(nodeConfig.Chain.ID, nodeConfig.Chain.Name, combinedL2ChainInfoFile, nodeConfig.Chain.InfoJson) + rollupAddrs, err = chaininfo.GetRollupAddressesConfig(nodeConfig.Chain.ID, nodeConfig.Chain.Name, nodeConfig.Chain.InfoFiles, nodeConfig.Chain.InfoJson) if err != nil { log.Crit("error getting rollup addresses", "err", err) } @@ -367,7 +365,7 @@ func mainImpl() int { log.Crit("--node.validator.only-create-wallet-contract conflicts with --node.dangerous.no-l1-listener") } // Just create validator smart wallet if needed then exit - deployInfo, err := chaininfo.GetRollupAddressesConfig(nodeConfig.Chain.ID, nodeConfig.Chain.Name, combinedL2ChainInfoFile, nodeConfig.Chain.InfoJson) + deployInfo, err := chaininfo.GetRollupAddressesConfig(nodeConfig.Chain.ID, nodeConfig.Chain.Name, nodeConfig.Chain.InfoFiles, nodeConfig.Chain.InfoJson) if err != nil { log.Crit("error getting rollup addresses config", "err", err) } @@ -472,6 +470,10 @@ func mainImpl() int { if nodeConfig.BlocksReExecutor.Enable && l2BlockChain != nil { blocksReExecutor = blocksreexecutor.New(&nodeConfig.BlocksReExecutor, l2BlockChain, fatalErrChan) if nodeConfig.Init.ThenQuit { + if err := gethexec.PopulateStylusTargetCache(&nodeConfig.Execution.StylusTarget); err != nil { + log.Error("error populating stylus target cache", "err", err) + return 1 + } success := make(chan struct{}) blocksReExecutor.Start(ctx, success) deferFuncs = append(deferFuncs, func() { blocksReExecutor.StopAndWait() }) @@ -489,7 +491,7 @@ func mainImpl() int { return 0 } - chainInfo, err := chaininfo.ProcessChainInfo(nodeConfig.Chain.ID, nodeConfig.Chain.Name, combinedL2ChainInfoFile, nodeConfig.Chain.InfoJson) + chainInfo, err := chaininfo.ProcessChainInfo(nodeConfig.Chain.ID, nodeConfig.Chain.Name, nodeConfig.Chain.InfoFiles, nodeConfig.Chain.InfoJson) if err != nil { log.Error("error processing l2 chain info", "err", err) return 1 @@ -836,12 +838,10 @@ func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.Wa l2ChainId := k.Int64("chain.id") l2ChainName := k.String("chain.name") - l2ChainInfoIpfsUrl := k.String("chain.info-ipfs-url") - l2ChainInfoIpfsDownloadPath := k.String("chain.info-ipfs-download-path") l2ChainInfoFiles := k.Strings("chain.info-files") l2ChainInfoJson := k.String("chain.info-json") // #nosec G115 - err = applyChainParameters(ctx, k, uint64(l2ChainId), l2ChainName, l2ChainInfoFiles, l2ChainInfoJson, l2ChainInfoIpfsUrl, l2ChainInfoIpfsDownloadPath) + err = applyChainParameters(k, uint64(l2ChainId), l2ChainName, l2ChainInfoFiles, l2ChainInfoJson) if err != nil { return nil, nil, err } @@ -904,20 +904,8 @@ func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.Wa return &nodeConfig, &l2DevWallet, nil } -func aggregateL2ChainInfoFiles(ctx context.Context, l2ChainInfoFiles []string, l2ChainInfoIpfsUrl string, l2ChainInfoIpfsDownloadPath string) []string { - if l2ChainInfoIpfsUrl != "" { - l2ChainInfoIpfsFile, err := util.GetL2ChainInfoIpfsFile(ctx, l2ChainInfoIpfsUrl, l2ChainInfoIpfsDownloadPath) - if err != nil { - log.Error("error getting l2 chain info file from ipfs", "err", err) - } - l2ChainInfoFiles = append(l2ChainInfoFiles, l2ChainInfoIpfsFile) - } - return l2ChainInfoFiles -} - -func applyChainParameters(ctx context.Context, k *koanf.Koanf, chainId uint64, chainName string, l2ChainInfoFiles []string, l2ChainInfoJson string, l2ChainInfoIpfsUrl string, l2ChainInfoIpfsDownloadPath string) error { - combinedL2ChainInfoFiles := aggregateL2ChainInfoFiles(ctx, l2ChainInfoFiles, l2ChainInfoIpfsUrl, l2ChainInfoIpfsDownloadPath) - chainInfo, err := chaininfo.ProcessChainInfo(chainId, chainName, combinedL2ChainInfoFiles, l2ChainInfoJson) +func applyChainParameters(k *koanf.Koanf, chainId uint64, chainName string, l2ChainInfoFiles []string, l2ChainInfoJson string) error { + chainInfo, err := chaininfo.ProcessChainInfo(chainId, chainName, l2ChainInfoFiles, l2ChainInfoJson) if err != nil { return err } @@ -926,7 +914,7 @@ func applyChainParameters(ctx context.Context, k *koanf.Koanf, chainId uint64, c parentChainIsArbitrum = *chainInfo.ParentChainIsArbitrum } else { log.Warn("Chain info field parent-chain-is-arbitrum is missing, in the future this will be required", "chainId", chainInfo.ChainConfig.ChainID, "parentChainId", chainInfo.ParentChainId) - _, err := chaininfo.ProcessChainInfo(chainInfo.ParentChainId, "", combinedL2ChainInfoFiles, "") + _, err := chaininfo.ProcessChainInfo(chainInfo.ParentChainId, "", l2ChainInfoFiles, "") if err == nil { parentChainIsArbitrum = true } @@ -1006,14 +994,15 @@ func initReorg(initConfig conf.InitConfig, chainConfig *params.ChainConfig, inbo return nil } // Reorg out the batch containing the next message - var missing bool + var found bool var err error - batchCount, missing, err = inboxTracker.FindInboxBatchContainingMessage(messageIndex + 1) + batchCount, found, err = inboxTracker.FindInboxBatchContainingMessage(messageIndex + 1) if err != nil { return err } - if missing { - return fmt.Errorf("cannot reorg to unknown message index %v", messageIndex) + if !found { + log.Warn("init-reorg: no need to reorg, because message ahead of chain", "messageIndex", messageIndex) + return nil } } return inboxTracker.ReorgBatchesTo(batchCount) diff --git a/cmd/pruning/pruning.go b/cmd/pruning/pruning.go index 6fc7741478..0755f5ff9e 100644 --- a/cmd/pruning/pruning.go +++ b/cmd/pruning/pruning.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/pruner" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -80,7 +81,7 @@ func (r *importantRoots) addHeader(header *types.Header, overwrite bool) error { var hashListRegex = regexp.MustCompile("^(0x)?[0-9a-fA-F]{64}(,(0x)?[0-9a-fA-F]{64})*$") // Finds important roots to retain while proving -func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) ([]common.Hash, error) { +func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client *ethclient.Client, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) ([]common.Hash, error) { chainConfig := gethexec.TryReadStoredChainConfig(chainDb) if chainConfig == nil { return nil, errors.New("database doesn't have a chain config (was this node initialized?)") @@ -233,7 +234,7 @@ func findImportantRoots(ctx context.Context, chainDb ethdb.Database, stack *node return roots.roots, nil } -func PruneChainDb(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) error { +func PruneChainDb(ctx context.Context, chainDb ethdb.Database, stack *node.Node, initConfig *conf.InitConfig, cacheConfig *core.CacheConfig, persistentConfig *conf.PersistentConfig, l1Client *ethclient.Client, rollupAddrs chaininfo.RollupAddresses, validatorRequired bool) error { if cacheConfig.StateScheme == rawdb.PathScheme { return nil } diff --git a/cmd/replay/main.go b/cmd/replay/main.go index 0fe56eb4c9..661040ea10 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -291,7 +292,7 @@ func main() { message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) chainContext := WavmChainContext{} - newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false) + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, core.MessageReplayMode) if err != nil { panic(err) } diff --git a/cmd/seq-coordinator-manager/rediscoordinator/redis_coordinator.go b/cmd/seq-coordinator-manager/rediscoordinator/redis_coordinator.go index e963c0e96c..b897b23252 100644 --- a/cmd/seq-coordinator-manager/rediscoordinator/redis_coordinator.go +++ b/cmd/seq-coordinator-manager/rediscoordinator/redis_coordinator.go @@ -5,8 +5,8 @@ import ( "errors" "strings" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/redis/go-redis/v9" ) // RedisCoordinator builds upon RedisCoordinator of redisutil with additional functionality diff --git a/cmd/util/chaininfoutil.go b/cmd/util/chaininfoutil.go deleted file mode 100644 index 906aa234ed..0000000000 --- a/cmd/util/chaininfoutil.go +++ /dev/null @@ -1,29 +0,0 @@ -package util - -import ( - "context" - "fmt" - - "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/cmd/ipfshelper" -) - -func GetL2ChainInfoIpfsFile(ctx context.Context, l2ChainInfoIpfsUrl string, l2ChainInfoIpfsDownloadPath string) (string, error) { - ipfsNode, err := ipfshelper.CreateIpfsHelper(ctx, l2ChainInfoIpfsDownloadPath, false, []string{}, ipfshelper.DefaultIpfsProfiles) - if err != nil { - return "", err - } - log.Info("Downloading l2 info file via IPFS", "url", l2ChainInfoIpfsDownloadPath) - l2ChainInfoFile, downloadErr := ipfsNode.DownloadFile(ctx, l2ChainInfoIpfsUrl, l2ChainInfoIpfsDownloadPath) - closeErr := ipfsNode.Close() - if downloadErr != nil { - if closeErr != nil { - log.Error("Failed to close IPFS node after download error", "err", closeErr) - } - return "", fmt.Errorf("failed to download file from IPFS: %w", downloadErr) - } - if closeErr != nil { - return "", fmt.Errorf("failed to close IPFS node: %w", err) - } - return l2ChainInfoFile, nil -} diff --git a/contracts b/contracts index 23fc796282..b140ed63ac 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 23fc79628292aa5d604d449fed48937ae7faeb2f +Subproject commit b140ed63acdb53cb906ffd1fa3c36fdbd474364e diff --git a/das/aggregator.go b/das/aggregator.go index e7460fa371..372e448e76 100644 --- a/das/aggregator.go +++ b/das/aggregator.go @@ -15,11 +15,11 @@ import ( flag "github.com/spf13/pflag" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/offchainlabs/nitro/arbstate/daprovider" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/solgen/go/bridgegen" @@ -114,7 +114,7 @@ func NewAggregator(ctx context.Context, config DataAvailabilityConfig, services func NewAggregatorWithL1Info( config DataAvailabilityConfig, services []ServiceDetails, - l1client arbutil.L1Interface, + l1client *ethclient.Client, seqInboxAddress common.Address, ) (*Aggregator, error) { seqInboxCaller, err := bridgegen.NewSequencerInboxCaller(seqInboxAddress, l1client) diff --git a/das/chain_fetch_das.go b/das/chain_fetch_das.go index 465b54f400..4de6c981cf 100644 --- a/das/chain_fetch_das.go +++ b/das/chain_fetch_das.go @@ -12,8 +12,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/solgen/go/bridgegen" ) @@ -42,7 +42,7 @@ type KeysetFetcher struct { keysetCache syncedKeysetCache } -func NewKeysetFetcher(l1client arbutil.L1Interface, seqInboxAddr common.Address) (*KeysetFetcher, error) { +func NewKeysetFetcher(l1client *ethclient.Client, seqInboxAddr common.Address) (*KeysetFetcher, error) { seqInbox, err := bridgegen.NewSequencerInbox(seqInboxAddr, l1client) if err != nil { return nil, err diff --git a/das/das.go b/das/das.go index 6bd02fbc75..0b03c05ad6 100644 --- a/das/das.go +++ b/das/das.go @@ -41,9 +41,10 @@ type DataAvailabilityConfig struct { LocalCache CacheConfig `koanf:"local-cache"` RedisCache RedisConfig `koanf:"redis-cache"` - LocalDBStorage LocalDBStorageConfig `koanf:"local-db-storage"` - LocalFileStorage LocalFileStorageConfig `koanf:"local-file-storage"` - S3Storage S3StorageServiceConfig `koanf:"s3-storage"` + LocalDBStorage LocalDBStorageConfig `koanf:"local-db-storage"` + LocalFileStorage LocalFileStorageConfig `koanf:"local-file-storage"` + S3Storage S3StorageServiceConfig `koanf:"s3-storage"` + GoogleCloudStorage GoogleCloudStorageServiceConfig `koanf:"google-cloud-storage"` MigrateLocalDBToFileStorage bool `koanf:"migrate-local-db-to-file-storage"` @@ -114,6 +115,7 @@ func dataAvailabilityConfigAddOptions(prefix string, f *flag.FlagSet, r role) { LocalDBStorageConfigAddOptions(prefix+".local-db-storage", f) LocalFileStorageConfigAddOptions(prefix+".local-file-storage", f) S3ConfigAddOptions(prefix+".s3-storage", f) + GoogleCloudConfigAddOptions(prefix+".google-cloud-storage", f) f.Bool(prefix+".migrate-local-db-to-file-storage", DefaultDataAvailabilityConfig.MigrateLocalDBToFileStorage, "daserver will migrate all data on startup from local-db-storage to local-file-storage, then mark local-db-storage as unusable") // Key config for storage diff --git a/das/dasRpcClient.go b/das/dasRpcClient.go index 7d48ed796d..d6e2c389c9 100644 --- a/das/dasRpcClient.go +++ b/das/dasRpcClient.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "golang.org/x/sync/errgroup" "github.com/ethereum/go-ethereum/rpc" @@ -21,6 +22,17 @@ import ( "github.com/offchainlabs/nitro/util/signature" ) +var ( + rpcClientStoreRequestGauge = metrics.NewRegisteredGauge("arb/das/rpcclient/store/requests", nil) + rpcClientStoreSuccessGauge = metrics.NewRegisteredGauge("arb/das/rpcclient/store/success", nil) + rpcClientStoreFailureGauge = metrics.NewRegisteredGauge("arb/das/rpcclient/store/failure", nil) + rpcClientStoreStoredBytesGauge = metrics.NewRegisteredGauge("arb/das/rpcclient/store/bytes", nil) + rpcClientStoreDurationHistogram = metrics.NewRegisteredHistogram("arb/das/rpcclient/store/duration", nil, metrics.NewBoundedHistogramSample()) + + rpcClientSendChunkSuccessGauge = metrics.NewRegisteredGauge("arb/das/rpcclient/sendchunk/success", nil) + rpcClientSendChunkFailureGauge = metrics.NewRegisteredGauge("arb/das/rpcclient/sendchunk/failure", nil) +) + type DASRPCClient struct { // implements DataAvailabilityService clnt *rpc.Client url string @@ -58,8 +70,20 @@ func NewDASRPCClient(target string, signer signature.DataSignerFunc, maxStoreChu } func (c *DASRPCClient) Store(ctx context.Context, message []byte, timeout uint64) (*daprovider.DataAvailabilityCertificate, error) { + rpcClientStoreRequestGauge.Inc(1) + start := time.Now() + success := false + defer func() { + if success { + rpcClientStoreSuccessGauge.Inc(1) + } else { + rpcClientStoreFailureGauge.Inc(1) + } + rpcClientStoreDurationHistogram.Update(time.Since(start).Nanoseconds()) + }() + // #nosec G115 - timestamp := uint64(time.Now().Unix()) + timestamp := uint64(start.Unix()) nChunks := uint64(len(message)) / c.chunkSize lastChunkSize := uint64(len(message)) % c.chunkSize if lastChunkSize > 0 { @@ -77,6 +101,7 @@ func (c *DASRPCClient) Store(ctx context.Context, message []byte, timeout uint64 var startChunkedStoreResult StartChunkedStoreResult if err := c.clnt.CallContext(ctx, &startChunkedStoreResult, "das_startChunkedStore", hexutil.Uint64(timestamp), hexutil.Uint64(nChunks), hexutil.Uint64(c.chunkSize), hexutil.Uint64(totalSize), hexutil.Uint64(timeout), hexutil.Bytes(startReqSig)); err != nil { if strings.Contains(err.Error(), "the method das_startChunkedStore does not exist") { + log.Info("Legacy store is used by the DAS client", "url", c.url) return c.legacyStore(ctx, message, timeout) } return nil, err @@ -116,6 +141,9 @@ func (c *DASRPCClient) Store(ctx context.Context, message []byte, timeout uint64 return nil, err } + rpcClientStoreStoredBytesGauge.Inc(int64(len(message))) + success = true + return &daprovider.DataAvailabilityCertificate{ DataHash: common.BytesToHash(storeResult.DataHash), Timeout: uint64(storeResult.Timeout), @@ -133,8 +161,10 @@ func (c *DASRPCClient) sendChunk(ctx context.Context, batchId, i uint64, chunk [ } if err := c.clnt.CallContext(ctx, nil, "das_sendChunk", hexutil.Uint64(batchId), hexutil.Uint64(i), hexutil.Bytes(chunk), hexutil.Bytes(chunkReqSig)); err != nil { + rpcClientSendChunkFailureGauge.Inc(1) return err } + rpcClientSendChunkSuccessGauge.Inc(1) return nil } diff --git a/das/factory.go b/das/factory.go index 7f696912b3..3e9771f932 100644 --- a/das/factory.go +++ b/das/factory.go @@ -9,8 +9,8 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/signature" @@ -65,6 +65,15 @@ func CreatePersistentStorageService( storageServices = append(storageServices, s) } + if config.GoogleCloudStorage.Enable { + s, err := NewGoogleCloudStorageService(config.GoogleCloudStorage) + if err != nil { + return nil, nil, err + } + lifecycleManager.Register(s) + storageServices = append(storageServices, s) + } + if len(storageServices) > 1 { s, err := NewRedundantStorageService(ctx, storageServices) if err != nil { @@ -112,7 +121,7 @@ func CreateBatchPosterDAS( ctx context.Context, config *DataAvailabilityConfig, dataSigner signature.DataSignerFunc, - l1Reader arbutil.L1Interface, + l1Reader *ethclient.Client, sequencerInboxAddr common.Address, ) (DataAvailabilityServiceWriter, DataAvailabilityServiceReader, *KeysetFetcher, *LifecycleManager, error) { if !config.Enable { diff --git a/das/google_cloud_storage_service.go b/das/google_cloud_storage_service.go new file mode 100644 index 0000000000..829f4b5265 --- /dev/null +++ b/das/google_cloud_storage_service.go @@ -0,0 +1,205 @@ +package das + +import ( + "context" + "fmt" + "io" + "math" + "sort" + "time" + + googlestorage "cloud.google.com/go/storage" + "github.com/google/go-cmp/cmp" + flag "github.com/spf13/pflag" + "google.golang.org/api/option" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/offchainlabs/nitro/arbstate/daprovider" + "github.com/offchainlabs/nitro/das/dastree" + "github.com/offchainlabs/nitro/util/pretty" +) + +type GoogleCloudStorageOperator interface { + Bucket(name string) *googlestorage.BucketHandle + Upload(ctx context.Context, bucket, objectPrefix string, value []byte) error + Download(ctx context.Context, bucket, objectPrefix string, key common.Hash) ([]byte, error) + Close(ctx context.Context) error +} + +type GoogleCloudStorageClient struct { + client *googlestorage.Client +} + +func (g *GoogleCloudStorageClient) Bucket(name string) *googlestorage.BucketHandle { + return g.client.Bucket(name) +} + +func (g *GoogleCloudStorageClient) Upload(ctx context.Context, bucket, objectPrefix string, value []byte) error { + obj := g.client.Bucket(bucket).Object(objectPrefix + EncodeStorageServiceKey(dastree.Hash(value))) + w := obj.NewWriter(ctx) + + if _, err := fmt.Fprintln(w, value); err != nil { + return err + } + return w.Close() + +} + +func (g *GoogleCloudStorageClient) Download(ctx context.Context, bucket, objectPrefix string, key common.Hash) ([]byte, error) { + obj := g.client.Bucket(bucket).Object(objectPrefix + EncodeStorageServiceKey(key)) + reader, err := obj.NewReader(ctx) + if err != nil { + return nil, err + } + return io.ReadAll(reader) +} + +func (g *GoogleCloudStorageClient) Close(ctx context.Context) error { + return g.client.Close() +} + +type GoogleCloudStorageServiceConfig struct { + Enable bool `koanf:"enable"` + AccessToken string `koanf:"access-token"` + Bucket string `koanf:"bucket"` + ObjectPrefix string `koanf:"object-prefix"` + EnableExpiry bool `koanf:"enable-expiry"` + MaxRetention time.Duration `koanf:"max-retention"` +} + +var DefaultGoogleCloudStorageServiceConfig = GoogleCloudStorageServiceConfig{} + +func GoogleCloudConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Bool(prefix+".enable", DefaultGoogleCloudStorageServiceConfig.Enable, "EXPERIMENTAL/unsupported - enable storage/retrieval of sequencer batch data from an Google Cloud Storage bucket") + f.String(prefix+".access-token", DefaultGoogleCloudStorageServiceConfig.AccessToken, "Google Cloud Storage access token") + f.String(prefix+".bucket", DefaultGoogleCloudStorageServiceConfig.Bucket, "Google Cloud Storage bucket") + f.String(prefix+".object-prefix", DefaultGoogleCloudStorageServiceConfig.ObjectPrefix, "prefix to add to Google Cloud Storage objects") + f.Bool(prefix+".enable-expiry", DefaultLocalFileStorageConfig.EnableExpiry, "enable expiry of batches") + f.Duration(prefix+".max-retention", DefaultLocalFileStorageConfig.MaxRetention, "store requests with expiry times farther in the future than max-retention will be rejected") + +} + +type GoogleCloudStorageService struct { + operator GoogleCloudStorageOperator + bucket string + objectPrefix string + enableExpiry bool + maxRetention time.Duration +} + +func NewGoogleCloudStorageService(config GoogleCloudStorageServiceConfig) (StorageService, error) { + var client *googlestorage.Client + var err error + // Note that if the credentials are not specified, the client library will find credentials using ADC(Application Default Credentials) + // https://cloud.google.com/docs/authentication/provide-credentials-adc. + if config.AccessToken == "" { + client, err = googlestorage.NewClient(context.Background()) + } else { + client, err = googlestorage.NewClient(context.Background(), option.WithCredentialsJSON([]byte(config.AccessToken))) + } + if err != nil { + return nil, fmt.Errorf("error creating Google Cloud Storage client: %w", err) + } + service := &GoogleCloudStorageService{ + operator: &GoogleCloudStorageClient{client: client}, + bucket: config.Bucket, + objectPrefix: config.ObjectPrefix, + enableExpiry: config.EnableExpiry, + maxRetention: config.MaxRetention, + } + if config.EnableExpiry { + lifecycleRule := googlestorage.LifecycleRule{ + Action: googlestorage.LifecycleAction{Type: "Delete"}, + Condition: googlestorage.LifecycleCondition{AgeInDays: int64(config.MaxRetention.Hours() / 24)}, // Objects older than 30 days + } + ctx := context.Background() + bucket := service.operator.Bucket(service.bucket) + // check if bucket exists (and others), and update expiration policy if enabled + attrs, err := bucket.Attrs(ctx) + if err != nil { + return nil, fmt.Errorf("error getting bucket attributes: %w", err) + } + attrs.Lifecycle.Rules = append(attrs.Lifecycle.Rules, lifecycleRule) + + bucketAttrsToUpdate := googlestorage.BucketAttrsToUpdate{ + Lifecycle: &attrs.Lifecycle, + } + if _, err := bucket.Update(ctx, bucketAttrsToUpdate); err != nil { + return nil, fmt.Errorf("failed to update bucket lifecycle: %w", err) + } + } + return service, nil +} + +func (gcs *GoogleCloudStorageService) Put(ctx context.Context, data []byte, expiry uint64) error { + logPut("das.GoogleCloudStorageService.Store", data, expiry, gcs) + if expiry > math.MaxInt64 { + return fmt.Errorf("request expiry time (%v) exceeds max int64", expiry) + } + // #nosec G115 + expiryTime := time.Unix(int64(expiry), 0) + currentTimePlusRetention := time.Now().Add(gcs.maxRetention) + if expiryTime.After(currentTimePlusRetention) { + return fmt.Errorf("requested expiry time (%v) exceeds current time plus maximum allowed retention period(%v)", expiryTime, currentTimePlusRetention) + } + if err := gcs.operator.Upload(ctx, gcs.bucket, gcs.objectPrefix, data); err != nil { + log.Error("das.GoogleCloudStorageService.Store", "err", err) + return err + } + return nil +} + +func (gcs *GoogleCloudStorageService) GetByHash(ctx context.Context, key common.Hash) ([]byte, error) { + log.Trace("das.GoogleCloudStorageService.GetByHash", "key", pretty.PrettyHash(key), "this", gcs) + buf, err := gcs.operator.Download(ctx, gcs.bucket, gcs.objectPrefix, key) + if err != nil { + log.Error("das.GoogleCloudStorageService.GetByHash", "err", err) + return nil, err + } + return buf, nil +} + +func (gcs *GoogleCloudStorageService) ExpirationPolicy(ctx context.Context) (daprovider.ExpirationPolicy, error) { + if gcs.enableExpiry { + return daprovider.KeepForever, nil + } + return daprovider.DiscardAfterDataTimeout, nil +} + +func (gcs *GoogleCloudStorageService) Sync(ctx context.Context) error { + return nil +} + +func (gcs *GoogleCloudStorageService) Close(ctx context.Context) error { + return gcs.operator.Close(ctx) +} + +func (gcs *GoogleCloudStorageService) String() string { + return fmt.Sprintf("GoogleCloudStorageService(:%s)", gcs.bucket) +} + +func (gcs *GoogleCloudStorageService) HealthCheck(ctx context.Context) error { + bucket := gcs.operator.Bucket(gcs.bucket) + // check if we have bucket permissions + permissions := []string{ + "storage.buckets.get", + "storage.buckets.list", + "storage.objects.create", + "storage.objects.delete", + "storage.objects.list", + "storage.objects.get", + } + perms, err := bucket.IAM().TestPermissions(ctx, permissions) + if err != nil { + return fmt.Errorf("could not check permissions: %w", err) + } + sort.Strings(permissions) + sort.Strings(perms) + if !cmp.Equal(perms, permissions) { + return fmt.Errorf("permissions mismatch (-want +got):\n%s", cmp.Diff(permissions, perms)) + } + + return nil +} diff --git a/das/google_cloud_storage_service_test.go b/das/google_cloud_storage_service_test.go new file mode 100644 index 0000000000..799d999bad --- /dev/null +++ b/das/google_cloud_storage_service_test.go @@ -0,0 +1,84 @@ +package das + +import ( + "bytes" + googlestorage "cloud.google.com/go/storage" + "context" + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/das/dastree" + "testing" + "time" +) + +type mockGCSClient struct { + storage map[string][]byte +} + +func (c *mockGCSClient) Bucket(name string) *googlestorage.BucketHandle { + return nil +} + +func (c *mockGCSClient) Download(ctx context.Context, bucket, objectPrefix string, key common.Hash) ([]byte, error) { + value, ok := c.storage[objectPrefix+EncodeStorageServiceKey(key)] + if !ok { + return nil, ErrNotFound + } + return value, nil +} + +func (c *mockGCSClient) Close(ctx context.Context) error { + return nil +} + +func (c *mockGCSClient) Upload(ctx context.Context, bucket, objectPrefix string, value []byte) error { + key := objectPrefix + EncodeStorageServiceKey(dastree.Hash(value)) + c.storage[key] = value + return nil +} + +func NewTestGoogleCloudStorageService(ctx context.Context, googleCloudStorageConfig GoogleCloudStorageServiceConfig) (StorageService, error) { + return &GoogleCloudStorageService{ + bucket: googleCloudStorageConfig.Bucket, + objectPrefix: googleCloudStorageConfig.ObjectPrefix, + operator: &mockGCSClient{ + storage: make(map[string][]byte), + }, + maxRetention: googleCloudStorageConfig.MaxRetention, + }, nil +} + +func TestNewGoogleCloudStorageService(t *testing.T) { + ctx := context.Background() + // #nosec G115 + expiry := uint64(time.Now().Add(time.Hour).Unix()) + googleCloudStorageServiceConfig := DefaultGoogleCloudStorageServiceConfig + googleCloudStorageServiceConfig.Enable = true + googleCloudStorageServiceConfig.MaxRetention = time.Hour * 24 + googleCloudService, err := NewTestGoogleCloudStorageService(ctx, googleCloudStorageServiceConfig) + Require(t, err) + + val1 := []byte("The first value") + val1CorrectKey := dastree.Hash(val1) + val2IncorrectKey := dastree.Hash(append(val1, 0)) + + _, err = googleCloudService.GetByHash(ctx, val1CorrectKey) + if !errors.Is(err, ErrNotFound) { + t.Fatal(err) + } + + err = googleCloudService.Put(ctx, val1, expiry) + Require(t, err) + + _, err = googleCloudService.GetByHash(ctx, val2IncorrectKey) + if !errors.Is(err, ErrNotFound) { + t.Fatal(err) + } + + val, err := googleCloudService.GetByHash(ctx, val1CorrectKey) + Require(t, err) + if !bytes.Equal(val, val1) { + t.Fatal(val, val1) + } + +} diff --git a/das/redis_storage_service.go b/das/redis_storage_service.go index 210d5cb2d4..e57240992c 100644 --- a/das/redis_storage_service.go +++ b/das/redis_storage_service.go @@ -12,11 +12,11 @@ import ( "golang.org/x/crypto/sha3" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/das/dastree" "github.com/offchainlabs/nitro/util/pretty" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/redis/go-redis/v9" flag "github.com/spf13/pflag" "github.com/ethereum/go-ethereum/common" diff --git a/das/rpc_aggregator.go b/das/rpc_aggregator.go index 1b3e2b8f44..9cf481e015 100644 --- a/das/rpc_aggregator.go +++ b/das/rpc_aggregator.go @@ -21,7 +21,7 @@ import ( "github.com/offchainlabs/nitro/util/signature" "github.com/ethereum/go-ethereum/common" - "github.com/offchainlabs/nitro/arbutil" + "github.com/ethereum/go-ethereum/ethclient" ) type BackendConfig struct { @@ -83,7 +83,7 @@ func NewRPCAggregator(ctx context.Context, config DataAvailabilityConfig, signer return NewAggregator(ctx, config, services) } -func NewRPCAggregatorWithL1Info(config DataAvailabilityConfig, l1client arbutil.L1Interface, seqInboxAddress common.Address, signer signature.DataSignerFunc) (*Aggregator, error) { +func NewRPCAggregatorWithL1Info(config DataAvailabilityConfig, l1client *ethclient.Client, seqInboxAddress common.Address, signer signature.DataSignerFunc) (*Aggregator, error) { services, err := ParseServices(config.RPCAggregator, signer) if err != nil { return nil, err diff --git a/das/sign_after_store_das_writer.go b/das/sign_after_store_das_writer.go index 40b03847d8..b09ed091cc 100644 --- a/das/sign_after_store_das_writer.go +++ b/das/sign_after_store_das_writer.go @@ -81,6 +81,7 @@ func NewSignAfterStoreDASWriter(ctx context.Context, config DataAvailabilityConf if err != nil { return nil, err } + log.Info("DAS public key used for signing", "key", hexutil.Encode(blsSignatures.PublicKeyToBytes(publicKey))) keyset := &daprovider.DataAvailabilityKeyset{ AssumedHonest: 1, diff --git a/das/syncing_fallback_storage.go b/das/syncing_fallback_storage.go index 43ae6160d7..0670a29c73 100644 --- a/das/syncing_fallback_storage.go +++ b/das/syncing_fallback_storage.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbstate/daprovider" "github.com/offchainlabs/nitro/arbutil" @@ -243,7 +244,7 @@ func FindDASDataFromLog( inboxContract *bridgegen.SequencerInbox, deliveredEvent *bridgegen.SequencerInboxSequencerBatchDelivered, inboxAddr common.Address, - l1Client arbutil.L1Interface, + l1Client *ethclient.Client, batchDeliveredLog types.Log) ([]byte, error) { data := []byte{} if deliveredEvent.DataLocation == uint8(batchDataSeparateEvent) { diff --git a/deploy/deploy.go b/deploy/deploy.go index f2099f976a..bb4b2e6594 100644 --- a/deploy/deploy.go +++ b/deploy/deploy.go @@ -20,57 +20,60 @@ import ( "github.com/offchainlabs/nitro/util/headerreader" ) -func andTxSucceeded(ctx context.Context, l1Reader *headerreader.HeaderReader, tx *types.Transaction, err error) error { +func andTxSucceeded(ctx context.Context, parentChainReader *headerreader.HeaderReader, tx *types.Transaction, err error) error { if err != nil { return fmt.Errorf("error submitting tx: %w", err) } - _, err = l1Reader.WaitForTxApproval(ctx, tx) + _, err = parentChainReader.WaitForTxApproval(ctx, tx) if err != nil { return fmt.Errorf("error executing tx: %w", err) } return nil } -func deployBridgeCreator(ctx context.Context, l1Reader *headerreader.HeaderReader, auth *bind.TransactOpts, maxDataSize *big.Int) (common.Address, error) { - client := l1Reader.Client() +func deployBridgeCreator(ctx context.Context, parentChainReader *headerreader.HeaderReader, auth *bind.TransactOpts, maxDataSize *big.Int, chainSupportsBlobs bool) (common.Address, error) { + client := parentChainReader.Client() /// deploy eth based templates bridgeTemplate, tx, _, err := bridgegen.DeployBridge(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("bridge deploy error: %w", err) } - reader4844, tx, _, err := yulgen.DeployReader4844(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) - if err != nil { - return common.Address{}, fmt.Errorf("blob basefee reader deploy error: %w", err) + var reader4844 common.Address + if chainSupportsBlobs { + reader4844, tx, _, err = yulgen.DeployReader4844(auth, client) + err = andTxSucceeded(ctx, parentChainReader, tx, err) + if err != nil { + return common.Address{}, fmt.Errorf("blob basefee reader deploy error: %w", err) + } } seqInboxTemplateEthBased, tx, _, err := bridgegen.DeploySequencerInbox(auth, client, maxDataSize, reader4844, false) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("sequencer inbox eth based deploy error: %w", err) } seqInboxTemplateERC20Based, tx, _, err := bridgegen.DeploySequencerInbox(auth, client, maxDataSize, reader4844, true) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("sequencer inbox erc20 based deploy error: %w", err) } inboxTemplate, tx, _, err := bridgegen.DeployInbox(auth, client, maxDataSize) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("inbox deploy error: %w", err) } rollupEventBridgeTemplate, tx, _, err := rollupgen.DeployRollupEventInbox(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("rollup event bridge deploy error: %w", err) } outboxTemplate, tx, _, err := bridgegen.DeployOutbox(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("outbox deploy error: %w", err) } @@ -85,25 +88,25 @@ func deployBridgeCreator(ctx context.Context, l1Reader *headerreader.HeaderReade /// deploy ERC20 based templates erc20BridgeTemplate, tx, _, err := bridgegen.DeployERC20Bridge(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("bridge deploy error: %w", err) } erc20InboxTemplate, tx, _, err := bridgegen.DeployERC20Inbox(auth, client, maxDataSize) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("inbox deploy error: %w", err) } erc20RollupEventBridgeTemplate, tx, _, err := rollupgen.DeployERC20RollupEventInbox(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("rollup event bridge deploy error: %w", err) } erc20OutboxTemplate, tx, _, err := bridgegen.DeployERC20Outbox(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("outbox deploy error: %w", err) } @@ -117,7 +120,7 @@ func deployBridgeCreator(ctx context.Context, l1Reader *headerreader.HeaderReade } bridgeCreatorAddr, tx, _, err := rollupgen.DeployBridgeCreator(auth, client, ethBasedTemplates, erc20BasedTemplates) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, fmt.Errorf("bridge creator deploy error: %w", err) } @@ -125,40 +128,40 @@ func deployBridgeCreator(ctx context.Context, l1Reader *headerreader.HeaderReade return bridgeCreatorAddr, nil } -func deployChallengeFactory(ctx context.Context, l1Reader *headerreader.HeaderReader, auth *bind.TransactOpts) (common.Address, common.Address, error) { - client := l1Reader.Client() +func deployChallengeFactory(ctx context.Context, parentChainReader *headerreader.HeaderReader, auth *bind.TransactOpts) (common.Address, common.Address, error) { + client := parentChainReader.Client() osp0, tx, _, err := ospgen.DeployOneStepProver0(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("osp0 deploy error: %w", err) } ospMem, tx, _, err := ospgen.DeployOneStepProverMemory(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("ospMemory deploy error: %w", err) } ospMath, tx, _, err := ospgen.DeployOneStepProverMath(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("ospMath deploy error: %w", err) } ospHostIo, tx, _, err := ospgen.DeployOneStepProverHostIo(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("ospHostIo deploy error: %w", err) } challengeManagerAddr, tx, _, err := challengegen.DeployChallengeManager(auth, client) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("challenge manager deploy error: %w", err) } ospEntryAddr, tx, _, err := ospgen.DeployOneStepProofEntry(auth, client, osp0, ospMem, ospMath, ospHostIo) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("ospEntry deploy error: %w", err) } @@ -166,55 +169,55 @@ func deployChallengeFactory(ctx context.Context, l1Reader *headerreader.HeaderRe return ospEntryAddr, challengeManagerAddr, nil } -func deployRollupCreator(ctx context.Context, l1Reader *headerreader.HeaderReader, auth *bind.TransactOpts, maxDataSize *big.Int) (*rollupgen.RollupCreator, common.Address, common.Address, common.Address, error) { - bridgeCreator, err := deployBridgeCreator(ctx, l1Reader, auth, maxDataSize) +func deployRollupCreator(ctx context.Context, parentChainReader *headerreader.HeaderReader, auth *bind.TransactOpts, maxDataSize *big.Int, chainSupportsBlobs bool) (*rollupgen.RollupCreator, common.Address, common.Address, common.Address, error) { + bridgeCreator, err := deployBridgeCreator(ctx, parentChainReader, auth, maxDataSize, chainSupportsBlobs) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("bridge creator deploy error: %w", err) } - ospEntryAddr, challengeManagerAddr, err := deployChallengeFactory(ctx, l1Reader, auth) + ospEntryAddr, challengeManagerAddr, err := deployChallengeFactory(ctx, parentChainReader, auth) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, err } - rollupAdminLogic, tx, _, err := rollupgen.DeployRollupAdminLogic(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + rollupAdminLogic, tx, _, err := rollupgen.DeployRollupAdminLogic(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("rollup admin logic deploy error: %w", err) } - rollupUserLogic, tx, _, err := rollupgen.DeployRollupUserLogic(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + rollupUserLogic, tx, _, err := rollupgen.DeployRollupUserLogic(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("rollup user logic deploy error: %w", err) } - rollupCreatorAddress, tx, rollupCreator, err := rollupgen.DeployRollupCreator(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + rollupCreatorAddress, tx, rollupCreator, err := rollupgen.DeployRollupCreator(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("rollup creator deploy error: %w", err) } - upgradeExecutor, tx, _, err := upgrade_executorgen.DeployUpgradeExecutor(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + upgradeExecutor, tx, _, err := upgrade_executorgen.DeployUpgradeExecutor(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("upgrade executor deploy error: %w", err) } - validatorUtils, tx, _, err := rollupgen.DeployValidatorUtils(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + validatorUtils, tx, _, err := rollupgen.DeployValidatorUtils(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("validator utils deploy error: %w", err) } - validatorWalletCreator, tx, _, err := rollupgen.DeployValidatorWalletCreator(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + validatorWalletCreator, tx, _, err := rollupgen.DeployValidatorWalletCreator(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("validator wallet creator deploy error: %w", err) } - l2FactoriesDeployHelper, tx, _, err := rollupgen.DeployDeployHelper(auth, l1Reader.Client()) - err = andTxSucceeded(ctx, l1Reader, tx, err) + l2FactoriesDeployHelper, tx, _, err := rollupgen.DeployDeployHelper(auth, parentChainReader.Client()) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("deploy helper creator deploy error: %w", err) } @@ -231,7 +234,7 @@ func deployRollupCreator(ctx context.Context, l1Reader *headerreader.HeaderReade validatorWalletCreator, l2FactoriesDeployHelper, ) - err = andTxSucceeded(ctx, l1Reader, tx, err) + err = andTxSucceeded(ctx, parentChainReader, tx, err) if err != nil { return nil, common.Address{}, common.Address{}, common.Address{}, fmt.Errorf("rollup set template error: %w", err) } @@ -239,12 +242,12 @@ func deployRollupCreator(ctx context.Context, l1Reader *headerreader.HeaderReade return rollupCreator, rollupCreatorAddress, validatorUtils, validatorWalletCreator, nil } -func DeployOnL1(ctx context.Context, parentChainReader *headerreader.HeaderReader, deployAuth *bind.TransactOpts, batchPosters []common.Address, batchPosterManager common.Address, authorizeValidators uint64, config rollupgen.Config, nativeToken common.Address, maxDataSize *big.Int) (*chaininfo.RollupAddresses, error) { +func DeployOnParentChain(ctx context.Context, parentChainReader *headerreader.HeaderReader, deployAuth *bind.TransactOpts, batchPosters []common.Address, batchPosterManager common.Address, authorizeValidators uint64, config rollupgen.Config, nativeToken common.Address, maxDataSize *big.Int, chainSupportsBlobs bool) (*chaininfo.RollupAddresses, error) { if config.WasmModuleRoot == (common.Hash{}) { return nil, errors.New("no machine specified") } - rollupCreator, _, validatorUtils, validatorWalletCreator, err := deployRollupCreator(ctx, parentChainReader, deployAuth, maxDataSize) + rollupCreator, _, validatorUtils, validatorWalletCreator, err := deployRollupCreator(ctx, parentChainReader, deployAuth, maxDataSize, chainSupportsBlobs) if err != nil { return nil, fmt.Errorf("error deploying rollup creator: %w", err) } diff --git a/execution/gethexec/block_recorder.go b/execution/gethexec/block_recorder.go index 8879c90702..a3af7876a8 100644 --- a/execution/gethexec/block_recorder.go +++ b/execution/gethexec/block_recorder.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/arbitrum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -16,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/execution" + flag "github.com/spf13/pflag" ) // BlockRecorder uses a separate statedatabase from the blockchain. @@ -25,6 +27,8 @@ import ( // Most recent/advanced header we ever computed (lastHdr) // Hopefully - some recent valid block. For that we always keep one candidate block until it becomes validated. type BlockRecorder struct { + config *BlockRecorderConfig + recordingDatabase *arbitrum.RecordingDatabase execEngine *ExecutionEngine @@ -39,10 +43,33 @@ type BlockRecorder struct { preparedLock sync.Mutex } -func NewBlockRecorder(config *arbitrum.RecordingDatabaseConfig, execEngine *ExecutionEngine, ethDb ethdb.Database) *BlockRecorder { +type BlockRecorderConfig struct { + TrieDirtyCache int `koanf:"trie-dirty-cache"` + TrieCleanCache int `koanf:"trie-clean-cache"` + MaxPrepared int `koanf:"max-prepared"` +} + +var DefaultBlockRecorderConfig = BlockRecorderConfig{ + TrieDirtyCache: 1024, + TrieCleanCache: 16, + MaxPrepared: 1000, +} + +func BlockRecorderConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Int(prefix+".trie-dirty-cache", DefaultBlockRecorderConfig.TrieDirtyCache, "like trie-dirty-cache for the separate, recording database (used for validation)") + f.Int(prefix+".trie-clean-cache", DefaultBlockRecorderConfig.TrieCleanCache, "like trie-clean-cache for the separate, recording database (used for validation)") + f.Int(prefix+".max-prepared", DefaultBlockRecorderConfig.MaxPrepared, "max references to store in the recording database") +} + +func NewBlockRecorder(config *BlockRecorderConfig, execEngine *ExecutionEngine, ethDb ethdb.Database) *BlockRecorder { + dbConfig := arbitrum.RecordingDatabaseConfig{ + TrieDirtyCache: config.TrieDirtyCache, + TrieCleanCache: config.TrieCleanCache, + } recorder := &BlockRecorder{ + config: config, execEngine: execEngine, - recordingDatabase: arbitrum.NewRecordingDatabase(config, ethDb, execEngine.bc), + recordingDatabase: arbitrum.NewRecordingDatabase(&dbConfig, ethDb, execEngine.bc), } execEngine.SetRecorder(recorder) return recorder @@ -128,6 +155,7 @@ func (r *BlockRecorder) RecordBlockCreation( chaincontext, chainConfig, false, + core.MessageReplayMode, ) if err != nil { return nil, err @@ -303,7 +331,7 @@ func (r *BlockRecorder) PrepareForRecord(ctx context.Context, start, end arbutil r.updateLastHdr(header) hdrNum++ } - r.preparedAddTrim(references, 1000) + r.preparedAddTrim(references, r.config.MaxPrepared) return nil } diff --git a/execution/gethexec/blockchain.go b/execution/gethexec/blockchain.go index 98ce4811d1..fda8f49093 100644 --- a/execution/gethexec/blockchain.go +++ b/execution/gethexec/blockchain.go @@ -26,20 +26,21 @@ import ( ) type CachingConfig struct { - Archive bool `koanf:"archive"` - BlockCount uint64 `koanf:"block-count"` - BlockAge time.Duration `koanf:"block-age"` - TrieTimeLimit time.Duration `koanf:"trie-time-limit"` - TrieDirtyCache int `koanf:"trie-dirty-cache"` - TrieCleanCache int `koanf:"trie-clean-cache"` - SnapshotCache int `koanf:"snapshot-cache"` - DatabaseCache int `koanf:"database-cache"` - SnapshotRestoreGasLimit uint64 `koanf:"snapshot-restore-gas-limit"` - MaxNumberOfBlocksToSkipStateSaving uint32 `koanf:"max-number-of-blocks-to-skip-state-saving"` - MaxAmountOfGasToSkipStateSaving uint64 `koanf:"max-amount-of-gas-to-skip-state-saving"` - StylusLRUCache uint32 `koanf:"stylus-lru-cache"` - StateScheme string `koanf:"state-scheme"` - StateHistory uint64 `koanf:"state-history"` + Archive bool `koanf:"archive"` + BlockCount uint64 `koanf:"block-count"` + BlockAge time.Duration `koanf:"block-age"` + TrieTimeLimit time.Duration `koanf:"trie-time-limit"` + TrieDirtyCache int `koanf:"trie-dirty-cache"` + TrieCleanCache int `koanf:"trie-clean-cache"` + SnapshotCache int `koanf:"snapshot-cache"` + DatabaseCache int `koanf:"database-cache"` + SnapshotRestoreGasLimit uint64 `koanf:"snapshot-restore-gas-limit"` + MaxNumberOfBlocksToSkipStateSaving uint32 `koanf:"max-number-of-blocks-to-skip-state-saving"` + MaxAmountOfGasToSkipStateSaving uint64 `koanf:"max-amount-of-gas-to-skip-state-saving"` + StylusLRUCacheCapacity uint32 `koanf:"stylus-lru-cache-capacity"` + DisableStylusCacheMetricsCollection bool `koanf:"disable-stylus-cache-metrics-collection"` + StateScheme string `koanf:"state-scheme"` + StateHistory uint64 `koanf:"state-history"` } func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -54,7 +55,8 @@ func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { f.Uint64(prefix+".snapshot-restore-gas-limit", DefaultCachingConfig.SnapshotRestoreGasLimit, "maximum gas rolled back to recover snapshot") f.Uint32(prefix+".max-number-of-blocks-to-skip-state-saving", DefaultCachingConfig.MaxNumberOfBlocksToSkipStateSaving, "maximum number of blocks to skip state saving to persistent storage (archive node only) -- warning: this option seems to cause issues") f.Uint64(prefix+".max-amount-of-gas-to-skip-state-saving", DefaultCachingConfig.MaxAmountOfGasToSkipStateSaving, "maximum amount of gas in blocks to skip saving state to Persistent storage (archive node only) -- warning: this option seems to cause issues") - f.Uint32(prefix+".stylus-lru-cache", DefaultCachingConfig.StylusLRUCache, "initialized stylus programs to keep in LRU cache") + f.Uint32(prefix+".stylus-lru-cache-capacity", DefaultCachingConfig.StylusLRUCacheCapacity, "capacity, in megabytes, of the LRU cache that keeps initialized stylus programs") + f.Bool(prefix+".disable-stylus-cache-metrics-collection", DefaultCachingConfig.DisableStylusCacheMetricsCollection, "disable metrics collection for the stylus cache") f.String(prefix+".state-scheme", DefaultCachingConfig.StateScheme, "scheme to use for state trie storage (hash, path)") f.Uint64(prefix+".state-history", DefaultCachingConfig.StateHistory, "number of recent blocks to retain state history for (path state-scheme only)") } @@ -76,7 +78,7 @@ var DefaultCachingConfig = CachingConfig{ SnapshotRestoreGasLimit: 300_000_000_000, MaxNumberOfBlocksToSkipStateSaving: 0, MaxAmountOfGasToSkipStateSaving: 0, - StylusLRUCache: 256, + StylusLRUCacheCapacity: 256, StateScheme: rawdb.HashScheme, StateHistory: getStateHistory(DefaultSequencerConfig.MaxBlockSpeed), } diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index 8594d5867d..6571672b71 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -7,7 +7,7 @@ package gethexec /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm #include "arbitrator.h" */ @@ -87,6 +87,8 @@ type ExecutionEngine struct { reorgSequencing bool + disableStylusCacheMetricsCollection bool + prefetchBlock bool cachedL1PriceData *L1PriceData @@ -150,7 +152,7 @@ func (s *ExecutionEngine) MarkFeedStart(to arbutil.MessageIndex) { } } -func populateStylusTargetCache(targetConfig *StylusTargetConfig) error { +func PopulateStylusTargetCache(targetConfig *StylusTargetConfig) error { localTarget := rawdb.LocalTarget() targets := targetConfig.WasmTargets() var nativeSet bool @@ -182,11 +184,11 @@ func populateStylusTargetCache(targetConfig *StylusTargetConfig) error { return nil } -func (s *ExecutionEngine) Initialize(rustCacheSize uint32, targetConfig *StylusTargetConfig) error { - if rustCacheSize != 0 { - programs.ResizeWasmLruCache(rustCacheSize) +func (s *ExecutionEngine) Initialize(rustCacheCapacityMB uint32, targetConfig *StylusTargetConfig) error { + if rustCacheCapacityMB != 0 { + programs.SetWasmLruCacheCapacity(arbmath.SaturatingUMul(uint64(rustCacheCapacityMB), 1024*1024)) } - if err := populateStylusTargetCache(targetConfig); err != nil { + if err := PopulateStylusTargetCache(targetConfig); err != nil { return fmt.Errorf("error populating stylus target cache: %w", err) } return nil @@ -212,6 +214,16 @@ func (s *ExecutionEngine) EnableReorgSequencing() { s.reorgSequencing = true } +func (s *ExecutionEngine) DisableStylusCacheMetricsCollection() { + if s.Started() { + panic("trying to disable stylus cache metrics collection after start") + } + if s.disableStylusCacheMetricsCollection { + panic("trying to disable stylus cache metrics collection when already set") + } + s.disableStylusCacheMetricsCollection = true +} + func (s *ExecutionEngine) EnablePrefetchBlock() { if s.Started() { panic("trying to enable prefetch block after start") @@ -505,6 +517,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. s.bc.Config(), hooks, false, + core.MessageCommitMode, ) if err != nil { return nil, err @@ -661,6 +674,10 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith statedb.StartPrefetcher("TransactionStreamer") defer statedb.StopPrefetcher() + runMode := core.MessageCommitMode + if isMsgForPrefetch { + runMode = core.MessageReplayMode + } block, receipts, err := arbos.ProduceBlock( msg.Message, msg.DelayedMessagesRead, @@ -669,6 +686,7 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith s.bc, s.bc.Config(), isMsgForPrefetch, + runMode, ) return block, statedb, receipts, err @@ -963,4 +981,17 @@ func (s *ExecutionEngine) Start(ctx_in context.Context) { } } }) + if !s.disableStylusCacheMetricsCollection { + // periodically update stylus cache metrics + s.LaunchThread(func(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Minute): + programs.UpdateWasmCacheMetrics() + } + } + }) + } } diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 355ed7a647..499a13164e 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -84,19 +85,19 @@ func StylusTargetConfigAddOptions(prefix string, f *flag.FlagSet) { } type Config struct { - ParentChainReader headerreader.Config `koanf:"parent-chain-reader" reload:"hot"` - Sequencer SequencerConfig `koanf:"sequencer" reload:"hot"` - RecordingDatabase arbitrum.RecordingDatabaseConfig `koanf:"recording-database"` - TxPreChecker TxPreCheckerConfig `koanf:"tx-pre-checker" reload:"hot"` - Forwarder ForwarderConfig `koanf:"forwarder"` - ForwardingTarget string `koanf:"forwarding-target"` - SecondaryForwardingTarget []string `koanf:"secondary-forwarding-target"` - Caching CachingConfig `koanf:"caching"` - RPC arbitrum.Config `koanf:"rpc"` - TxLookupLimit uint64 `koanf:"tx-lookup-limit"` - EnablePrefetchBlock bool `koanf:"enable-prefetch-block"` - SyncMonitor SyncMonitorConfig `koanf:"sync-monitor"` - StylusTarget StylusTargetConfig `koanf:"stylus-target"` + ParentChainReader headerreader.Config `koanf:"parent-chain-reader" reload:"hot"` + Sequencer SequencerConfig `koanf:"sequencer" reload:"hot"` + RecordingDatabase BlockRecorderConfig `koanf:"recording-database"` + TxPreChecker TxPreCheckerConfig `koanf:"tx-pre-checker" reload:"hot"` + Forwarder ForwarderConfig `koanf:"forwarder"` + ForwardingTarget string `koanf:"forwarding-target"` + SecondaryForwardingTarget []string `koanf:"secondary-forwarding-target"` + Caching CachingConfig `koanf:"caching"` + RPC arbitrum.Config `koanf:"rpc"` + TxLookupLimit uint64 `koanf:"tx-lookup-limit"` + EnablePrefetchBlock bool `koanf:"enable-prefetch-block"` + SyncMonitor SyncMonitorConfig `koanf:"sync-monitor"` + StylusTarget StylusTargetConfig `koanf:"stylus-target"` forwardingTarget string } @@ -129,7 +130,7 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { arbitrum.ConfigAddOptions(prefix+".rpc", f) SequencerConfigAddOptions(prefix+".sequencer", f) headerreader.AddOptions(prefix+".parent-chain-reader", f) - arbitrum.RecordingDatabaseConfigAddOptions(prefix+".recording-database", f) + BlockRecorderConfigAddOptions(prefix+".recording-database", f) f.String(prefix+".forwarding-target", ConfigDefault.ForwardingTarget, "transaction forwarding target URL, or \"null\" to disable forwarding (iff not sequencer)") f.StringSlice(prefix+".secondary-forwarding-target", ConfigDefault.SecondaryForwardingTarget, "secondary transaction forwarding target URL") AddOptionsForNodeForwarderConfig(prefix+".forwarder", f) @@ -145,7 +146,7 @@ var ConfigDefault = Config{ RPC: arbitrum.DefaultConfig, Sequencer: DefaultSequencerConfig, ParentChainReader: headerreader.DefaultConfig, - RecordingDatabase: arbitrum.DefaultRecordingDatabaseConfig, + RecordingDatabase: DefaultBlockRecorderConfig, ForwardingTarget: "", SecondaryForwardingTarget: []string{}, TxPreChecker: DefaultTxPreCheckerConfig, @@ -179,7 +180,7 @@ func CreateExecutionNode( stack *node.Node, chainDB ethdb.Database, l2BlockChain *core.BlockChain, - l1client arbutil.L1Interface, + l1client *ethclient.Client, configFetcher ConfigFetcher, ) (*ExecutionNode, error) { config := configFetcher() @@ -187,6 +188,9 @@ func CreateExecutionNode( if config.EnablePrefetchBlock { execEngine.EnablePrefetchBlock() } + if config.Caching.DisableStylusCacheMetricsCollection { + execEngine.DisableStylusCacheMetricsCollection() + } if err != nil { return nil, err } @@ -314,7 +318,7 @@ func (n *ExecutionNode) MarkFeedStart(to arbutil.MessageIndex) { func (n *ExecutionNode) Initialize(ctx context.Context) error { config := n.ConfigFetcher() - err := n.ExecEngine.Initialize(config.Caching.StylusLRUCache, &config.StylusTarget) + err := n.ExecEngine.Initialize(config.Caching.StylusLRUCacheCapacity, &config.StylusTarget) if err != nil { return fmt.Errorf("error initializing execution engine: %w", err) } diff --git a/execution/gethexec/wasmstorerebuilder.go b/execution/gethexec/wasmstorerebuilder.go index 698ba3ec8a..e3eb8e9268 100644 --- a/execution/gethexec/wasmstorerebuilder.go +++ b/execution/gethexec/wasmstorerebuilder.go @@ -63,7 +63,7 @@ func RebuildWasmStore(ctx context.Context, wasmStore ethdb.KeyValueStore, chainD var err error var stateDb *state.StateDB - if err := populateStylusTargetCache(targetConfig); err != nil { + if err := PopulateStylusTargetCache(targetConfig); err != nil { return fmt.Errorf("error populating stylus target cache: %w", err) } diff --git a/go-ethereum b/go-ethereum index ad31b75098..b1075d3786 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit ad31b75098f874c7124e7b29b47db8662d47b9ff +Subproject commit b1075d3786b28a6a3a06fe0e0ab8d1cdecc72f55 diff --git a/go.mod b/go.mod index da49b0d8b9..488d455f44 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,22 @@ module github.com/offchainlabs/nitro -go 1.21 +go 1.23 replace github.com/VictoriaMetrics/fastcache => ./fastcache replace github.com/ethereum/go-ethereum => ./go-ethereum require ( + cloud.google.com/go/storage v1.43.0 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible github.com/Shopify/toxiproxy v2.1.4+incompatible github.com/alicebob/miniredis/v2 v2.32.1 github.com/andybalholm/brotli v1.0.4 - github.com/aws/aws-sdk-go-v2 v1.21.2 - github.com/aws/aws-sdk-go-v2/config v1.18.45 - github.com/aws/aws-sdk-go-v2/credentials v1.13.43 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10 - github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 + github.com/aws/aws-sdk-go-v2 v1.31.0 + github.com/aws/aws-sdk-go-v2/config v1.27.40 + github.com/aws/aws-sdk-go-v2/credentials v1.17.38 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.27 + github.com/aws/aws-sdk-go-v2/service/s3 v1.64.1 github.com/cavaliergopher/grab/v3 v3.0.1 github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 github.com/codeclysm/extract/v3 v3.0.2 @@ -24,13 +25,12 @@ require ( github.com/ethereum/go-ethereum v1.10.26 github.com/fatih/structtag v1.2.0 github.com/gdamore/tcell/v2 v2.7.1 - github.com/go-redis/redis/v8 v8.11.5 github.com/gobwas/httphead v0.1.0 github.com/gobwas/ws v1.2.1 github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 github.com/google/btree v1.1.2 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/holiman/uint256 v1.2.4 github.com/knadh/koanf v1.4.0 @@ -38,19 +38,43 @@ require ( github.com/mitchellh/mapstructure v1.4.1 github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v3 v3.0.1 + github.com/redis/go-redis/v9 v9.6.1 github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 github.com/spf13/pflag v1.0.5 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/wealdtech/go-merkletree v1.0.0 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/sys v0.18.0 - golang.org/x/term v0.18.0 - golang.org/x/tools v0.16.0 + golang.org/x/sys v0.21.0 + golang.org/x/term v0.21.0 + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d + google.golang.org/api v0.187.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 ) -require github.com/google/go-querystring v1.1.0 // indirect +require ( + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.6.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.8 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.5 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/grpc v1.64.0 // indirect +) require ( github.com/DataDog/zstd v1.4.5 // indirect @@ -58,24 +82,24 @@ require ( github.com/StackExchange/wmi v1.2.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect - github.com/aws/smithy-go v1.15.0 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.23.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.31.4 // indirect + github.com/aws/smithy-go v1.22.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect github.com/cockroachdb/redact v1.1.3 // indirect @@ -94,7 +118,6 @@ require ( github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/fjl/memsize v0.0.2 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gammazero/deque v0.2.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect @@ -107,9 +130,9 @@ require ( github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/glog v1.0.0 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/glog v1.2.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/flatbuffers v1.12.1 // indirect github.com/google/go-github/v62 v62.0.0 @@ -122,7 +145,6 @@ require ( github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect github.com/klauspost/compress v1.17.2 // indirect @@ -159,13 +181,13 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect - go.opencensus.io v0.22.5 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.23.0 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.22.0 - golang.org/x/sync v0.5.0 - golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/sync v0.7.0 + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index c0193be769..d11610724e 100644 --- a/go.sum +++ b/go.sum @@ -13,14 +13,26 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38= +cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -30,6 +42,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -70,64 +84,53 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= -github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= -github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= +github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= +github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5/go.mod h1:wYSv6iDS621sEFLfKvpPE2ugjTuGlAG7iROg0hLOkfc= github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= -github.com/aws/aws-sdk-go-v2/config v1.15.5/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= -github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes= -github.com/aws/aws-sdk-go-v2/config v1.18.45/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE= +github.com/aws/aws-sdk-go-v2/config v1.27.40 h1:sie4mPBGFOO+Z27+yHzvyN31G20h/bf2xb5mCbpLv2Q= +github.com/aws/aws-sdk-go-v2/config v1.27.40/go.mod h1:4KW7Aa5tNo+0VHnuLnnE1vPHtwMurlNZNS65IdcewHA= github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= -github.com/aws/aws-sdk-go-v2/credentials v1.12.0/go.mod h1:9YWk7VW+eyKsoIL6/CljkTrNVWBSK9pkqOPUuijid4A= -github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8= -github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.38 h1:iM90eRhCeZtlkzCNCG1JysOzJXGYf5rx80aD1lUgNDU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.38/go.mod h1:TCVYPZeQuLaYNEkf/TVn6k5k/zdVZZ7xH9po548VNNg= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10 h1:JL7cY85hyjlgfA29MMyAlItX+JYIH9XsxgMBS7jtlqA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.10/go.mod h1:p+ul5bLZSDRRXCZ/vePvfmZBH9akozXBJA5oMshWa5U= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.27 h1:1oLpQSTuqbizOUEYdxAwH+Eveg+FOCOkg84Yijba6Kc= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.27/go.mod h1:afo0vF9P3pjy1ny+cb45lzBjtKeEb5t5MPRxeTXpujw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1 h1:C21IDZCm9Yu5xqjb3fKmxDoYvJXtw1DNlOmLZEIlY1M= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.1/go.mod h1:l/BbcfqDCT3hePawhy4ZRtewjtdkl6GWtd9/U+1penQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 h1:OWYvKL53l1rbsUmW7bQyJVsYU/Ii3bbAAQIIFNbM0Tk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18/go.mod h1:CUx0G1v3wG6l01tUB+j7Y8kclA8NSqK4ef0YG79a4cg= github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5 h1:9LSZqt4v1JiehyZTrQnRFf2mY/awmyYNNY/b7zqtduU= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.5/go.mod h1:S8TVP66AAkMMdYYCNZGvrdEq9YRm+qLXjio4FqRnrEE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 h1:rTWjG6AvWekO2B1LHeM3ktU7MqyX9rzWQ7hgzneZW7E= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20/go.mod h1:RGW2DDpVc8hu6Y6yG8G5CHVmVOAn1oV8rNKOHRJyswg= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4 h1:RE/DlZLYrz1OOmq8F28IXHLksuuvlpzUbvJ+SESCZBI= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.4/go.mod h1:oudbsSdDtazNj47z1ut1n37re9hDsKpk2ZI3v7KSxq0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9 h1:LCQKnopq2t4oQS3VKivlYTzAHCTJZZoQICM9fny7KHY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.9/go.mod h1:iMYipLPXlWpBJ0KFX7QJHZ84rBydHBY8as2aQICTPWk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 h1:eb+tFOIl9ZsUe2259/BKPeniKuz4/02zZFH/i4Nf8Rg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18/go.mod h1:GVCC2IJNJTmdlyEsSmofEy7EfJncP7DNnXDzRjJ5Keg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.64.1 h1:jjHf+M6vCp/WzbyFEroY4/Nx8dJac520A0EPwlYk0Do= +github.com/aws/aws-sdk-go-v2/service/s3 v1.64.1/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q= github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.4/go.mod h1:cPDwJwsP4Kff9mldCXAmddjJL6JGQqtA3Mzer2zyr88= -github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k= -github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.4 h1:ck/Y8XWNR1gHa4BFkwE3oSu7XDJGwl+8TI7E/RB2EcQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.4/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.4 h1:4f2/JKYZHAZbQ7koBpZ012bKi32NHPY0m7TDuJgsbug= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.4/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsViglqlG9eEFYxNryTZS5rn3QE= -github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU= -github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.4 h1:uK6dUUdJtqutK1XO/tmNaQMJiPLCJY/eAeOOmqQ6ygY= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.4/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= -github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8= -github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -136,6 +139,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -147,8 +154,8 @@ github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -233,8 +240,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= -github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -269,12 +276,15 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= -github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -303,12 +313,13 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -332,8 +343,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= @@ -353,11 +364,10 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= @@ -368,8 +378,11 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -381,12 +394,18 @@ github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8q github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -443,9 +462,7 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -567,22 +584,19 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -628,6 +642,8 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rhnvrm/simples3 v0.6.1 h1:H0DJwybR6ryQE+Odi9eqkHuzjYAeJgtGcGtuBwOhsH8= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 h1:bWLHTRekAy497pE7+nXSuzXwwFHI0XauRzz6roUvY+s= @@ -670,14 +686,19 @@ github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobt github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -729,8 +750,20 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -743,8 +776,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -781,8 +814,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -819,6 +852,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -827,8 +861,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -851,8 +885,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -928,14 +962,14 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -947,14 +981,15 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1003,11 +1038,12 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= -golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -1025,6 +1061,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo= +google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1063,6 +1101,12 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1078,7 +1122,10 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1091,8 +1138,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/nitro-testnode b/nitro-testnode index f328006579..72141dd495 160000 --- a/nitro-testnode +++ b/nitro-testnode @@ -1 +1 @@ -Subproject commit f328006579cbefe22c6c57de3d6b86397fde4438 +Subproject commit 72141dd495ad965aa2a23723ea3e755037903ad7 diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go index 9f42cacb5a..bc24c8a6e8 100644 --- a/precompiles/ArbWasm.go +++ b/precompiles/ArbWasm.go @@ -5,6 +5,8 @@ package precompiles import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + gethparams "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/util/arbmath" @@ -32,12 +34,13 @@ func (con ArbWasm) ActivateProgram(c ctx, evm mech, value huge, program addr) (u debug := evm.ChainConfig().DebugMode() runMode := c.txProcessor.RunMode() programs := c.State.Programs() + arbosVersion := c.State.ArbOSVersion() // charge a fixed cost up front to begin activation if err := c.Burn(1659168); err != nil { return 0, nil, err } - version, codeHash, moduleHash, dataFee, takeAllGas, err := programs.ActivateProgram(evm, program, runMode, debug) + version, codeHash, moduleHash, dataFee, takeAllGas, err := programs.ActivateProgram(evm, program, arbosVersion, runMode, debug) if takeAllGas { _ = c.BurnOut() } @@ -133,6 +136,9 @@ func (con ArbWasm) MinInitGas(c ctx, _ mech) (uint64, uint64, error) { params, err := c.State.Programs().Params() init := uint64(params.MinInitGas) * programs.MinInitGasUnits cached := uint64(params.MinCachedInitGas) * programs.MinCachedGasUnits + if c.State.ArbOSVersion() < gethparams.ArbosVersion_StylusChargingFixes { + return 0, 0, vm.ErrExecutionReverted + } return init, cached, err } diff --git a/pubsub/common.go b/pubsub/common.go index d7f041af15..ad36b6e622 100644 --- a/pubsub/common.go +++ b/pubsub/common.go @@ -2,12 +2,15 @@ package pubsub import ( "context" + "fmt" "strings" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" ) +func ResultKeyFor(streamName, id string) string { return fmt.Sprintf("%s.%s", streamName, id) } + // CreateStream tries to create stream with given name, if it already exists // does not return an error. func CreateStream(ctx context.Context, streamName string, client redis.UniversalClient) error { diff --git a/pubsub/consumer.go b/pubsub/consumer.go index df3695606d..391042bd7e 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -5,36 +5,38 @@ import ( "encoding/json" "errors" "fmt" + "math" + "math/rand" + "strconv" "time" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/google/uuid" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/redis/go-redis/v9" "github.com/spf13/pflag" ) type ConsumerConfig struct { // Timeout of result entry in Redis. ResponseEntryTimeout time.Duration `koanf:"response-entry-timeout"` - // Duration after which consumer is considered to be dead if heartbeat - // is not updated. - KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` + // Minimum idle time after which messages will be autoclaimed + IdletimeToAutoclaim time.Duration `koanf:"idletime-to-autoclaim"` } var DefaultConsumerConfig = ConsumerConfig{ ResponseEntryTimeout: time.Hour, - KeepAliveTimeout: 5 * time.Minute, + IdletimeToAutoclaim: 5 * time.Minute, } var TestConsumerConfig = ConsumerConfig{ ResponseEntryTimeout: time.Minute, - KeepAliveTimeout: 30 * time.Millisecond, + IdletimeToAutoclaim: 30 * time.Millisecond, } func ConsumerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Duration(prefix+".response-entry-timeout", DefaultConsumerConfig.ResponseEntryTimeout, "timeout for response entry") - f.Duration(prefix+".keepalive-timeout", DefaultConsumerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") + f.Duration(prefix+".idletime-to-autoclaim", DefaultConsumerConfig.IdletimeToAutoclaim, "After a message spends this amount of time in PEL (Pending Entries List i.e claimed by another consumer but not Acknowledged) it will be allowed to be autoclaimed by other consumers") } // Consumer implements a consumer for redis stream provides heartbeat to @@ -51,6 +53,7 @@ type Consumer[Request any, Response any] struct { type Message[Request any] struct { ID string Value Request + Ack func() } func NewConsumer[Request any, Response any](client redis.UniversalClient, streamName string, cfg *ConsumerConfig) (*Consumer[Request, Response], error) { @@ -69,21 +72,14 @@ func NewConsumer[Request any, Response any](client redis.UniversalClient, stream // Start starts the consumer to iteratively perform heartbeat in configured intervals. func (c *Consumer[Request, Response]) Start(ctx context.Context) { c.StopWaiter.Start(ctx, c) - c.StopWaiter.CallIteratively( - func(ctx context.Context) time.Duration { - c.heartBeat(ctx) - return c.cfg.KeepAliveTimeout / 10 - }, - ) } -func (c *Consumer[Request, Response]) StopAndWait() { - c.StopWaiter.StopAndWait() - c.deleteHeartBeat(c.GetParentContext()) +func (c *Consumer[Request, Response]) Id() string { + return c.id } -func heartBeatKey(id string) string { - return fmt.Sprintf("consumer:%s:heartbeat", id) +func (c *Consumer[Request, Response]) StopAndWait() { + c.StopWaiter.StopAndWait() } func (c *Consumer[Request, Response]) RedisClient() redis.UniversalClient { @@ -94,68 +90,124 @@ func (c *Consumer[Request, Response]) StreamName() string { return c.redisStream } -func (c *Consumer[Request, Response]) heartBeatKey() string { - return heartBeatKey(c.id) -} - -// deleteHeartBeat deletes the heartbeat to indicate it is being shut down. -func (c *Consumer[Request, Response]) deleteHeartBeat(ctx context.Context) { - if err := c.client.Del(ctx, c.heartBeatKey()).Err(); err != nil { - l := log.Info - if ctx.Err() != nil { - l = log.Error - } - l("Deleting heardbeat", "consumer", c.id, "error", err) +func decrementMsgIdByOne(msgId string) string { + id, err := getUintParts(msgId) + if err != nil { + log.Error("Error decrementing start of XAutoClaim by one, defaulting to 0", "err", err) + return "0" } -} - -// heartBeat updates the heartBeat key indicating aliveness. -func (c *Consumer[Request, Response]) heartBeat(ctx context.Context) { - if err := c.client.Set(ctx, c.heartBeatKey(), time.Now().UnixMilli(), 2*c.cfg.KeepAliveTimeout).Err(); err != nil { - l := log.Info - if ctx.Err() != nil { - l = log.Error - } - l("Updating heardbeat", "consumer", c.id, "error", err) + if id[1] > 0 { + return strconv.FormatUint(id[0], 10) + "-" + strconv.FormatUint(id[1]-1, 10) + } else if id[0] > 0 { + return strconv.FormatUint(id[0]-1, 10) + "-" + strconv.FormatUint(math.MaxUint64, 10) } + return "0" } // Consumer first checks it there exists pending message that is claimed by // unresponsive consumer, if not then reads from the stream. func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Request], error) { - res, err := c.client.XReadGroup(ctx, &redis.XReadGroupArgs{ - Group: c.redisGroup, - Consumer: c.id, - // Receive only messages that were never delivered to any other consumer, - // that is, only new messages. - Streams: []string{c.redisStream, ">"}, - Count: 1, - Block: time.Millisecond, // 0 seems to block the read instead of immediately returning - }).Result() - if errors.Is(err, redis.Nil) { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("reading message for consumer: %q: %w", c.id, err) + // First try to XAUTOCLAIM, with start as a random messageID from PEL with MinIdle as IdletimeToAutoclaim + // this prioritizes processing PEL messages that have been waiting for more than IdletimeToAutoclaim duration + var messages []redis.XMessage + if pendingMsgs, err := c.client.XPendingExt(ctx, &redis.XPendingExtArgs{ + Stream: c.redisStream, + Group: c.redisGroup, + Start: "-", + End: "+", + Count: 50, + Idle: c.cfg.IdletimeToAutoclaim, + }).Result(); err != nil { + if !errors.Is(err, redis.Nil) { + log.Error("Error from XpendingExt in getting PEL for auto claim", "err", err, "penindlen", len(pendingMsgs)) + } + } else if len(pendingMsgs) > 0 { + idx := rand.Intn(len(pendingMsgs)) + messages, _, err = c.client.XAutoClaim(ctx, &redis.XAutoClaimArgs{ + Group: c.redisGroup, + Consumer: c.id, + MinIdle: c.cfg.IdletimeToAutoclaim, // Minimum idle time for messages to claim (in milliseconds) + Stream: c.redisStream, + Start: decrementMsgIdByOne(pendingMsgs[idx].ID), + Count: 1, + }).Result() + if err != nil { + log.Info("error from xautoclaim", "err", err) + } } - if len(res) != 1 || len(res[0].Messages) != 1 { - return nil, fmt.Errorf("redis returned entries: %+v, for querying single message", res) + if len(messages) == 0 { + // If we fail to autoclaim then we do not retry but instead fallback to reading new messages + res, err := c.client.XReadGroup(ctx, &redis.XReadGroupArgs{ + Group: c.redisGroup, + Consumer: c.id, + // Receive only messages that were never delivered to any other consumer, + // that is, only new messages. + Streams: []string{c.redisStream, ">"}, + Count: 1, + Block: time.Millisecond, // 0 seems to block the read instead of immediately returning + }).Result() + if errors.Is(err, redis.Nil) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("reading message for consumer: %q: %w", c.id, err) + } + if len(res) != 1 || len(res[0].Messages) != 1 { + return nil, fmt.Errorf("redis returned entries: %+v, for querying single message", res) + } + messages = res[0].Messages } + var ( - value = res[0].Messages[0].Values[messageKey] + value = messages[0].Values[messageKey] data, ok = (value).(string) ) if !ok { - return nil, fmt.Errorf("casting request to string: %w", err) + return nil, errors.New("error casting request to string") } var req Request if err := json.Unmarshal([]byte(data), &req); err != nil { return nil, fmt.Errorf("unmarshaling value: %v, error: %w", value, err) } - log.Debug("Redis stream consuming", "consumer_id", c.id, "message_id", res[0].Messages[0].ID) + ackNotifier := make(chan struct{}) + c.StopWaiter.LaunchThread(func(ctx context.Context) { + for { + // Use XClaimJustID so that we would have clear difference between invalid requests that are claimed multiple times due to xautoclaim and + // valid requests that are just being claimed in regular intervals to indicate heartbeat + if ids, err := c.client.XClaimJustID(ctx, &redis.XClaimArgs{ + Stream: c.redisStream, + Group: c.redisGroup, + Consumer: c.id, + MinIdle: 0, + Messages: []string{messages[0].ID}, + }).Result(); err != nil { + log.Error("Error claiming message, it might be possible that other consumers might pick this request", "msgID", messages[0].ID) + } else if len(ids) == 0 { + log.Warn("XClaimJustID returned empty response when indicating hearbeat", "msgID", messages[0].ID) + } else if len(ids) > 1 { + log.Error("XClaimJustID returned response with more than entry", "msgIDs", ids) + } + select { + case <-ackNotifier: + return + case <-ctx.Done(): + log.Info("Context done while claiming message to indicate hearbeat", "messageID", messages[0].ID, "error", ctx.Err().Error()) + if c.StopWaiter.GetParentContext().Err() == nil { + // Proceeding to set the Idle time of message to IdletimeToAutoclaim to allow it to be picked by other consumers + if err := c.client.Do(c.StopWaiter.GetParentContext(), "XCLAIM", c.redisStream, c.redisGroup, c.id, 0, messages[0].ID, "IDLE", c.cfg.IdletimeToAutoclaim.Milliseconds()).Err(); err != nil { + log.Error("error when trying to set the idle time of currently worked on message to IdletimeToAutoclaim", "messageID", messages[0].ID, "err", err) + } + } + return + case <-time.After(c.cfg.IdletimeToAutoclaim / 10): + } + } + }) + log.Debug("Redis stream consuming", "consumer_id", c.id, "message_id", messages[0].ID) return &Message[Request]{ - ID: res[0].Messages[0].ID, + ID: messages[0].ID, Value: req, + Ack: func() { close(ackNotifier) }, }, nil } @@ -164,12 +216,18 @@ func (c *Consumer[Request, Response]) SetResult(ctx context.Context, messageID s if err != nil { return fmt.Errorf("marshaling result: %w", err) } - acquired, err := c.client.SetNX(ctx, messageID, resp, c.cfg.ResponseEntryTimeout).Result() + resultKey := ResultKeyFor(c.StreamName(), messageID) + log.Debug("consumer: setting result", "cid", c.id, "msgIdInStream", messageID, "resultKeyInRedis", resultKey) + acquired, err := c.client.SetNX(ctx, resultKey, resp, c.cfg.ResponseEntryTimeout).Result() if err != nil || !acquired { - return fmt.Errorf("setting result for message: %v, error: %w", messageID, err) + return fmt.Errorf("setting result for message with message-id in stream: %v, error: %w", messageID, err) } + log.Debug("consumer: xack", "cid", c.id, "messageId", messageID) if _, err := c.client.XAck(ctx, c.redisStream, c.redisGroup, messageID).Result(); err != nil { return fmt.Errorf("acking message: %v, error: %w", messageID, err) } + if _, err := c.client.XDel(ctx, c.redisStream, messageID).Result(); err != nil { + return fmt.Errorf("deleting message: %v, error: %w", messageID, err) + } return nil } diff --git a/pubsub/producer.go b/pubsub/producer.go index 2b1cdb5e3f..dacaeba7d0 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -13,17 +13,16 @@ import ( "encoding/json" "errors" "fmt" - "math" "strconv" "strings" "sync" "time" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/google/uuid" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/stopwaiter" + "github.com/redis/go-redis/v9" "github.com/spf13/pflag" ) @@ -43,50 +42,31 @@ type Producer[Request any, Response any] struct { promisesLock sync.RWMutex promises map[string]*containers.Promise[Response] - // Used for running checks for pending messages with inactive consumers - // and checking responses from consumers iteratively for the first time when - // Produce is called. + // Used for checking responses from consumers iteratively + // For the first time when Produce is called. once sync.Once } type ProducerConfig struct { - // When enabled, messages that are sent to consumers that later die before - // processing them, will be re-inserted into the stream to be proceesed by - // another consumer - EnableReproduce bool `koanf:"enable-reproduce"` - // Interval duration in which producer checks for pending messages delivered - // to the consumers that are currently inactive. - CheckPendingInterval time.Duration `koanf:"check-pending-interval"` - // Duration after which consumer is considered to be dead if heartbeat - // is not updated. - KeepAliveTimeout time.Duration `koanf:"keepalive-timeout"` // Interval duration for checking the result set by consumers. CheckResultInterval time.Duration `koanf:"check-result-interval"` - CheckPendingItems int64 `koanf:"check-pending-items"` + // RequestTimeout is a TTL for any message sent to the redis stream + RequestTimeout time.Duration `koanf:"request-timeout"` } var DefaultProducerConfig = ProducerConfig{ - EnableReproduce: true, - CheckPendingInterval: time.Second, - KeepAliveTimeout: 5 * time.Minute, - CheckResultInterval: 5 * time.Second, - CheckPendingItems: 256, + CheckResultInterval: 5 * time.Second, + RequestTimeout: 3 * time.Hour, } var TestProducerConfig = ProducerConfig{ - EnableReproduce: false, - CheckPendingInterval: 10 * time.Millisecond, - KeepAliveTimeout: 100 * time.Millisecond, - CheckResultInterval: 5 * time.Millisecond, - CheckPendingItems: 256, + CheckResultInterval: 5 * time.Millisecond, + RequestTimeout: time.Minute, } func ProducerAddConfigAddOptions(prefix string, f *pflag.FlagSet) { - f.Bool(prefix+".enable-reproduce", DefaultProducerConfig.EnableReproduce, "when enabled, messages with dead consumer will be re-inserted into the stream") - f.Duration(prefix+".check-pending-interval", DefaultProducerConfig.CheckPendingInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") f.Duration(prefix+".check-result-interval", DefaultProducerConfig.CheckResultInterval, "interval in which producer checks pending messages whether consumer processing them is inactive") - f.Duration(prefix+".keepalive-timeout", DefaultProducerConfig.KeepAliveTimeout, "timeout after which consumer is considered inactive if heartbeat wasn't performed") - f.Int64(prefix+".check-pending-items", DefaultProducerConfig.CheckPendingItems, "items to screen during check-pending") + f.Duration(prefix+".request-timeout", DefaultProducerConfig.RequestTimeout, "timeout after which the message in redis stream is considered as errored, this prevents workers from working on wrong requests indefinitely") } func NewProducer[Request any, Response any](client redis.UniversalClient, streamName string, cfg *ProducerConfig) (*Producer[Request, Response], error) { @@ -106,149 +86,128 @@ func NewProducer[Request any, Response any](client redis.UniversalClient, stream }, nil } -func (p *Producer[Request, Response]) errorPromisesFor(msgIds []string) { - p.promisesLock.Lock() - defer p.promisesLock.Unlock() - for _, msg := range msgIds { - if promise, found := p.promises[msg]; found { - promise.ProduceError(fmt.Errorf("internal error, consumer died while serving the request")) - delete(p.promises, msg) - } +func getUintParts(msgId string) ([2]uint64, error) { + idParts := strings.Split(msgId, "-") + if len(idParts) != 2 { + return [2]uint64{}, fmt.Errorf("invalid i.d: %v", msgId) } -} - -// checkAndReproduce reproduce pending messages that were sent to consumers -// that are currently inactive. -func (p *Producer[Request, Response]) checkAndReproduce(ctx context.Context) time.Duration { - staleIds, err := p.checkPending(ctx) + idTimeStamp, err := strconv.ParseUint(idParts[0], 10, 64) if err != nil { - log.Error("Checking pending messages", "error", err) - return p.cfg.CheckPendingInterval - } - if len(staleIds) == 0 { - return p.cfg.CheckPendingInterval + return [2]uint64{}, fmt.Errorf("invalid i.d: %v err: %w", msgId, err) } - if p.cfg.EnableReproduce { - err = p.reproduceIds(ctx, staleIds) - if err != nil { - log.Warn("filed reproducing messages", "err", err) - } - } else { - p.errorPromisesFor(staleIds) - } - return p.cfg.CheckPendingInterval -} - -func (p *Producer[Request, Response]) reproduceIds(ctx context.Context, staleIds []string) error { - log.Info("Attempting to claim", "messages", staleIds) - claimedMsgs, err := p.client.XClaim(ctx, &redis.XClaimArgs{ - Stream: p.redisStream, - Group: p.redisGroup, - Consumer: p.id, - MinIdle: p.cfg.KeepAliveTimeout, - Messages: staleIds, - }).Result() + idSerial, err := strconv.ParseUint(idParts[1], 10, 64) if err != nil { - return fmt.Errorf("claiming ownership on messages: %v, error: %w", staleIds, err) + return [2]uint64{}, fmt.Errorf("invalid i.d serial: %v err: %w", msgId, err) } - for _, msg := range claimedMsgs { - data, ok := (msg.Values[messageKey]).(string) - if !ok { - log.Error("redis producer reproduce: message not string", "id", msg.ID, "value", msg.Values[messageKey]) - continue - } - var req Request - if err := json.Unmarshal([]byte(data), &req); err != nil { - log.Error("redis producer reproduce: message not a request", "id", msg.ID, "err", err, "value", msg.Values[messageKey]) - continue - } - if _, err := p.client.XAck(ctx, p.redisStream, p.redisGroup, msg.ID).Result(); err != nil { - log.Error("redis producer reproduce: could not ACK", "id", msg.ID, "err", err) - continue - } - // Only re-insert messages that were removed the the pending list first. - if _, err := p.reproduce(ctx, req, msg.ID); err != nil { - log.Error("redis producer reproduce: error", "err", err) - } - } - return nil + return [2]uint64{idTimeStamp, idSerial}, nil } -func setMinIdInt(min *[2]uint64, id string) error { - idParts := strings.Split(id, "-") - if len(idParts) != 2 { - return fmt.Errorf("invalid i.d: %v", id) - } - idTimeStamp, err := strconv.ParseUint(idParts[0], 10, 64) +// cmpMsgId compares two msgid's and returns (0) if equal, (-1) if msgId1 < msgId2, (1) if msgId1 > msgId2, (-2) if not comparable (or error) +func cmpMsgId(msgId1, msgId2 string) int { + id1, err := getUintParts(msgId1) if err != nil { - return fmt.Errorf("invalid i.d: %v err: %w", id, err) + log.Trace("error comparing msgIds", "msgId1", msgId1, "msgId2", msgId2) + return -2 } - if idTimeStamp > min[0] { - return nil - } - idSerial, err := strconv.ParseUint(idParts[1], 10, 64) + id2, err := getUintParts(msgId2) if err != nil { - return fmt.Errorf("invalid i.d serial: %v err: %w", id, err) + log.Trace("error comparing msgIds", "msgId1", msgId1, "msgId2", msgId2) + return -2 } - if idTimeStamp < min[0] { - min[0] = idTimeStamp - min[1] = idSerial - return nil + if id1[0] < id2[0] { + return -1 + } else if id1[0] > id2[0] { + return 1 + } else if id1[1] < id2[1] { + return -1 + } else if id1[1] > id2[1] { + return 1 } - // idTimeStamp == min[0] - if idSerial < min[1] { - min[1] = idSerial - } - return nil + return 0 } // checkResponses checks iteratively whether response for the promise is ready. func (p *Producer[Request, Response]) checkResponses(ctx context.Context) time.Duration { - minIdInt := [2]uint64{math.MaxUint64, math.MaxUint64} + log.Debug("redis producer: check responses starting") p.promisesLock.Lock() defer p.promisesLock.Unlock() responded := 0 errored := 0 + checked := 0 + allowedOldestID := fmt.Sprintf("%d-0", time.Now().Add(-p.cfg.RequestTimeout).UnixMilli()) for id, promise := range p.promises { if ctx.Err() != nil { return 0 } - res, err := p.client.Get(ctx, id).Result() + checked++ + resultKey := ResultKeyFor(p.redisStream, id) + res, err := p.client.Get(ctx, resultKey).Result() if err != nil { - errSetId := setMinIdInt(&minIdInt, id) - if errSetId != nil { - log.Error("error setting minId", "err", err) - return p.cfg.CheckResultInterval - } if !errors.Is(err, redis.Nil) { - log.Error("Error reading value in redis", "key", id, "error", err) + log.Error("Error reading value in redis", "key", resultKey, "error", err) + } else if cmpMsgId(id, allowedOldestID) == -1 { + // The request this producer is waiting for has been past its TTL or is older than current PEL's lower, + // so safe to error and stop tracking this promise + promise.ProduceError(errors.New("error getting response, request has been waiting for too long")) + log.Error("error getting response, request has been waiting past its TTL") + errored++ + delete(p.promises, id) } continue } var resp Response if err := json.Unmarshal([]byte(res), &resp); err != nil { promise.ProduceError(fmt.Errorf("error unmarshalling: %w", err)) - log.Error("Error unmarshaling", "value", res, "error", err) + log.Error("redis producer: Error unmarshaling", "value", res, "error", err) errored++ } else { promise.Produce(resp) responded++ } + p.client.Del(ctx, resultKey) delete(p.promises, id) } - var trimmed int64 - var trimErr error - minId := "+" - if minIdInt[0] < math.MaxUint64 { - minId = fmt.Sprintf("%d-%d", minIdInt[0], minIdInt[1]) - trimmed, trimErr = p.client.XTrimMinID(ctx, p.redisStream, minId).Result() - } else { - trimmed, trimErr = p.client.XTrimMaxLen(ctx, p.redisStream, 0).Result() - } - log.Trace("trimming", "id", minId, "trimmed", trimmed, "responded", responded, "errored", errored, "trim-err", trimErr) + log.Debug("checkResponses", "responded", responded, "errored", errored, "checked", checked) return p.cfg.CheckResultInterval } +func (p *Producer[Request, Response]) clearMessages(ctx context.Context) time.Duration { + pelData, err := p.client.XPending(ctx, p.redisStream, p.redisGroup).Result() + if err != nil { + log.Error("error getting PEL data from xpending, xtrimming is disabled", "err", err) + } + // XDEL on consumer side already deletes acked messages (mark as deleted) but doesnt claim the memory back, XTRIM helps in claiming this memory in normal conditions + // pelData might be outdated when we do the xtrim, but thats ok as the messages are also being trimmed by other producers + if pelData != nil && pelData.Lower != "" { + trimmed, trimErr := p.client.XTrimMinID(ctx, p.redisStream, pelData.Lower).Result() + log.Debug("trimming", "xTrimMinID", pelData.Lower, "trimmed", trimmed, "trim-err", trimErr) + // Check if pelData.Lower has been past its TTL and if it is then ack it to remove from PEL and delete it, once + // its taken out from PEL the producer that sent this request will handle the corresponding promise accordingly (as its past TTL) + allowedOldestID := fmt.Sprintf("%d-0", time.Now().Add(-p.cfg.RequestTimeout).UnixMilli()) + if cmpMsgId(pelData.Lower, allowedOldestID) == -1 { + if err := p.client.XClaim(ctx, &redis.XClaimArgs{ + Stream: p.redisStream, + Group: p.redisGroup, + Consumer: p.id, + MinIdle: 0, + Messages: []string{pelData.Lower}, + }).Err(); err != nil { + log.Error("error claiming PEL's lower message thats past its TTL", "msgID", pelData.Lower, "err", err) + return 5 * p.cfg.CheckResultInterval + } + if _, err := p.client.XAck(ctx, p.redisStream, p.redisGroup, pelData.Lower).Result(); err != nil { + log.Error("error acking PEL's lower message thats past its TTL", "msgID", pelData.Lower, "err", err) + return 5 * p.cfg.CheckResultInterval + } + if _, err := p.client.XDel(ctx, p.redisStream, pelData.Lower).Result(); err != nil { + log.Error("error deleting PEL's lower message thats past its TTL", "msgID", pelData.Lower, "err", err) + return 0 + } + } + } + return 5 * p.cfg.CheckResultInterval +} + func (p *Producer[Request, Response]) Start(ctx context.Context) { p.StopWaiter.Start(ctx, p) } @@ -259,101 +218,31 @@ func (p *Producer[Request, Response]) promisesLen() int { return len(p.promises) } -// reproduce is used when Producer claims ownership on the pending -// message that was sent to inactive consumer and reinserts it into the stream, -// so that seamlessly return the answer in the same promise. -func (p *Producer[Request, Response]) reproduce(ctx context.Context, value Request, oldKey string) (*containers.Promise[Response], error) { +func (p *Producer[Request, Response]) produce(ctx context.Context, value Request) (*containers.Promise[Response], error) { val, err := json.Marshal(value) if err != nil { return nil, fmt.Errorf("marshaling value: %w", err) } - // catching the promiseLock before we sendXadd makes sure promise ids will - // be always ascending + // catching the promiseLock before we sendXadd makes sure promise ids will be always ascending p.promisesLock.Lock() defer p.promisesLock.Unlock() - id, err := p.client.XAdd(ctx, &redis.XAddArgs{ + msgId, err := p.client.XAdd(ctx, &redis.XAddArgs{ Stream: p.redisStream, Values: map[string]any{messageKey: val}, }).Result() if err != nil { return nil, fmt.Errorf("adding values to redis: %w", err) } - promise := p.promises[oldKey] - if oldKey != "" && promise == nil { - // This will happen if the old consumer became inactive but then ack_d - // the message afterwards. - // don't error - log.Warn("tried reproducing a message but it wasn't found - probably got response", "oldKey", oldKey) - } - if oldKey == "" || promise == nil { - pr := containers.NewPromise[Response](nil) - promise = &pr - } - delete(p.promises, oldKey) - p.promises[id] = promise - return promise, nil + promise := containers.NewPromise[Response](nil) + p.promises[msgId] = &promise + return &promise, nil } func (p *Producer[Request, Response]) Produce(ctx context.Context, value Request) (*containers.Promise[Response], error) { log.Debug("Redis stream producing", "value", value) p.once.Do(func() { - p.StopWaiter.CallIteratively(p.checkAndReproduce) p.StopWaiter.CallIteratively(p.checkResponses) + p.StopWaiter.CallIteratively(p.clearMessages) }) - return p.reproduce(ctx, value, "") -} - -// Check if a consumer is with specified ID is alive. -func (p *Producer[Request, Response]) isConsumerAlive(ctx context.Context, consumerID string) bool { - if _, err := p.client.Get(ctx, heartBeatKey(consumerID)).Int64(); err != nil { - return false - } - return true -} - -func (p *Producer[Request, Response]) havePromiseFor(messageID string) bool { - p.promisesLock.Lock() - defer p.promisesLock.Unlock() - _, found := p.promises[messageID] - return found -} - -// returns ids of pending messages that's worker doesn't appear alive -func (p *Producer[Request, Response]) checkPending(ctx context.Context) ([]string, error) { - pendingMessages, err := p.client.XPendingExt(ctx, &redis.XPendingExtArgs{ - Stream: p.redisStream, - Group: p.redisGroup, - Start: "-", - End: "+", - Count: p.cfg.CheckPendingItems, - }).Result() - - if err != nil && !errors.Is(err, redis.Nil) { - return nil, fmt.Errorf("querying pending messages: %w", err) - } - if len(pendingMessages) == 0 { - return nil, nil - } - if len(pendingMessages) >= int(p.cfg.CheckPendingItems) { - log.Warn("redis producer: many pending items found", "stream", p.redisStream, "check-pending-items", p.cfg.CheckPendingItems) - } - // IDs of the pending messages with inactive consumers. - var ids []string - active := make(map[string]bool) - for _, msg := range pendingMessages { - // Ignore messages not produced by this producer. - if !p.havePromiseFor(msg.ID) { - continue - } - alive, found := active[msg.Consumer] - if !found { - alive = p.isConsumerAlive(ctx, msg.Consumer) - active[msg.Consumer] = alive - } - if alive { - continue - } - ids = append(ids, msg.ID) - } - return ids, nil + return p.produce(ctx, value) } diff --git a/pubsub/pubsub_test.go b/pubsub/pubsub_test.go index 9f774b6372..8bd1aed25d 100644 --- a/pubsub/pubsub_test.go +++ b/pubsub/pubsub_test.go @@ -10,11 +10,11 @@ import ( "time" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/redis/go-redis/v9" ) var ( @@ -23,7 +23,8 @@ var ( ) type testRequest struct { - Request string + Request string + IsInvalid bool } type testResponse struct { @@ -45,36 +46,21 @@ func destroyRedisGroup(ctx context.Context, t *testing.T, streamName string, cli } } -type configOpt interface { - apply(consCfg *ConsumerConfig, prodCfg *ProducerConfig) -} - -type withReproduce struct { - reproduce bool -} - -func (e *withReproduce) apply(_ *ConsumerConfig, prodCfg *ProducerConfig) { - prodCfg.EnableReproduce = e.reproduce -} - func producerCfg() *ProducerConfig { return &ProducerConfig{ - EnableReproduce: TestProducerConfig.EnableReproduce, - CheckPendingInterval: TestProducerConfig.CheckPendingInterval, - KeepAliveTimeout: TestProducerConfig.KeepAliveTimeout, - CheckResultInterval: TestProducerConfig.CheckResultInterval, - CheckPendingItems: TestProducerConfig.CheckPendingItems, + CheckResultInterval: TestProducerConfig.CheckResultInterval, + RequestTimeout: 2 * time.Second, } } func consumerCfg() *ConsumerConfig { return &ConsumerConfig{ ResponseEntryTimeout: TestConsumerConfig.ResponseEntryTimeout, - KeepAliveTimeout: TestConsumerConfig.KeepAliveTimeout, + IdletimeToAutoclaim: TestConsumerConfig.IdletimeToAutoclaim, } } -func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) (redis.UniversalClient, string, *Producer[testRequest, testResponse], []*Consumer[testRequest, testResponse]) { +func newProducerConsumers(ctx context.Context, t *testing.T) (redis.UniversalClient, string, *Producer[testRequest, testResponse], []*Consumer[testRequest, testResponse]) { t.Helper() redisClient, err := redisutil.RedisClientFromURL(redisutil.CreateTestRedis(ctx, t)) if err != nil { @@ -82,9 +68,7 @@ func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) } prodCfg, consCfg := producerCfg(), consumerCfg() streamName := fmt.Sprintf("stream:%s", uuid.NewString()) - for _, o := range opts { - o.apply(consCfg, prodCfg) - } + producer, err := NewProducer[testRequest, testResponse](redisClient, streamName, prodCfg) if err != nil { t.Fatalf("Error creating new producer: %v", err) @@ -102,13 +86,6 @@ func newProducerConsumers(ctx context.Context, t *testing.T, opts ...configOpt) t.Cleanup(func() { ctx := context.Background() destroyRedisGroup(ctx, t, streamName, producer.client) - var keys []string - for _, c := range consumers { - keys = append(keys, c.heartBeatKey()) - } - if _, err := producer.client.Del(ctx, keys...).Result(); err != nil { - log.Debug("Error deleting heartbeat keys", "error", err) - } }) return redisClient, streamName, producer, consumers } @@ -125,10 +102,10 @@ func msgForIndex(idx int) string { return fmt.Sprintf("msg: %d", idx) } -func wantMessages(n int) []string { +func wantMessages(n int, group string) []string { var ret []string for i := 0; i < n; i++ { - ret = append(ret, msgForIndex(i)) + ret = append(ret, group+msgForIndex(i)) } sort.Strings(ret) return ret @@ -143,10 +120,14 @@ func flatten(responses [][]string) []string { return ret } -func produceMessages(ctx context.Context, msgs []string, producer *Producer[testRequest, testResponse]) ([]*containers.Promise[testResponse], error) { +func produceMessages(ctx context.Context, msgs []string, producer *Producer[testRequest, testResponse], withInvalidEntries bool) ([]*containers.Promise[testResponse], error) { var promises []*containers.Promise[testResponse] - for i := 0; i < messagesCount; i++ { - promise, err := producer.Produce(ctx, testRequest{Request: msgs[i]}) + for i := 0; i < len(msgs); i++ { + req := testRequest{Request: msgs[i]} + if withInvalidEntries && i%50 == 0 { + req.IsInvalid = true + } + promise, err := producer.Produce(ctx, req) if err != nil { return nil, err } @@ -197,51 +178,97 @@ func consume(ctx context.Context, t *testing.T, consumers []*Consumer[testReques continue } gotMessages[idx][res.ID] = res.Value.Request - resp := fmt.Sprintf("result for: %v", res.ID) - if err := c.SetResult(ctx, res.ID, testResponse{Response: resp}); err != nil { - t.Errorf("Error setting a result: %v", err) + if !res.Value.IsInvalid { + resp := fmt.Sprintf("result for: %v", res.ID) + if err := c.SetResult(ctx, res.ID, testResponse{Response: resp}); err != nil { + t.Errorf("Error setting a result: %v", err) + } + wantResponses[idx] = append(wantResponses[idx], resp) } - wantResponses[idx] = append(wantResponses[idx], resp) + res.Ack() } }) } return wantResponses } -func TestRedisProduce(t *testing.T) { +func TestRedisProduceComplex(t *testing.T) { log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) t.Parallel() for _, tc := range []struct { - name string - killConsumers bool - autoRecover bool + name string + entriesCount []int + numProducers int + killConsumers bool + withInvalidEntries bool // If this is set, then every 50th entry is invalid (requests that can't be solved by any consumer) }{ { - name: "all consumers are active", - killConsumers: false, - autoRecover: false, + name: "one producer, all consumers are active", + entriesCount: []int{messagesCount}, + numProducers: 1, + }, + { + name: "two producers, all consumers are active", + entriesCount: []int{20, 20}, + numProducers: 2, }, { - name: "some consumers killed, others should take over their work", + name: "one producer, some consumers killed, others should take over their work", + entriesCount: []int{messagesCount}, + numProducers: 1, killConsumers: true, - autoRecover: true, }, + { - name: "some consumers killed, should return failure", + name: "two producers, some consumers killed, others should take over their work, unequal number of requests from producers", + entriesCount: []int{messagesCount, 2 * messagesCount}, + numProducers: 2, killConsumers: true, - autoRecover: false, + }, + { + name: "two producers, some consumers killed, others should take over their work, some invalid entries, unequal number of requests from producers", + entriesCount: []int{messagesCount, 2 * messagesCount}, + numProducers: 2, + killConsumers: true, + withInvalidEntries: true, }, } { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - redisClient, streamName, producer, consumers := newProducerConsumers(ctx, t, &withReproduce{tc.autoRecover}) - producer.Start(ctx) - wantMsgs := wantMessages(messagesCount) - promises, err := produceMessages(ctx, wantMsgs, producer) - if err != nil { - t.Fatalf("Error producing messages: %v", err) + + var producers []*Producer[testRequest, testResponse] + redisClient, streamName, producer, consumers := newProducerConsumers(ctx, t) + producers = append(producers, producer) + if tc.numProducers == 2 { + producer, err := NewProducer[testRequest, testResponse](redisClient, streamName, producerCfg()) + if err != nil { + t.Fatalf("Error creating second producer: %v", err) + } + producers = append(producers, producer) + } + + for _, producer := range producers { + producer.Start(ctx) + } + + var entries [][]string + if tc.numProducers == 2 { + entries = append(entries, wantMessages(tc.entriesCount[0], "1.")) + entries = append(entries, wantMessages(tc.entriesCount[1], "2.")) + } else { + entries = append(entries, wantMessages(tc.entriesCount[0], "")) } + + var promises [][]*containers.Promise[testResponse] + for i := 0; i < tc.numProducers; i++ { + prs, err := produceMessages(ctx, entries[i], producers[i], tc.withInvalidEntries) + if err != nil { + t.Fatalf("Error producing messages from producer%d: %v", i, err) + } + promises = append(promises, prs) + } + gotMessages := messagesMaps(len(consumers)) if tc.killConsumers { // Consumer messages in every third consumer but don't ack them to check @@ -252,40 +279,79 @@ func TestRedisProduce(t *testing.T) { if err != nil { t.Errorf("Error consuming message: %v", err) } - if !tc.autoRecover { - gotMessages[i][req.ID] = req.Value.Request + if req == nil { + t.Error("Didn't consume any message") } + // Kills the actnotifier hence allowing XAUTOCLAIM consumers[i].StopAndWait() } } + time.Sleep(time.Second) wantResponses := consume(ctx, t, consumers, gotMessages) - gotResponses, errIndexes := awaitResponses(ctx, promises) - if len(errIndexes) != 0 && tc.autoRecover { - t.Fatalf("Error awaiting responses: %v", errIndexes) + + var gotResponses []string + for i := 0; i < tc.numProducers; i++ { + grs, errIndexes := awaitResponses(ctx, promises[i]) + if tc.withInvalidEntries { + if errIndexes[len(errIndexes)-1]+50 < len(entries[i]) { + t.Fatalf("Unexpected number of invalid requests while awaiting responses") + } + for j, idx := range errIndexes { + if idx != j*50 { + t.Fatalf("Invalid request' index mismatch want: %d got %d", j*50, idx) + } + } + } else if len(errIndexes) != 0 { + t.Fatalf("Error awaiting responses from promises %d: %v", i, errIndexes) + } + gotResponses = append(gotResponses, grs...) } - producer.StopAndWait() + for _, c := range consumers { c.StopAndWait() } - got, err := mergeValues(gotMessages) + + got, err := mergeValues(gotMessages, tc.withInvalidEntries) if err != nil { t.Fatalf("mergeMaps() unexpected error: %v", err) } + // Only when there are invalid entries got will have duplicates + if tc.withInvalidEntries { + got = removeDuplicates(got) + } + + var combinedEntries []string + for i := 0; i < tc.numProducers; i++ { + combinedEntries = append(combinedEntries, entries[i]...) + } + wantMsgs := combinedEntries if diff := cmp.Diff(wantMsgs, got); diff != "" { t.Errorf("Unexpected diff (-want +got):\n%s\n", diff) } - wantResp := flatten(wantResponses) + sort.Strings(gotResponses) + wantResp := flatten(wantResponses) if diff := cmp.Diff(wantResp, gotResponses); diff != "" { t.Errorf("Unexpected diff in responses:\n%s\n", diff) } - if cnt := producer.promisesLen(); cnt != 0 { - t.Errorf("Producer still has %d unfullfilled promises", cnt) + + // Check each producers all promises were responded to + for i := 0; i < tc.numProducers; i++ { + if cnt := producers[i].promisesLen(); cnt != 0 { + t.Errorf("Producer%d still has %d unfullfilled promises", i, cnt) + } } + // Trigger a trim - producer.checkResponses(ctx) + time.Sleep(time.Second) + for i := 0; i < tc.numProducers; i++ { + producers[i].checkResponses(ctx) + producers[i].StopAndWait() + } + + // Check that no messages remain in the stream msgs, err := redisClient.XRange(ctx, streamName, "-", "+").Result() if err != nil { t.Errorf("XRange failed: %v", err) @@ -297,14 +363,27 @@ func TestRedisProduce(t *testing.T) { } } +func removeDuplicates(list []string) []string { + capture := map[string]bool{} + var ret []string + for _, elem := range list { + if _, found := capture[elem]; !found { + ret = append(ret, elem) + capture[elem] = true + } + } + sort.Strings(ret) + return ret +} + // mergeValues merges maps from the slice and returns their values. // Returns and error if there exists duplicate key. -func mergeValues(messages []map[string]string) ([]string, error) { +func mergeValues(messages []map[string]string, withInvalidEntries bool) ([]string, error) { res := make(map[string]any) var ret []string for _, m := range messages { for k, v := range m { - if _, found := res[k]; found { + if _, found := res[k]; found && !withInvalidEntries { return nil, fmt.Errorf("duplicate key: %v", k) } res[k] = v diff --git a/scripts/build-brotli.sh b/scripts/build-brotli.sh index 7160936baa..1a23a88ae0 100755 --- a/scripts/build-brotli.sh +++ b/scripts/build-brotli.sh @@ -2,7 +2,7 @@ set -e -mydir=`dirname $0` +mydir=$(dirname "$0") cd "$mydir" BUILD_WASM=false @@ -35,7 +35,7 @@ usage(){ echo "all relative paths are relative to script location" } -while getopts "s:t:c:D:wldhf" option; do +while getopts "n:s:t:c:D:wldhf" option; do case $option in h) usage @@ -62,6 +62,9 @@ while getopts "s:t:c:D:wldhf" option; do s) SOURCE_DIR="$OPTARG" ;; + *) + usage + ;; esac done @@ -74,7 +77,7 @@ if [ ! -d "$TARGET_DIR" ]; then mkdir -p "${TARGET_DIR}lib" ln -s "lib" "${TARGET_DIR}lib64" # Fedora build fi -TARGET_DIR_ABS=`cd -P "$TARGET_DIR"; pwd` +TARGET_DIR_ABS=$(cd -P "$TARGET_DIR"; pwd) if $USE_DOCKER; then @@ -94,9 +97,9 @@ cd "$SOURCE_DIR" if $BUILD_WASM; then mkdir -p buildfiles/build-wasm mkdir -p buildfiles/install-wasm - TEMP_INSTALL_DIR_ABS=`cd -P buildfiles/install-wasm; pwd` + TEMP_INSTALL_DIR_ABS=$(cd -P buildfiles/install-wasm; pwd) cd buildfiles/build-wasm - cmake ../../ -DCMAKE_C_COMPILER=emcc -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DCMAKE_INSTALL_PREFIX="$TEMP_INSTALL_DIR_ABS" -DCMAKE_AR=`which emar` -DCMAKE_RANLIB=`which touch` + cmake ../../ -DCMAKE_C_COMPILER=emcc -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-fPIC -DCMAKE_INSTALL_PREFIX="$TEMP_INSTALL_DIR_ABS" -DCMAKE_AR="$(which emar)" -DCMAKE_RANLIB="$(which touch)" make -j make install cp -rv "$TEMP_INSTALL_DIR_ABS/lib" "$TARGET_DIR_ABS/lib-wasm" diff --git a/scripts/check-build.sh b/scripts/check-build.sh new file mode 100755 index 0000000000..d654405c49 --- /dev/null +++ b/scripts/check-build.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# This script checks the prerequisites for building Arbitrum Nitro locally. + +# Color codes +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Documentation link for installation instructions +INSTALLATION_DOCS_URL="Refer to https://docs.arbitrum.io/run-arbitrum-node/nitro/build-nitro-locally for installation." + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +EXIT_CODE=0 + +# Detect operating system +OS=$(uname -s) +echo -e "${BLUE}Detected OS: $OS${NC}" +echo -e "${BLUE}Checking prerequisites for building Nitro locally...${NC}" + +# Step 1: Check Docker Installation +if command_exists docker; then + echo -e "${GREEN}Docker is installed.${NC}" +else + echo -e "${RED}Docker is not installed.${NC}" + EXIT_CODE=1 +fi + +# Step 2: Check if Docker service is running +if [[ "$OS" == "Linux" ]] && ! sudo service docker status >/dev/null; then + echo -e "${YELLOW}Docker service is not running on Linux. Start it with: sudo service docker start${NC}" + EXIT_CODE=1 +elif [[ "$OS" == "Darwin" ]] && ! docker info >/dev/null 2>&1; then + echo -e "${YELLOW}Docker service is not running on macOS. Ensure Docker Desktop is started.${NC}" + EXIT_CODE=1 +else + echo -e "${GREEN}Docker service is running.${NC}" +fi + +# Step 3: Check the version tag +VERSION_TAG=$(git tag --points-at HEAD | sed '/-/!s/$/_/' | sort -rV | sed 's/_$//' | head -n 1 | grep ^ || git show -s --pretty=%D | sed 's/, /\n/g' | grep -v '^origin/' | grep -v '^grafted\|HEAD\|master\|main$' || echo "dev") +echo -e "${YELLOW}You are on the version tag: $VERSION_TAG${NC}" + +# Check if submodules are properly initialized and updated +if git submodule status | grep -qE '^-|\+'; then + echo -e "${YELLOW}Submodules are not properly initialized or updated. Run: git submodule update --init --recursive --force${NC}" + EXIT_CODE=1 +else + echo -e "${GREEN}All submodules are properly initialized and up to date.${NC}" +fi + +# Step 4: Check if Nitro Docker Image is built +if docker images | grep -q "nitro-node"; then + echo -e "${GREEN}Nitro Docker image is built.${NC}" +else + echo -e "${YELLOW}Nitro Docker image is not built. Build it using: docker build . --tag nitro-node${NC}" +fi + +# Step 5: Check prerequisites for building binaries +echo -e "${BLUE}Checking prerequisites for building Nitro's binaries...${NC}" +if [[ "$OS" == "Linux" ]]; then + prerequisites=(git curl make cmake npm golang clang make gotestsum wasm2wat wasm-ld python3 yarn) +else + prerequisites=(git curl make cmake npm go golangci-lint wasm2wat clang wasm-ld gotestsum yarn) +fi + +for pkg in "${prerequisites[@]}"; do + EXISTS=$(command_exists "$pkg") + [[ "$pkg" == "make" ]] && pkg="build-essential" + [[ "$pkg" == "wasm2wat" ]] && pkg="wabt" + [[ "$pkg" == "clang" ]] && pkg="llvm" + [[ "$pkg" == "wasm-ld" ]] && pkg="lld" + if $EXISTS; then + # There is no way to check for wabt / llvm directly, since they install multiple tools + # So instead, we check for wasm2wat and clang, which are part of wabt and llvm respectively + # and if they are installed, we assume wabt / llvm is installed else we ask the user to install wabt / llvm + + echo -e "${GREEN}$pkg is installed.${NC}" + else + echo -e "${RED}$pkg is not installed. Please install $pkg.${NC}" + EXIT_CODE=1 + fi +done + +# Step 6: Check Node.js version +if command_exists node && node -v | grep -q "v18"; then + echo -e "${GREEN}Node.js version 18 is installed.${NC}" +else + echo -e "${RED}Node.js version 18 not installed.${NC}" + EXIT_CODE=1 +fi + +# Step 7a: Check Rust version +if command_exists rustc && rustc --version | grep -q "1.80.1"; then + echo -e "${GREEN}Rust version 1.80.1 is installed.${NC}" +else + echo -e "${RED}Rust version 1.80.1 not installed.${NC}" + EXIT_CODE=1 +fi + +# Step 7b: Check Rust nightly toolchain +if rustup toolchain list | grep -q "nightly"; then + echo -e "${GREEN}Rust nightly toolchain is installed.${NC}" +else + echo -e "${RED}Rust nightly toolchain is not installed. Install it using: rustup toolchain install nightly${NC}" + EXIT_CODE=1 +fi + +# Step 8: Check Go version +go_version_needed=$(grep "^go " go.mod | awk '{print $2}') +if command_exists go && go version | grep -q "$go_version_needed"; then + echo -e "${GREEN}Go version $go_version_needed is installed.${NC}" +else + echo -e "${RED}Go version $go_version_needed not installed.${NC}" + EXIT_CODE=1 +fi + +# Step 9: Check Foundry installation +if command_exists foundryup; then + echo -e "${GREEN}Foundry is installed.${NC}" +else + echo -e "${RED}Foundry is not installed.${NC}" + EXIT_CODE=1 +fi + +echo -e "${BLUE}Verification complete.${NC}" + +if [ $EXIT_CODE != 0 ]; then + echo -e "${RED}One or more dependencies missing. $INSTALLATION_DOCS_URL${NC}" +fi + +exit $EXIT_CODE + diff --git a/scripts/convert-databases.bash b/scripts/convert-databases.bash index 3020b389b4..baddcdcacd 100755 --- a/scripts/convert-databases.bash +++ b/scripts/convert-databases.bash @@ -33,7 +33,7 @@ printStatus() { } printUsage() { -echo Usage: $0 \[OPTIONS..\] +echo Usage: "$0" \[OPTIONS..\] echo echo OPTIONS: echo "--dbconv dbconv binary path (default: \"$DEFAULT_DBCONV\")" @@ -42,15 +42,15 @@ echo Usage: $0 \[OPTIONS..\] echo "--force remove destination directory if it exists" echo "--skip-existing skip convertion of databases which directories already exist in the destination directory" echo "--clean sets what should be removed in case of error, possible values:" - echo " \"failed\" - remove database which conversion failed (default)" + echo " \"failed\" - remove database which conversion failed (default)" echo " \"none\" - remove nothing, leave unfinished and potentially corrupted databases" echo " \"all\" - remove whole destination directory" } removeDir() { cmd="rm -r \"$1\"" - echo $cmd - eval $cmd + echo "$cmd" + eval "$cmd" return $? } @@ -62,7 +62,7 @@ cleanup() { ;; failed) echo "== Note: removing only failed destination directory" - dstdir=$(echo $dst/$1 | tr -s /) + dstdir=$(echo "$dst"/"$1" | tr -s /) removeDir "$dstdir" ;; none) @@ -127,8 +127,8 @@ if $force && $skip_existing; then exit 1 fi -if [ $clean != "all" ] && [ $clean != "failed" ] && [ $clean != "none" ] ; then - echo Error: Invalid --clean value: $clean +if [ "$clean" != "all" ] && [ "$clean" != "failed" ] && [ "$clean" != "none" ] ; then + echo Error: Invalid --clean value: "$clean" printUsage exit 1 fi @@ -138,8 +138,8 @@ if ! [ -e "$dbconv" ]; then exit 1 fi -if ! [ -n "$dst" ]; then - echo Error: Missing destination directory \(\-\-dst\) +if [ -z "$dst" ]; then + echo "Error: Missing destination directory (--dst)" printUsage exit 1 fi @@ -168,9 +168,8 @@ fi if [ -e "$dst" ] && ! $skip_existing; then if $force; then - echo == Warning! Destination already exists, --force is set, removing all files under path: "$dst" - removeDir "$dst" - if [ $? -ne 0 ]; then + echo "== Warning! Destination already exists, --force is set, removing all files under path: $dst" + if ! removeDir "$dst"; then echo Error: failed to remove "$dst" exit 1 fi @@ -183,14 +182,13 @@ fi convert_result= convert () { srcdir="$src"/$1 - dstdir=$(echo $dst/$1 | tr -s /) - if ! [ -e $dstdir ]; then + dstdir=$(echo "$dst"/"$1" | tr -s /) + if ! [ -e "$dstdir" ]; then echo "== Converting $1 db" cmd="$dbconv --src.db-engine=leveldb --src.data \"$srcdir\" --dst.db-engine=pebble --dst.data \"$dstdir\" --convert --compact" - echo $cmd - eval $cmd - if [ $? -ne 0 ]; then - cleanup $1 + echo "$cmd" + if ! eval "$cmd"; then + cleanup "$1" convert_result="FAILED" return 1 fi @@ -221,9 +219,8 @@ if ! [ -e "$dst"/l2chaindata/ancient ]; then ancient_dst=$(echo "$dst"/l2chaindata/ | tr -s /) echo "== Copying l2chaindata ancients" cmd="cp -r \"$ancient_src\" \"$ancient_dst\"" - echo $cmd - eval $cmd - if [ $? -ne 0 ]; then + echo "$cmd" + if ! eval "$cmd"; then l2chaindata_ancient_status="FAILED (failed to copy)" cleanup "l2chaindata" printStatus @@ -249,7 +246,7 @@ if [ $res -ne 0 ]; then exit 1 fi -if [ -e $src/wasm ]; then +if [ -e "$src"/wasm ]; then convert "wasm" res=$? wasm_status=$convert_result @@ -262,7 +259,7 @@ else wasm_status="not found in source directory" fi -if [ -e $src/classic-msg ]; then +if [ -e "$src"/classic-msg ]; then convert "classic-msg" res=$? classicmsg_status=$convert_result diff --git a/scripts/fuzz.bash b/scripts/fuzz.bash index 6271b917b6..a73c208e88 100755 --- a/scripts/fuzz.bash +++ b/scripts/fuzz.bash @@ -2,12 +2,12 @@ set -e -mydir=`dirname $0` +mydir=$(dirname "$0") cd "$mydir" function printusage { - echo Usage: $0 --build \[--binary-path PATH\] - echo " " $0 \ \[--binary-path PATH\] \[--fuzzcache-path PATH\] \[--nitro-path PATH\] \[--duration DURATION\] + echo Usage: "$0" --build \[--binary-path PATH\] + echo " " "$0" \ \[--binary-path PATH\] \[--fuzzcache-path PATH\] \[--nitro-path PATH\] \[--duration DURATION\] echo echo fuzzer names: echo " " FuzzPrecompiles @@ -22,7 +22,6 @@ if [[ $# -eq 0 ]]; then exit fi -fuzz_executable=../target/bin/system_test.fuzz binpath=../target/bin/ fuzzcachepath=../target/var/fuzz-cache nitropath=../ @@ -72,7 +71,7 @@ while [[ $# -gt 0 ]]; do shift ;; FuzzPrecompiles | FuzzStateTransition) - if [[ ! -z "$test_name" ]]; then + if [[ -n "$test_name" ]]; then echo can only run one fuzzer at a time exit 1 fi @@ -81,7 +80,7 @@ while [[ $# -gt 0 ]]; do shift ;; FuzzInboxMultiplexer) - if [[ ! -z "$test_name" ]]; then + if [[ -n "$test_name" ]]; then echo can only run one fuzzer at a time exit 1 fi @@ -102,17 +101,17 @@ fi if $run_build; then for build_group in system_tests arbstate; do - go test -c ${nitropath}/${build_group} -fuzz Fuzz -o "$binpath"/${build_group}.fuzz + go test -c "${nitropath}"/${build_group} -fuzz Fuzz -o "$binpath"/${build_group}.fuzz done fi -if [[ ! -z $test_group ]]; then - timeout "$((60 * duration))" "$binpath"/${test_group}.fuzz -test.run "^$" -test.fuzzcachedir "$fuzzcachepath" -test.fuzz $test_name || exit_status=$? +if [[ -n $test_group ]]; then + timeout "$((60 * duration))" "$binpath"/${test_group}.fuzz -test.run "^$" -test.fuzzcachedir "$fuzzcachepath" -test.fuzz "$test_name" || exit_status=$? fi -if [ -n "$exit_status" ] && [ $exit_status -ne 0 ] && [ $exit_status -ne 124 ]; then +if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ] && [ "$exit_status" -ne 124 ]; then echo "Fuzzing failed." - exit $exit_status + exit "$exit_status" fi echo "Fuzzing succeeded." diff --git a/scripts/startup-testnode.bash b/scripts/startup-testnode.bash index 701e7ff59a..5313a9ec5d 100755 --- a/scripts/startup-testnode.bash +++ b/scripts/startup-testnode.bash @@ -5,9 +5,9 @@ timeout 60 ./nitro-testnode/test-node.bash --init --dev || exit_status=$? -if [ -n "$exit_status" ] && [ $exit_status -ne 0 ] && [ $exit_status -ne 124 ]; then +if [ -n "$exit_status" ] && [ "$exit_status" -ne 0 ] && [ "$exit_status" -ne 124 ]; then echo "Startup failed." - exit $exit_status + exit "$exit_status" fi echo "Startup succeeded." diff --git a/staker/block_validator.go b/staker/block_validator.go index 7a7efca846..5a1f123693 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -29,6 +29,8 @@ import ( "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/client/redis" + "github.com/offchainlabs/nitro/validator/inputs" + "github.com/offchainlabs/nitro/validator/server_api" "github.com/spf13/pflag" ) @@ -56,12 +58,12 @@ type BlockValidator struct { chainCaughtUp bool // can only be accessed from creation thread or if holding reorg-write - nextCreateBatch []byte - nextCreateBatchBlockHash common.Hash - nextCreateBatchMsgCount arbutil.MessageIndex - nextCreateBatchReread bool - nextCreateStartGS validator.GoGlobalState - nextCreatePrevDelayed uint64 + nextCreateBatch *FullBatchInfo + nextCreateBatchReread bool + prevBatchCache map[uint64][]byte + + nextCreateStartGS validator.GoGlobalState + nextCreatePrevDelayed uint64 // can only be accessed from from validation thread or if holding reorg-write lastValidGS validator.GoGlobalState @@ -94,6 +96,9 @@ type BlockValidator struct { // for testing only testingProgressMadeChan chan struct{} + // For troubleshooting failed validations + validationInputsWriter *inputs.Writer + fatalErr chan<- error MemoryFreeLimitChecker resourcemanager.LimitChecker @@ -108,12 +113,16 @@ type BlockValidatorConfig struct { PrerecordedBlocks uint64 `koanf:"prerecorded-blocks" reload:"hot"` RecordingIterLimit uint64 `koanf:"recording-iter-limit"` ForwardBlocks uint64 `koanf:"forward-blocks" reload:"hot"` + BatchCacheLimit uint32 `koanf:"batch-cache-limit"` CurrentModuleRoot string `koanf:"current-module-root"` // TODO(magic) requires reinitialization on hot reload PendingUpgradeModuleRoot string `koanf:"pending-upgrade-module-root"` // TODO(magic) requires StatelessBlockValidator recreation on hot reload FailureIsFatal bool `koanf:"failure-is-fatal" reload:"hot"` Dangerous BlockValidatorDangerousConfig `koanf:"dangerous"` MemoryFreeLimit string `koanf:"memory-free-limit" reload:"hot"` ValidationServerConfigsList string `koanf:"validation-server-configs-list"` + // The directory to which the BlockValidator will write the + // block_inputs_.json files when WriteToFile() is called. + BlockInputsFilePath string `koanf:"block-inputs-file-path"` memoryFreeLimit int } @@ -172,14 +181,16 @@ func BlockValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { redis.ValidationClientConfigAddOptions(prefix+".redis-validation-client-config", f) f.String(prefix+".validation-server-configs-list", DefaultBlockValidatorConfig.ValidationServerConfigsList, "array of execution rpc configs given as a json string. time duration should be supplied in number indicating nanoseconds") f.Duration(prefix+".validation-poll", DefaultBlockValidatorConfig.ValidationPoll, "poll time to check validations") - f.Uint64(prefix+".forward-blocks", DefaultBlockValidatorConfig.ForwardBlocks, "prepare entries for up to that many blocks ahead of validation (small footprint)") + f.Uint64(prefix+".forward-blocks", DefaultBlockValidatorConfig.ForwardBlocks, "prepare entries for up to that many blocks ahead of validation (stores batch-copy per block)") f.Uint64(prefix+".prerecorded-blocks", DefaultBlockValidatorConfig.PrerecordedBlocks, "record that many blocks ahead of validation (larger footprint)") + f.Uint32(prefix+".batch-cache-limit", DefaultBlockValidatorConfig.BatchCacheLimit, "limit number of old batches to keep in block-validator") f.String(prefix+".current-module-root", DefaultBlockValidatorConfig.CurrentModuleRoot, "current wasm module root ('current' read from chain, 'latest' from machines/latest dir, or provide hash)") f.Uint64(prefix+".recording-iter-limit", DefaultBlockValidatorConfig.RecordingIterLimit, "limit on block recordings sent per iteration") f.String(prefix+".pending-upgrade-module-root", DefaultBlockValidatorConfig.PendingUpgradeModuleRoot, "pending upgrade wasm module root to additionally validate (hash, 'latest' or empty)") f.Bool(prefix+".failure-is-fatal", DefaultBlockValidatorConfig.FailureIsFatal, "failing a validation is treated as a fatal error") BlockValidatorDangerousConfigAddOptions(prefix+".dangerous", f) f.String(prefix+".memory-free-limit", DefaultBlockValidatorConfig.MemoryFreeLimit, "minimum free-memory limit after reaching which the blockvalidator pauses validation. Enabled by default as 1GB, to disable provide empty string") + f.String(prefix+".block-inputs-file-path", DefaultBlockValidatorConfig.BlockInputsFilePath, "directory to write block validation inputs files") } func BlockValidatorDangerousConfigAddOptions(prefix string, f *pflag.FlagSet) { @@ -192,12 +203,14 @@ var DefaultBlockValidatorConfig = BlockValidatorConfig{ ValidationServer: rpcclient.DefaultClientConfig, RedisValidationClientConfig: redis.DefaultValidationClientConfig, ValidationPoll: time.Second, - ForwardBlocks: 1024, + ForwardBlocks: 128, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), + BatchCacheLimit: 20, CurrentModuleRoot: "current", PendingUpgradeModuleRoot: "latest", FailureIsFatal: true, Dangerous: DefaultBlockValidatorDangerousConfig, + BlockInputsFilePath: "./target/validation_inputs", MemoryFreeLimit: "default", RecordingIterLimit: 20, } @@ -209,12 +222,14 @@ var TestBlockValidatorConfig = BlockValidatorConfig{ RedisValidationClientConfig: redis.TestValidationClientConfig, ValidationPoll: 100 * time.Millisecond, ForwardBlocks: 128, + BatchCacheLimit: 20, PrerecordedBlocks: uint64(2 * runtime.NumCPU()), RecordingIterLimit: 20, CurrentModuleRoot: "latest", PendingUpgradeModuleRoot: "latest", FailureIsFatal: true, Dangerous: DefaultBlockValidatorDangerousConfig, + BlockInputsFilePath: "./target/validation_inputs", MemoryFreeLimit: "default", } @@ -271,7 +286,15 @@ func NewBlockValidator( progressValidationsChan: make(chan struct{}, 1), config: config, fatalErr: fatalErr, + prevBatchCache: make(map[uint64][]byte), + } + valInputsWriter, err := inputs.NewWriter( + inputs.WithBaseDir(ret.stack.InstanceDir()), + inputs.WithSlug("BlockValidator")) + if err != nil { + return nil, err } + ret.validationInputsWriter = valInputsWriter if !config().Dangerous.ResetBlockValidation { validated, err := ret.ReadLastValidatedInfo() if err != nil { @@ -503,18 +526,16 @@ func (v *BlockValidator) sendRecord(s *validationStatus) error { } //nolint:gosec -func (v *BlockValidator) writeToFile(validationEntry *validationEntry, moduleRoot common.Hash) error { +func (v *BlockValidator) writeToFile(validationEntry *validationEntry) error { input, err := validationEntry.ToInput([]ethdb.WasmTarget{rawdb.TargetWavm}) if err != nil { return err } - for _, spawner := range v.execSpawners { - if validator.SpawnerSupportsModule(spawner, moduleRoot) { - _, err = spawner.WriteToFile(input, validationEntry.End, moduleRoot).Await(v.GetContext()) - return err - } + inputJson := server_api.ValidationInputToJson(input) + if err := v.validationInputsWriter.Write(inputJson); err != nil { + return err } - return errors.New("did not find exec spawner for wasmModuleRoot") + return nil } func (v *BlockValidator) SetCurrentWasmModuleRoot(hash common.Hash) error { @@ -571,33 +592,63 @@ func (v *BlockValidator) createNextValidationEntry(ctx context.Context) (bool, e } if v.nextCreateStartGS.PosInBatch == 0 || v.nextCreateBatchReread { // new batch - found, batch, batchBlockHash, count, err := v.readBatch(ctx, v.nextCreateStartGS.Batch) + found, fullBatchInfo, err := v.readFullBatch(ctx, v.nextCreateStartGS.Batch) if !found { return false, err } - v.nextCreateBatch = batch - v.nextCreateBatchBlockHash = batchBlockHash - v.nextCreateBatchMsgCount = count + if v.nextCreateBatch != nil { + v.prevBatchCache[v.nextCreateBatch.Number] = v.nextCreateBatch.PostedData + } + v.nextCreateBatch = fullBatchInfo // #nosec G115 - validatorMsgCountCurrentBatch.Update(int64(count)) + validatorMsgCountCurrentBatch.Update(int64(fullBatchInfo.MsgCount)) + batchCacheLimit := v.config().BatchCacheLimit + if len(v.prevBatchCache) > int(batchCacheLimit) { + for num := range v.prevBatchCache { + if num+uint64(batchCacheLimit) < v.nextCreateStartGS.Batch { + delete(v.prevBatchCache, num) + } + } + } v.nextCreateBatchReread = false } endGS := validator.GoGlobalState{ BlockHash: endRes.BlockHash, SendRoot: endRes.SendRoot, } - if pos+1 < v.nextCreateBatchMsgCount { + if pos+1 < v.nextCreateBatch.MsgCount { endGS.Batch = v.nextCreateStartGS.Batch endGS.PosInBatch = v.nextCreateStartGS.PosInBatch + 1 - } else if pos+1 == v.nextCreateBatchMsgCount { + } else if pos+1 == v.nextCreateBatch.MsgCount { endGS.Batch = v.nextCreateStartGS.Batch + 1 endGS.PosInBatch = 0 } else { - return false, fmt.Errorf("illegal batch msg count %d pos %d batch %d", v.nextCreateBatchMsgCount, pos, endGS.Batch) + return false, fmt.Errorf("illegal batch msg count %d pos %d batch %d", v.nextCreateBatch.MsgCount, pos, endGS.Batch) } chainConfig := v.streamer.ChainConfig() + prevBatchNums, err := msg.Message.PastBatchesRequired() + if err != nil { + return false, err + } + prevBatches := make([]validator.BatchInfo, 0, len(prevBatchNums)) + // prevBatchNums are only used for batch reports, each is only used once + for _, batchNum := range prevBatchNums { + data, found := v.prevBatchCache[batchNum] + if found { + delete(v.prevBatchCache, batchNum) + } else { + data, err = v.readPostedBatch(ctx, batchNum) + if err != nil { + return false, err + } + } + prevBatches = append(prevBatches, validator.BatchInfo{ + Number: batchNum, + Data: data, + }) + } entry, err := newValidationEntry( - pos, v.nextCreateStartGS, endGS, msg, v.nextCreateBatch, v.nextCreateBatchBlockHash, v.nextCreatePrevDelayed, chainConfig, + pos, v.nextCreateStartGS, endGS, msg, v.nextCreateBatch, prevBatches, v.nextCreatePrevDelayed, chainConfig, ) if err != nil { return false, err @@ -788,7 +839,7 @@ validationsLoop: runEnd, err := run.Current() if err == nil && runEnd != validationStatus.Entry.End { err = fmt.Errorf("validation failed: expected %v got %v", validationStatus.Entry.End, runEnd) - writeErr := v.writeToFile(validationStatus.Entry, run.WasmModuleRoot()) + writeErr := v.writeToFile(validationStatus.Entry) if writeErr != nil { log.Warn("failed to write debug results file", "err", writeErr) } @@ -997,6 +1048,9 @@ func (v *BlockValidator) UpdateLatestStaked(count arbutil.MessageIndex, globalSt v.nextCreateStartGS = globalState v.nextCreatePrevDelayed = msg.DelayedMessagesRead v.nextCreateBatchReread = true + if v.nextCreateBatch != nil { + v.prevBatchCache[v.nextCreateBatch.Number] = v.nextCreateBatch.PostedData + } v.createdA.Store(countUint64) } // under the reorg mutex we don't need atomic access @@ -1023,6 +1077,7 @@ func (v *BlockValidator) ReorgToBatchCount(count uint64) { defer v.reorgMutex.Unlock() if v.nextCreateStartGS.Batch >= count { v.nextCreateBatchReread = true + v.prevBatchCache = make(map[uint64][]byte) } } @@ -1063,6 +1118,7 @@ func (v *BlockValidator) Reorg(ctx context.Context, count arbutil.MessageIndex) v.nextCreateStartGS = buildGlobalState(*res, endPosition) v.nextCreatePrevDelayed = msg.DelayedMessagesRead v.nextCreateBatchReread = true + v.prevBatchCache = make(map[uint64][]byte) countUint64 := uint64(count) v.createdA.Store(countUint64) // under the reorg mutex we don't need atomic access diff --git a/staker/l1_validator.go b/staker/l1_validator.go index 6ea9fd8ded..5b0c211324 100644 --- a/staker/l1_validator.go +++ b/staker/l1_validator.go @@ -19,6 +19,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/rollupgen" @@ -45,7 +46,7 @@ type L1Validator struct { rollup *RollupWatcher rollupAddress common.Address validatorUtils *rollupgen.ValidatorUtils - client arbutil.L1Interface + client *ethclient.Client builder *txbuilder.Builder wallet ValidatorWalletInterface callOpts bind.CallOpts @@ -57,7 +58,7 @@ type L1Validator struct { } func NewL1Validator( - client arbutil.L1Interface, + client *ethclient.Client, wallet ValidatorWalletInterface, validatorUtilsAddress common.Address, callOpts bind.CallOpts, diff --git a/staker/rollup_watcher.go b/staker/rollup_watcher.go index 5ef28a49dc..4d7db52322 100644 --- a/staker/rollup_watcher.go +++ b/staker/rollup_watcher.go @@ -4,16 +4,19 @@ package staker import ( + "bytes" "context" "encoding/binary" "errors" "fmt" "math/big" + "strings" "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/util/headerreader" @@ -48,12 +51,19 @@ type RollupWatcher struct { *rollupgen.RollupUserLogic address common.Address fromBlock *big.Int - client arbutil.L1Interface + client RollupWatcherL1Interface baseCallOpts bind.CallOpts unSupportedL3Method atomic.Bool + supportedL3Method atomic.Bool } -func NewRollupWatcher(address common.Address, client arbutil.L1Interface, callOpts bind.CallOpts) (*RollupWatcher, error) { +type RollupWatcherL1Interface interface { + bind.ContractBackend + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) +} + +func NewRollupWatcher(address common.Address, client RollupWatcherL1Interface, callOpts bind.CallOpts) (*RollupWatcher, error) { con, err := rollupgen.NewRollupUserLogic(address, client) if err != nil { return nil, err @@ -73,15 +83,41 @@ func (r *RollupWatcher) getCallOpts(ctx context.Context) *bind.CallOpts { return &opts } +const noNodeErr string = "NO_NODE" + +func looksLikeNoNodeError(err error) bool { + if err == nil { + return false + } + if strings.Contains(err.Error(), noNodeErr) { + return true + } + var errWithData rpc.DataError + ok := errors.As(err, &errWithData) + if !ok { + return false + } + dataString, ok := errWithData.ErrorData().(string) + if !ok { + return false + } + data := common.FromHex(dataString) + return bytes.Contains(data, []byte(noNodeErr)) +} + func (r *RollupWatcher) getNodeCreationBlock(ctx context.Context, nodeNum uint64) (*big.Int, error) { callOpts := r.getCallOpts(ctx) if !r.unSupportedL3Method.Load() { createdAtBlock, err := r.GetNodeCreationBlockForLogLookup(callOpts, nodeNum) if err == nil { + r.supportedL3Method.Store(true) return createdAtBlock, nil } - log.Trace("failed to call getNodeCreationBlockForLogLookup, falling back on node CreatedAtBlock field", "err", err) - if headerreader.ExecutionRevertedRegexp.MatchString(err.Error()) { + if headerreader.ExecutionRevertedRegexp.MatchString(err.Error()) && !looksLikeNoNodeError(err) { + if r.supportedL3Method.Load() { + return nil, fmt.Errorf("getNodeCreationBlockForLogLookup failed despite previously succeeding: %w", err) + } + log.Info("getNodeCreationBlockForLogLookup does not seem to exist, falling back on node CreatedAtBlock field", "err", err) r.unSupportedL3Method.Store(true) } else { return nil, err diff --git a/staker/staker.go b/staker/staker.go index 77ca93e02c..45e6f6f551 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rpc" @@ -280,7 +281,7 @@ type ValidatorWalletInterface interface { TxSenderAddress() *common.Address RollupAddress() common.Address ChallengeManagerAddress() common.Address - L1Client() arbutil.L1Interface + L1Client() *ethclient.Client TestTransactions(context.Context, []*types.Transaction) error ExecuteTransactions(context.Context, *txbuilder.Builder, common.Address) (*types.Transaction, error) TimeoutChallenges(context.Context, []uint64) (*types.Transaction, error) @@ -304,7 +305,6 @@ func NewStaker( validatorUtilsAddress common.Address, fatalErr chan<- error, ) (*Staker, error) { - if err := config().Validate(); err != nil { return nil, err } @@ -511,7 +511,9 @@ func (s *Staker) Start(ctxIn context.Context) { } s.StopWaiter.Start(ctxIn, s) backoff := time.Second - ephemeralErrorHandler := util.NewEphemeralErrorHandler(10*time.Minute, "is ahead of on-chain nonce", 0) + isAheadOfOnChainNonceEphemeralErrorHandler := util.NewEphemeralErrorHandler(10*time.Minute, "is ahead of on-chain nonce", 0) + exceedsMaxMempoolSizeEphemeralErrorHandler := util.NewEphemeralErrorHandler(10*time.Minute, dataposter.ErrExceedsMaxMempoolSize.Error(), 0) + blockValidationPendingEphemeralErrorHandler := util.NewEphemeralErrorHandler(10*time.Minute, "block validation is still pending", 0) s.CallIteratively(func(ctx context.Context) (returningWait time.Duration) { defer func() { panicErr := recover() @@ -545,7 +547,9 @@ func (s *Staker) Start(ctxIn context.Context) { } } if err == nil { - ephemeralErrorHandler.Reset() + isAheadOfOnChainNonceEphemeralErrorHandler.Reset() + exceedsMaxMempoolSizeEphemeralErrorHandler.Reset() + blockValidationPendingEphemeralErrorHandler.Reset() backoff = time.Second stakerLastSuccessfulActionGauge.Update(time.Now().Unix()) stakerActionSuccessCounter.Inc(1) @@ -563,7 +567,9 @@ func (s *Staker) Start(ctxIn context.Context) { } else { logLevel = log.Warn } - logLevel = ephemeralErrorHandler.LogLevel(err, logLevel) + logLevel = isAheadOfOnChainNonceEphemeralErrorHandler.LogLevel(err, logLevel) + logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) + logLevel = blockValidationPendingEphemeralErrorHandler.LogLevel(err, logLevel) logLevel("error acting as staker", "err", err) return backoff }) @@ -1220,7 +1226,7 @@ func (s *Staker) updateStakerBalanceMetric(ctx context.Context) { } balance, err := s.client.BalanceAt(ctx, *txSenderAddress, nil) if err != nil { - log.Error("error getting staker balance", "txSenderAddress", *txSenderAddress, "err", err) + log.Warn("error getting staker balance", "txSenderAddress", *txSenderAddress, "err", err) return } stakerBalanceGauge.Update(arbmath.BalancePerEther(balance)) diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index f54ec8b58c..d9c9c5446b 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -23,6 +23,7 @@ import ( "github.com/offchainlabs/nitro/util/rpcclient" "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/client/redis" + "github.com/offchainlabs/nitro/validator/server_api" validatorclient "github.com/offchainlabs/nitro/validator/client" ) @@ -40,6 +41,7 @@ type StatelessBlockValidator struct { streamer TransactionStreamerInterface db ethdb.Database dapReaders []daprovider.Reader + stack *node.Node } type BlockValidatorRegistrer interface { @@ -114,6 +116,13 @@ const ( Ready ) +type FullBatchInfo struct { + Number uint64 + PostedData []byte + MsgCount arbutil.MessageIndex + Preimages map[arbutil.PreimageType]map[common.Hash][]byte +} + type validationEntry struct { Stage ValidationEntryStage // Valid since ReadyforRecord: @@ -171,16 +180,28 @@ func newValidationEntry( start validator.GoGlobalState, end validator.GoGlobalState, msg *arbostypes.MessageWithMetadata, - batch []byte, - batchBlockHash common.Hash, + fullBatchInfo *FullBatchInfo, + prevBatches []validator.BatchInfo, prevDelayed uint64, chainConfig *params.ChainConfig, ) (*validationEntry, error) { - batchInfo := validator.BatchInfo{ - Number: start.Batch, - BlockHash: batchBlockHash, - Data: batch, + preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) + if fullBatchInfo == nil { + return nil, fmt.Errorf("fullbatchInfo cannot be nil") } + if fullBatchInfo.Number != start.Batch { + return nil, fmt.Errorf("got wrong batch expected: %d got: %d", start.Batch, fullBatchInfo.Number) + } + valBatches := []validator.BatchInfo{ + { + Number: fullBatchInfo.Number, + Data: fullBatchInfo.PostedData, + }, + } + valBatches = append(valBatches, prevBatches...) + + copyPreimagesInto(preimages, fullBatchInfo.Preimages) + hasDelayed := false var delayedNum uint64 if msg.DelayedMessagesRead == prevDelayed+1 { @@ -189,6 +210,7 @@ func newValidationEntry( } else if msg.DelayedMessagesRead != prevDelayed { return nil, fmt.Errorf("illegal validation entry delayedMessage %d, previous %d", msg.DelayedMessagesRead, prevDelayed) } + return &validationEntry{ Stage: ReadyForRecord, Pos: pos, @@ -197,8 +219,9 @@ func newValidationEntry( HasDelayedMsg: hasDelayed, DelayedMsgNr: delayedNum, msg: msg, - BatchInfo: []validator.BatchInfo{batchInfo}, + BatchInfo: valBatches, ChainConfig: chainConfig, + Preimages: preimages, }, nil } @@ -243,33 +266,88 @@ func NewStatelessBlockValidator( db: arbdb, dapReaders: dapReaders, execSpawners: executionSpawners, + stack: stack, }, nil } -func (v *StatelessBlockValidator) readBatch(ctx context.Context, batchNum uint64) (bool, []byte, common.Hash, arbutil.MessageIndex, error) { +func (v *StatelessBlockValidator) readPostedBatch(ctx context.Context, batchNum uint64) ([]byte, error) { + batchCount, err := v.inboxTracker.GetBatchCount() + if err != nil { + return nil, err + } + if batchCount <= batchNum { + return nil, fmt.Errorf("batch not found: %d", batchNum) + } + postedData, _, err := v.inboxReader.GetSequencerMessageBytes(ctx, batchNum) + return postedData, err +} + +func (v *StatelessBlockValidator) readFullBatch(ctx context.Context, batchNum uint64) (bool, *FullBatchInfo, error) { batchCount, err := v.inboxTracker.GetBatchCount() if err != nil { - return false, nil, common.Hash{}, 0, err + return false, nil, err } if batchCount <= batchNum { - return false, nil, common.Hash{}, 0, nil + return false, nil, nil } batchMsgCount, err := v.inboxTracker.GetBatchMessageCount(batchNum) if err != nil { - return false, nil, common.Hash{}, 0, err + return false, nil, err } - batch, batchBlockHash, err := v.inboxReader.GetSequencerMessageBytes(ctx, batchNum) + postedData, batchBlockHash, err := v.inboxReader.GetSequencerMessageBytes(ctx, batchNum) if err != nil { - return false, nil, common.Hash{}, 0, err + return false, nil, err + } + preimages := make(map[arbutil.PreimageType]map[common.Hash][]byte) + if len(postedData) > 40 { + foundDA := false + for _, dapReader := range v.dapReaders { + if dapReader != nil && dapReader.IsValidHeaderByte(postedData[40]) { + preimageRecorder := daprovider.RecordPreimagesTo(preimages) + _, err := dapReader.RecoverPayloadFromBatch(ctx, batchNum, batchBlockHash, postedData, preimageRecorder, true) + if err != nil { + // Matches the way keyset validation was done inside DAS readers i.e logging the error + // But other daproviders might just want to return the error + if errors.Is(err, daprovider.ErrSeqMsgValidation) && daprovider.IsDASMessageHeaderByte(postedData[40]) { + log.Error(err.Error()) + } else { + return false, nil, err + } + } + foundDA = true + break + } + } + if !foundDA { + if daprovider.IsDASMessageHeaderByte(postedData[40]) { + log.Error("No DAS Reader configured, but sequencer message found with DAS header") + } + } + } + fullInfo := FullBatchInfo{ + Number: batchNum, + PostedData: postedData, + MsgCount: batchMsgCount, + Preimages: preimages, + } + return true, &fullInfo, nil +} + +func copyPreimagesInto(dest, source map[arbutil.PreimageType]map[common.Hash][]byte) { + for piType, piMap := range source { + if dest[piType] == nil { + dest[piType] = make(map[common.Hash][]byte, len(piMap)) + } + for hash, preimage := range piMap { + dest[piType][hash] = preimage + } } - return true, batch, batchBlockHash, batchMsgCount, nil } func (v *StatelessBlockValidator) ValidationEntryRecord(ctx context.Context, e *validationEntry) error { if e.Stage != ReadyForRecord { return fmt.Errorf("validation entry should be ReadyForRecord, is: %v", e.Stage) } - e.Preimages = make(map[arbutil.PreimageType]map[common.Hash][]byte) if e.Pos != 0 { recording, err := v.recorder.RecordBlockCreation(ctx, e.Pos, e.msg) if err != nil { @@ -278,30 +356,11 @@ func (v *StatelessBlockValidator) ValidationEntryRecord(ctx context.Context, e * if recording.BlockHash != e.End.BlockHash { return fmt.Errorf("recording failed: pos %d, hash expected %v, got %v", e.Pos, e.End.BlockHash, recording.BlockHash) } - // record any additional batch fetching - batchFetcher := func(batchNum uint64) ([]byte, error) { - found, data, hash, _, err := v.readBatch(ctx, batchNum) - if err != nil { - return nil, err - } - if !found { - return nil, errors.New("batch not found") - } - e.BatchInfo = append(e.BatchInfo, validator.BatchInfo{ - Number: batchNum, - BlockHash: hash, - Data: data, - }) - return data, nil - } - e.msg.Message.BatchGasCost = nil - err = e.msg.Message.FillInBatchGasCost(batchFetcher) - if err != nil { - return err - } - if recording.Preimages != nil { - e.Preimages[arbutil.Keccak256PreimageType] = recording.Preimages + recordingPreimages := map[arbutil.PreimageType]map[common.Hash][]byte{ + arbutil.Keccak256PreimageType: recording.Preimages, + } + copyPreimagesInto(e.Preimages, recordingPreimages) } e.UserWasms = recording.UserWasms } @@ -316,35 +375,6 @@ func (v *StatelessBlockValidator) ValidationEntryRecord(ctx context.Context, e * } e.DelayedMsg = delayedMsg } - for _, batch := range e.BatchInfo { - if len(batch.Data) <= 40 { - continue - } - foundDA := false - for _, dapReader := range v.dapReaders { - if dapReader != nil && dapReader.IsValidHeaderByte(batch.Data[40]) { - preimageRecorder := daprovider.RecordPreimagesTo(e.Preimages) - _, err := dapReader.RecoverPayloadFromBatch(ctx, batch.Number, batch.BlockHash, batch.Data, preimageRecorder, true) - if err != nil { - // Matches the way keyset validation was done inside DAS readers i.e logging the error - // But other daproviders might just want to return the error - if errors.Is(err, daprovider.ErrSeqMsgValidation) && daprovider.IsDASMessageHeaderByte(batch.Data[40]) { - log.Error(err.Error()) - } else { - return err - } - } - foundDA = true - break - } - } - if !foundDA { - if daprovider.IsDASMessageHeaderByte(batch.Data[40]) { - log.Error("No DAS Reader configured, but sequencer message found with DAS header") - } - } - } - e.msg = nil // no longer needed e.Stage = Ready return nil @@ -404,11 +434,30 @@ func (v *StatelessBlockValidator) CreateReadyValidationEntry(ctx context.Context } start := buildGlobalState(*prevResult, startPos) end := buildGlobalState(*result, endPos) - seqMsg, batchBlockHash, err := v.inboxReader.GetSequencerMessageBytes(ctx, startPos.BatchNumber) + found, fullBatchInfo, err := v.readFullBatch(ctx, start.Batch) + if err != nil { + return nil, err + } + if !found { + return nil, fmt.Errorf("batch %d not found", startPos.BatchNumber) + } + + prevBatchNums, err := msg.Message.PastBatchesRequired() if err != nil { return nil, err } - entry, err := newValidationEntry(pos, start, end, msg, seqMsg, batchBlockHash, prevDelayed, v.streamer.ChainConfig()) + prevBatches := make([]validator.BatchInfo, 0, len(prevBatchNums)) + for _, batchNum := range prevBatchNums { + data, err := v.readPostedBatch(ctx, batchNum) + if err != nil { + return nil, err + } + prevBatches = append(prevBatches, validator.BatchInfo{ + Number: batchNum, + Data: data, + }) + } + entry, err := newValidationEntry(pos, start, end, msg, fullBatchInfo, prevBatches, prevDelayed, v.streamer.ChainConfig()) if err != nil { return nil, err } @@ -462,6 +511,18 @@ func (v *StatelessBlockValidator) ValidateResult( return true, &entry.End, nil } +func (v *StatelessBlockValidator) ValidationInputsAt(ctx context.Context, pos arbutil.MessageIndex, targets ...ethdb.WasmTarget) (server_api.InputJSON, error) { + entry, err := v.CreateReadyValidationEntry(ctx, pos) + if err != nil { + return server_api.InputJSON{}, err + } + input, err := entry.ToInput(targets) + if err != nil { + return server_api.InputJSON{}, err + } + return *server_api.ValidationInputToJson(input), nil +} + func (v *StatelessBlockValidator) OverrideRecorder(t *testing.T, recorder execution.ExecutionRecorder) { v.recorder = recorder } diff --git a/staker/txbuilder/builder.go b/staker/txbuilder/builder.go index 9a5e9df2b5..f52b03a781 100644 --- a/staker/txbuilder/builder.go +++ b/staker/txbuilder/builder.go @@ -12,13 +12,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/offchainlabs/nitro/arbutil" + "github.com/ethereum/go-ethereum/ethclient" ) type ValidatorWalletInterface interface { // Address must be able to be called concurrently with other functions Address() *common.Address - L1Client() arbutil.L1Interface + L1Client() *ethclient.Client TestTransactions(context.Context, []*types.Transaction) error ExecuteTransactions(context.Context, *Builder, common.Address) (*types.Transaction, error) AuthIfEoa() *bind.TransactOpts @@ -27,10 +27,10 @@ type ValidatorWalletInterface interface { // Builder combines any transactions sent to it via SendTransaction into one batch, // which is then sent to the validator wallet. // This lets the validator make multiple atomic transactions. -// This inherits from an eth client so it can be used as an L1Interface, -// where it transparently intercepts calls to SendTransaction and queues them for the next batch. +// This inherits from an ethclient.Client so it can be used to transparently +// intercept calls to SendTransaction and queue them for the next batch. type Builder struct { - arbutil.L1Interface + *ethclient.Client transactions []*types.Transaction builderAuth *bind.TransactOpts isAuthFake bool @@ -55,7 +55,7 @@ func NewBuilder(wallet ValidatorWalletInterface) (*Builder, error) { return &Builder{ builderAuth: builderAuth, wallet: wallet, - L1Interface: wallet.L1Client(), + Client: wallet.L1Client(), isAuthFake: isAuthFake, }, nil } @@ -70,7 +70,7 @@ func (b *Builder) ClearTransactions() { func (b *Builder) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { if len(b.transactions) == 0 && !b.isAuthFake { - return b.L1Interface.EstimateGas(ctx, call) + return b.Client.EstimateGas(ctx, call) } return 0, nil } diff --git a/staker/validatorwallet/contract.go b/staker/validatorwallet/contract.go index 6346029c3a..3202d58569 100644 --- a/staker/validatorwallet/contract.go +++ b/staker/validatorwallet/contract.go @@ -16,10 +16,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode/dataposter" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/txbuilder" "github.com/offchainlabs/nitro/util/arbmath" @@ -384,7 +384,7 @@ func (v *Contract) TimeoutChallenges(ctx context.Context, challenges []uint64) ( return v.dataPoster.PostSimpleTransaction(ctx, auth.Nonce.Uint64(), *v.Address(), data, gas, auth.Value) } -func (v *Contract) L1Client() arbutil.L1Interface { +func (v *Contract) L1Client() *ethclient.Client { return v.l1Reader.Client() } diff --git a/staker/validatorwallet/eoa.go b/staker/validatorwallet/eoa.go index 3ae305b36c..7c7f472579 100644 --- a/staker/validatorwallet/eoa.go +++ b/staker/validatorwallet/eoa.go @@ -10,8 +10,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/offchainlabs/nitro/arbnode/dataposter" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/challengegen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/txbuilder" @@ -19,7 +19,7 @@ import ( type EOA struct { auth *bind.TransactOpts - client arbutil.L1Interface + client *ethclient.Client rollupAddress common.Address challengeManager *challengegen.ChallengeManager challengeManagerAddress common.Address @@ -27,7 +27,7 @@ type EOA struct { getExtraGas func() uint64 } -func NewEOA(dataPoster *dataposter.DataPoster, rollupAddress common.Address, l1Client arbutil.L1Interface, getExtraGas func() uint64) (*EOA, error) { +func NewEOA(dataPoster *dataposter.DataPoster, rollupAddress common.Address, l1Client *ethclient.Client, getExtraGas func() uint64) (*EOA, error) { return &EOA{ auth: dataPoster.Auth(), client: l1Client, @@ -63,7 +63,7 @@ func (w *EOA) TxSenderAddress() *common.Address { return &w.auth.From } -func (w *EOA) L1Client() arbutil.L1Interface { +func (w *EOA) L1Client() *ethclient.Client { return w.client } diff --git a/staker/validatorwallet/noop.go b/staker/validatorwallet/noop.go index b050ebe861..fec39ac2b1 100644 --- a/staker/validatorwallet/noop.go +++ b/staker/validatorwallet/noop.go @@ -10,18 +10,18 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/offchainlabs/nitro/arbnode/dataposter" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/staker/txbuilder" ) // NoOp validator wallet is used for watchtower mode. type NoOp struct { - l1Client arbutil.L1Interface + l1Client *ethclient.Client rollupAddress common.Address } -func NewNoOp(l1Client arbutil.L1Interface, rollupAddress common.Address) *NoOp { +func NewNoOp(l1Client *ethclient.Client, rollupAddress common.Address) *NoOp { return &NoOp{ l1Client: l1Client, rollupAddress: rollupAddress, @@ -46,7 +46,7 @@ func (*NoOp) TimeoutChallenges(ctx context.Context, challenges []uint64) (*types return nil, errors.New("no op validator wallet cannot timeout challenges") } -func (n *NoOp) L1Client() arbutil.L1Interface { return n.l1Client } +func (n *NoOp) L1Client() *ethclient.Client { return n.l1Client } func (n *NoOp) RollupAddress() common.Address { return n.rollupAddress } diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index cca5572f4e..9125c3921e 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -284,6 +284,20 @@ func TestBlockValidatorSimpleOnchain(t *testing.T) { testBlockValidatorSimple(t, opts) } +func TestBlockValidatorSimpleJITOnchainWithPublishedMachine(t *testing.T) { + cr, err := github.LatestConsensusRelease(context.Background()) + Require(t, err) + machPath := populateMachineDir(t, cr) + opts := Options{ + dasModeString: "onchain", + workloadLoops: 1, + workload: ethSend, + arbitrator: false, + wasmRootDir: machPath, + } + testBlockValidatorSimple(t, opts) +} + func TestBlockValidatorSimpleOnchainWithPublishedMachine(t *testing.T) { cr, err := github.LatestConsensusRelease(context.Background()) Require(t, err) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 6975990be0..027a41d875 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -9,6 +9,7 @@ import ( "encoding/binary" "encoding/hex" "encoding/json" + "flag" "io" "math/big" "net" @@ -20,11 +21,11 @@ import ( "testing" "time" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbstate/daprovider" + "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" @@ -36,10 +37,12 @@ import ( "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/redisutil" "github.com/offchainlabs/nitro/util/signature" + "github.com/offchainlabs/nitro/validator/inputs" "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" rediscons "github.com/offchainlabs/nitro/validator/valnode/redis" + "github.com/redis/go-redis/v9" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -69,7 +72,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbutil" _ "github.com/offchainlabs/nitro/execution/nodeInterface" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" @@ -83,15 +85,15 @@ import ( ) type info = *BlockchainTestInfo -type client = arbutil.L1Interface type SecondNodeParams struct { - nodeConfig *arbnode.Config - execConfig *gethexec.Config - stackConfig *node.Config - dasConfig *das.DataAvailabilityConfig - initData *statetransfer.ArbosInitializationInfo - addresses *chaininfo.RollupAddresses + nodeConfig *arbnode.Config + execConfig *gethexec.Config + stackConfig *node.Config + dasConfig *das.DataAvailabilityConfig + initData *statetransfer.ArbosInitializationInfo + addresses *chaininfo.RollupAddresses + wasmCacheTag uint32 } type TestClient struct { @@ -138,8 +140,8 @@ func (tc *TestClient) GetBaseFeeAt(t *testing.T, blockNum *big.Int) *big.Int { return GetBaseFeeAt(t, tc.Client, tc.ctx, blockNum) } -func (tc *TestClient) SendWaitTestTransactions(t *testing.T, txs []*types.Transaction) { - SendWaitTestTransactions(t, tc.ctx, tc.Client, txs) +func (tc *TestClient) SendWaitTestTransactions(t *testing.T, txs []*types.Transaction) []*types.Receipt { + return SendWaitTestTransactions(t, tc.ctx, tc.Client, txs) } func (tc *TestClient) DeploySimple(t *testing.T, auth bind.TransactOpts) (common.Address, *mocksgen.Simple) { @@ -155,19 +157,20 @@ func (tc *TestClient) EnsureTxSucceededWithTimeout(transaction *types.Transactio } var TestCachingConfig = gethexec.CachingConfig{ - Archive: false, - BlockCount: 128, - BlockAge: 30 * time.Minute, - TrieTimeLimit: time.Hour, - TrieDirtyCache: 1024, - TrieCleanCache: 600, - SnapshotCache: 400, - DatabaseCache: 2048, - SnapshotRestoreGasLimit: 300_000_000_000, - MaxNumberOfBlocksToSkipStateSaving: 0, - MaxAmountOfGasToSkipStateSaving: 0, - StylusLRUCache: 0, - StateScheme: env.GetTestStateScheme(), + Archive: false, + BlockCount: 128, + BlockAge: 30 * time.Minute, + TrieTimeLimit: time.Hour, + TrieDirtyCache: 1024, + TrieCleanCache: 600, + SnapshotCache: 400, + DatabaseCache: 2048, + SnapshotRestoreGasLimit: 300_000_000_000, + MaxNumberOfBlocksToSkipStateSaving: 0, + MaxAmountOfGasToSkipStateSaving: 0, + StylusLRUCacheCapacity: 0, + DisableStylusCacheMetricsCollection: true, + StateScheme: env.GetTestStateScheme(), } var DefaultTestForwarderConfig = gethexec.ForwarderConfig{ @@ -233,21 +236,74 @@ type NodeBuilder struct { l1StackConfig *node.Config l2StackConfig *node.Config valnodeConfig *valnode.Config + l3Config *NitroConfig L1Info info L2Info info + L3Info info - // L1, L2 Node parameters + // L1, L2, L3 Node parameters dataDir string isSequencer bool takeOwnership bool withL1 bool addresses *chaininfo.RollupAddresses + l3Addresses *chaininfo.RollupAddresses initMessage *arbostypes.ParsedInitMessage + l3InitMessage *arbostypes.ParsedInitMessage withProdConfirmPeriodBlocks bool + wasmCacheTag uint32 // Created nodes L1 *TestClient L2 *TestClient + L3 *TestClient +} + +type NitroConfig struct { + chainConfig *params.ChainConfig + nodeConfig *arbnode.Config + execConfig *gethexec.Config + stackConfig *node.Config + valnodeConfig *valnode.Config + + withProdConfirmPeriodBlocks bool + isSequencer bool +} + +func L3NitroConfigDefaultTest(t *testing.T) *NitroConfig { + chainConfig := ¶ms.ChainConfig{ + ChainID: big.NewInt(333333), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArbitrumChainParams: params.ArbitrumDevTestParams(), + Clique: ¶ms.CliqueConfig{ + Period: 0, + Epoch: 0, + }, + } + + valnodeConfig := valnode.TestValidationConfig + return &NitroConfig{ + chainConfig: chainConfig, + nodeConfig: arbnode.ConfigDefaultL1Test(), + execConfig: ExecConfigDefaultTest(t), + stackConfig: testhelpers.CreateStackConfigForTest(t.TempDir()), + valnodeConfig: &valnodeConfig, + + withProdConfirmPeriodBlocks: false, + isSequencer: true, + } } func NewNodeBuilder(ctx context.Context) *NodeBuilder { @@ -273,6 +329,7 @@ func (b *NodeBuilder) DefaultConfig(t *testing.T, withL1 bool) *NodeBuilder { cp := valnode.TestValidationConfig b.valnodeConfig = &cp b.execConfig = ExecConfigDefaultTest(t) + b.l3Config = L3NitroConfigDefaultTest(t) return b } @@ -298,6 +355,15 @@ func (b *NodeBuilder) WithExtraArchs(targets []string) *NodeBuilder { return b } +func (b *NodeBuilder) WithStylusLongTermCache(enabled bool) *NodeBuilder { + if enabled { + b.wasmCacheTag = 1 + } else { + b.wasmCacheTag = 0 + } + return b +} + func (b *NodeBuilder) Build(t *testing.T) func() { b.CheckConfig(t) if b.withL1 { @@ -337,64 +403,175 @@ func (b *NodeBuilder) BuildL1(t *testing.T) { b.L1Info, b.L1.Client, b.L1.L1Backend, b.L1.Stack = createTestL1BlockChain(t, b.L1Info) locator, err := server_common.NewMachineLocator(b.valnodeConfig.Wasm.RootPath) Require(t, err) - b.addresses, b.initMessage = DeployOnTestL1(t, b.ctx, b.L1Info, b.L1.Client, b.chainConfig, locator.LatestWasmModuleRoot(), b.withProdConfirmPeriodBlocks) + b.addresses, b.initMessage = deployOnParentChain( + t, + b.ctx, + b.L1Info, + b.L1.Client, + &headerreader.TestConfig, + b.chainConfig, + locator.LatestWasmModuleRoot(), + b.withProdConfirmPeriodBlocks, + true, + ) b.L1.cleanup = func() { requireClose(t, b.L1.Stack) } } -func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { - if b.L1 == nil { - t.Fatal("must build L1 before building L2") +func buildOnParentChain( + t *testing.T, + ctx context.Context, + + dataDir string, + + parentChainInfo info, + parentChainTestClient *TestClient, + parentChainId *big.Int, + + chainConfig *params.ChainConfig, + stackConfig *node.Config, + execConfig *gethexec.Config, + nodeConfig *arbnode.Config, + valnodeConfig *valnode.Config, + isSequencer bool, + chainInfo info, + + initMessage *arbostypes.ParsedInitMessage, + addresses *chaininfo.RollupAddresses, + + wasmCacheTag uint32, +) *TestClient { + if parentChainTestClient == nil { + t.Fatal("must build parent chain before building chain") } - b.L2 = NewTestClient(b.ctx) - var l2chainDb ethdb.Database - var l2arbDb ethdb.Database - var l2blockchain *core.BlockChain - _, b.L2.Stack, l2chainDb, l2arbDb, l2blockchain = createL2BlockChainWithStackConfig( - t, b.L2Info, b.dataDir, b.chainConfig, b.initMessage, b.l2StackConfig, b.execConfig) + chainTestClient := NewTestClient(ctx) + + var chainDb ethdb.Database + var arbDb ethdb.Database + var blockchain *core.BlockChain + _, chainTestClient.Stack, chainDb, arbDb, blockchain = createNonL1BlockChainWithStackConfig( + t, chainInfo, dataDir, chainConfig, initMessage, stackConfig, execConfig, wasmCacheTag) var sequencerTxOptsPtr *bind.TransactOpts var dataSigner signature.DataSignerFunc - if b.isSequencer { - sequencerTxOpts := b.L1Info.GetDefaultTransactOpts("Sequencer", b.ctx) + if isSequencer { + sequencerTxOpts := parentChainInfo.GetDefaultTransactOpts("Sequencer", ctx) sequencerTxOptsPtr = &sequencerTxOpts - dataSigner = signature.DataSignerFromPrivateKey(b.L1Info.GetInfoWithPrivKey("Sequencer").PrivateKey) + dataSigner = signature.DataSignerFromPrivateKey(parentChainInfo.GetInfoWithPrivKey("Sequencer").PrivateKey) } else { - b.nodeConfig.BatchPoster.Enable = false - b.nodeConfig.Sequencer = false - b.nodeConfig.DelayedSequencer.Enable = false - b.execConfig.Sequencer.Enable = false + nodeConfig.BatchPoster.Enable = false + nodeConfig.Sequencer = false + nodeConfig.DelayedSequencer.Enable = false + execConfig.Sequencer.Enable = false } var validatorTxOptsPtr *bind.TransactOpts - if b.nodeConfig.Staker.Enable { - validatorTxOpts := b.L1Info.GetDefaultTransactOpts("Validator", b.ctx) + if nodeConfig.Staker.Enable { + validatorTxOpts := parentChainInfo.GetDefaultTransactOpts("Validator", ctx) validatorTxOptsPtr = &validatorTxOpts } - AddValNodeIfNeeded(t, b.ctx, b.nodeConfig, true, "", b.valnodeConfig.Wasm.RootPath) + AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", valnodeConfig.Wasm.RootPath) - Require(t, b.execConfig.Validate()) - execConfig := b.execConfig - execConfigFetcher := func() *gethexec.Config { return execConfig } - execNode, err := gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, l2chainDb, l2blockchain, b.L1.Client, execConfigFetcher) + Require(t, execConfig.Validate()) + execConfigToBeUsedInConfigFetcher := execConfig + execConfigFetcher := func() *gethexec.Config { return execConfigToBeUsedInConfigFetcher } + execNode, err := gethexec.CreateExecutionNode(ctx, chainTestClient.Stack, chainDb, blockchain, parentChainTestClient.Client, execConfigFetcher) Require(t, err) fatalErrChan := make(chan error, 10) - b.L2.ConsensusNode, err = arbnode.CreateNode( - b.ctx, b.L2.Stack, execNode, l2arbDb, NewFetcherFromConfig(b.nodeConfig), l2blockchain.Config(), b.L1.Client, - b.addresses, validatorTxOptsPtr, sequencerTxOptsPtr, dataSigner, fatalErrChan, big.NewInt(1337), nil) + chainTestClient.ConsensusNode, err = arbnode.CreateNode( + ctx, chainTestClient.Stack, execNode, arbDb, NewFetcherFromConfig(nodeConfig), blockchain.Config(), parentChainTestClient.Client, + addresses, validatorTxOptsPtr, sequencerTxOptsPtr, dataSigner, fatalErrChan, parentChainId, nil) Require(t, err) - err = b.L2.ConsensusNode.Start(b.ctx) + err = chainTestClient.ConsensusNode.Start(ctx) Require(t, err) - b.L2.Client = ClientForStack(t, b.L2.Stack) + chainTestClient.Client = ClientForStack(t, chainTestClient.Stack) - StartWatchChanErr(t, b.ctx, fatalErrChan, b.L2.ConsensusNode) + StartWatchChanErr(t, ctx, fatalErrChan, chainTestClient.ConsensusNode) + + chainTestClient.ExecNode = getExecNode(t, chainTestClient.ConsensusNode) + chainTestClient.cleanup = func() { chainTestClient.ConsensusNode.StopAndWait() } + + return chainTestClient +} + +func (b *NodeBuilder) BuildL3OnL2(t *testing.T) func() { + b.L3Info = NewArbTestInfo(t, b.l3Config.chainConfig.ChainID) + + locator, err := server_common.NewMachineLocator(b.l3Config.valnodeConfig.Wasm.RootPath) + Require(t, err) + + parentChainReaderConfig := headerreader.TestConfig + parentChainReaderConfig.Dangerous.WaitForTxApprovalSafePoll = 0 + b.l3Addresses, b.l3InitMessage = deployOnParentChain( + t, + b.ctx, + b.L2Info, + b.L2.Client, + &parentChainReaderConfig, + b.l3Config.chainConfig, + locator.LatestWasmModuleRoot(), + b.l3Config.withProdConfirmPeriodBlocks, + false, + ) + + b.L3 = buildOnParentChain( + t, + b.ctx, + + b.dataDir, + + b.L2Info, + b.L2, + b.chainConfig.ChainID, + + b.l3Config.chainConfig, + b.l3Config.stackConfig, + b.l3Config.execConfig, + b.l3Config.nodeConfig, + b.l3Config.valnodeConfig, + b.l3Config.isSequencer, + b.L3Info, + + b.l3InitMessage, + b.l3Addresses, + + b.wasmCacheTag, + ) + + return func() { + b.L3.cleanup() + } +} + +func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { + b.L2 = buildOnParentChain( + t, + b.ctx, + + b.dataDir, + + b.L1Info, + b.L1, + big.NewInt(1337), + + b.chainConfig, + b.l2StackConfig, + b.execConfig, + b.nodeConfig, + b.valnodeConfig, + b.isSequencer, + b.L2Info, + + b.initMessage, + b.addresses, + + b.wasmCacheTag, + ) - b.L2.ExecNode = getExecNode(t, b.L2.ConsensusNode) - b.L2.cleanup = func() { b.L2.ConsensusNode.StopAndWait() } return func() { b.L2.cleanup() if b.L1 != nil && b.L1.cleanup != nil { @@ -414,7 +591,7 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { var arbDb ethdb.Database var blockchain *core.BlockChain b.L2Info, b.L2.Stack, chainDb, arbDb, blockchain = createL2BlockChain( - t, b.L2Info, b.dataDir, b.chainConfig, b.execConfig) + t, b.L2Info, b.dataDir, b.chainConfig, b.execConfig, b.wasmCacheTag) Require(t, b.execConfig.Validate()) execConfig := b.execConfig @@ -465,7 +642,7 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { } b.L2.cleanup() - l2info, stack, chainDb, arbDb, blockchain := createL2BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.initMessage, b.l2StackConfig, b.execConfig) + l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.initMessage, b.l2StackConfig, b.execConfig, b.wasmCacheTag) execConfigFetcher := func() *gethexec.Config { return b.execConfig } execNode, err := gethexec.CreateExecutionNode(b.ctx, stack, chainDb, blockchain, nil, execConfigFetcher) @@ -490,13 +667,25 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { b.L2Info = l2info } -func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*TestClient, func()) { - if b.L2 == nil { - t.Fatal("builder did not previously build a L2 Node") - } - if b.withL1 && b.L1 == nil { - t.Fatal("builder did not previously build a L1 Node") - } +func build2ndNode( + t *testing.T, + ctx context.Context, + + firstNodeStackConfig *node.Config, + firsNodeExecConfig *gethexec.Config, + firstNodeNodeConfig *arbnode.Config, + firstNodeInfo info, + firstNodeTestClient *TestClient, + valnodeConfig *valnode.Config, + + parentChainTestClient *TestClient, + parentChainInfo info, + + params *SecondNodeParams, + + addresses *chaininfo.RollupAddresses, + initMessage *arbostypes.ParsedInitMessage, +) (*TestClient, func()) { if params.nodeConfig == nil { params.nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() } @@ -504,18 +693,18 @@ func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*Tes params.nodeConfig.DataAvailability = *params.dasConfig } if params.stackConfig == nil { - params.stackConfig = b.l2StackConfig + params.stackConfig = firstNodeStackConfig // should use different dataDir from the previously used ones params.stackConfig.DataDir = t.TempDir() } if params.initData == nil { - params.initData = &b.L2Info.ArbInitData + params.initData = &firstNodeInfo.ArbInitData } if params.execConfig == nil { - params.execConfig = b.execConfig + params.execConfig = firsNodeExecConfig } if params.addresses == nil { - params.addresses = b.addresses + params.addresses = addresses } if params.execConfig.RPC.MaxRecreateStateDepth == arbitrum.UninitializedMaxRecreateStateDepth { if params.execConfig.Caching.Archive { @@ -524,42 +713,98 @@ func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*Tes params.execConfig.RPC.MaxRecreateStateDepth = arbitrum.DefaultNonArchiveNodeMaxRecreateStateDepth } } - if b.nodeConfig.BatchPoster.Enable && params.nodeConfig.BatchPoster.Enable && params.nodeConfig.BatchPoster.RedisUrl == "" { + if firstNodeNodeConfig.BatchPoster.Enable && params.nodeConfig.BatchPoster.Enable && params.nodeConfig.BatchPoster.RedisUrl == "" { t.Fatal("The batch poster must use Redis when enabled for multiple nodes") } - l2 := NewTestClient(b.ctx) - l2.Client, l2.ConsensusNode = - Create2ndNodeWithConfig(t, b.ctx, b.L2.ConsensusNode, b.L1.Stack, b.L1Info, params.initData, params.nodeConfig, params.execConfig, params.stackConfig, b.valnodeConfig, params.addresses, b.initMessage) - l2.ExecNode = getExecNode(t, l2.ConsensusNode) - l2.cleanup = func() { l2.ConsensusNode.StopAndWait() } - return l2, func() { l2.cleanup() } + testClient := NewTestClient(ctx) + testClient.Client, testClient.ConsensusNode = + Create2ndNodeWithConfig(t, ctx, firstNodeTestClient.ConsensusNode, parentChainTestClient.Stack, parentChainInfo, params.initData, params.nodeConfig, params.execConfig, params.stackConfig, valnodeConfig, params.addresses, initMessage, params.wasmCacheTag) + testClient.ExecNode = getExecNode(t, testClient.ConsensusNode) + testClient.cleanup = func() { testClient.ConsensusNode.StopAndWait() } + return testClient, func() { testClient.cleanup() } +} + +func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*TestClient, func()) { + if b.L2 == nil { + t.Fatal("builder did not previously built an L2 Node") + } + if b.withL1 && b.L1 == nil { + t.Fatal("builder did not previously built an L1 Node") + } + return build2ndNode( + t, + b.ctx, + + b.l2StackConfig, + b.execConfig, + b.nodeConfig, + b.L2Info, + b.L2, + b.valnodeConfig, + + b.L1, + b.L1Info, + + params, + + b.addresses, + b.initMessage, + ) +} + +func (b *NodeBuilder) Build2ndNodeOnL3(t *testing.T, params *SecondNodeParams) (*TestClient, func()) { + if b.L3 == nil { + t.Fatal("builder did not previously built an L3 Node") + } + return build2ndNode( + t, + b.ctx, + + b.l3Config.stackConfig, + b.l3Config.execConfig, + b.l3Config.nodeConfig, + b.L3Info, + b.L3, + b.l3Config.valnodeConfig, + + b.L2, + b.L2Info, + + params, + + b.l3Addresses, + b.l3InitMessage, + ) } func (b *NodeBuilder) BridgeBalance(t *testing.T, account string, amount *big.Int) (*types.Transaction, *types.Receipt) { return BridgeBalance(t, account, amount, b.L1Info, b.L2Info, b.L1.Client, b.L2.Client, b.ctx) } -func SendWaitTestTransactions(t *testing.T, ctx context.Context, client client, txs []*types.Transaction) { +func SendWaitTestTransactions(t *testing.T, ctx context.Context, client *ethclient.Client, txs []*types.Transaction) []*types.Receipt { t.Helper() + receipts := make([]*types.Receipt, len(txs)) for _, tx := range txs { Require(t, client.SendTransaction(ctx, tx)) } - for _, tx := range txs { - _, err := EnsureTxSucceeded(ctx, client, tx) + for i, tx := range txs { + var err error + receipts[i], err = EnsureTxSucceeded(ctx, client, tx) Require(t, err) } + return receipts } func TransferBalance( - t *testing.T, from, to string, amount *big.Int, l2info info, client client, ctx context.Context, + t *testing.T, from, to string, amount *big.Int, l2info info, client *ethclient.Client, ctx context.Context, ) (*types.Transaction, *types.Receipt) { t.Helper() return TransferBalanceTo(t, from, l2info.GetAddress(to), amount, l2info, client, ctx) } func TransferBalanceTo( - t *testing.T, from string, to common.Address, amount *big.Int, l2info info, client client, ctx context.Context, + t *testing.T, from string, to common.Address, amount *big.Int, l2info info, client *ethclient.Client, ctx context.Context, ) (*types.Transaction, *types.Receipt) { t.Helper() tx := l2info.PrepareTxTo(from, &to, l2info.TransferGas, amount, nil) @@ -572,7 +817,7 @@ func TransferBalanceTo( // if l2client is not nil - will wait until balance appears in l2 func BridgeBalance( - t *testing.T, account string, amount *big.Int, l1info info, l2info info, l1client client, l2client client, ctx context.Context, + t *testing.T, account string, amount *big.Int, l1info info, l2info info, l1client *ethclient.Client, l2client *ethclient.Client, ctx context.Context, ) (*types.Transaction, *types.Receipt) { t.Helper() @@ -618,7 +863,7 @@ func BridgeBalance( break } TransferBalance(t, "Faucet", "User", big.NewInt(1), l1info, l1client, ctx) - if i > 20 { + if i > 200 { Fatal(t, "bridging failed") } <-time.After(time.Millisecond * 100) @@ -632,8 +877,8 @@ func SendSignedTxesInBatchViaL1( t *testing.T, ctx context.Context, l1info *BlockchainTestInfo, - l1client arbutil.L1Interface, - l2client arbutil.L1Interface, + l1client *ethclient.Client, + l2client *ethclient.Client, delayedTxes types.Transactions, ) types.Receipts { delayedInboxContract, err := bridgegen.NewInbox(l1info.GetAddress("Inbox"), l1client) @@ -683,8 +928,8 @@ func SendSignedTxViaL1( t *testing.T, ctx context.Context, l1info *BlockchainTestInfo, - l1client arbutil.L1Interface, - l2client arbutil.L1Interface, + l1client *ethclient.Client, + l2client *ethclient.Client, delayedTx *types.Transaction, ) *types.Receipt { delayedInboxContract, err := bridgegen.NewInbox(l1info.GetAddress("Inbox"), l1client) @@ -714,8 +959,8 @@ func SendUnsignedTxViaL1( t *testing.T, ctx context.Context, l1info *BlockchainTestInfo, - l1client arbutil.L1Interface, - l2client arbutil.L1Interface, + l1client *ethclient.Client, + l2client *ethclient.Client, templateTx *types.Transaction, ) *types.Receipt { delayedInboxContract, err := bridgegen.NewInbox(l1info.GetAddress("Inbox"), l1client) @@ -761,13 +1006,13 @@ func SendUnsignedTxViaL1( return receipt } -func GetBaseFee(t *testing.T, client client, ctx context.Context) *big.Int { +func GetBaseFee(t *testing.T, client *ethclient.Client, ctx context.Context) *big.Int { header, err := client.HeaderByNumber(ctx, nil) Require(t, err) return header.BaseFee } -func GetBaseFeeAt(t *testing.T, client client, ctx context.Context, blockNum *big.Int) *big.Int { +func GetBaseFeeAt(t *testing.T, client *ethclient.Client, ctx context.Context, blockNum *big.Int) *big.Int { header, err := client.HeaderByNumber(ctx, blockNum) Require(t, err) return header.BaseFee @@ -989,8 +1234,8 @@ func createTestL1BlockChain(t *testing.T, l1info info) (info, *ethclient.Client, return l1info, l1Client, l1backend, stack } -func getInitMessage(ctx context.Context, t *testing.T, l1client client, addresses *chaininfo.RollupAddresses) *arbostypes.ParsedInitMessage { - bridge, err := arbnode.NewDelayedBridge(l1client, addresses.Bridge, addresses.DeployedAt) +func getInitMessage(ctx context.Context, t *testing.T, parentChainClient *ethclient.Client, addresses *chaininfo.RollupAddresses) *arbostypes.ParsedInitMessage { + bridge, err := arbnode.NewDelayedBridge(parentChainClient, addresses.Bridge, addresses.DeployedAt) Require(t, err) deployedAtBig := arbmath.UintToBig(addresses.DeployedAt) messages, err := bridge.LookupMessagesInRange(ctx, deployedAtBig, deployedAtBig, nil) @@ -1004,63 +1249,72 @@ func getInitMessage(ctx context.Context, t *testing.T, l1client client, addresse return initMessage } -func DeployOnTestL1( - t *testing.T, ctx context.Context, l1info info, l1client client, chainConfig *params.ChainConfig, wasmModuleRoot common.Hash, prodConfirmPeriodBlocks bool, +func deployOnParentChain( + t *testing.T, + ctx context.Context, + parentChainInfo info, + parentChainClient *ethclient.Client, + parentChainReaderConfig *headerreader.Config, + chainConfig *params.ChainConfig, + wasmModuleRoot common.Hash, + prodConfirmPeriodBlocks bool, + chainSupportsBlobs bool, ) (*chaininfo.RollupAddresses, *arbostypes.ParsedInitMessage) { - l1info.GenerateAccount("RollupOwner") - l1info.GenerateAccount("Sequencer") - l1info.GenerateAccount("Validator") - l1info.GenerateAccount("User") - - SendWaitTestTransactions(t, ctx, l1client, []*types.Transaction{ - l1info.PrepareTx("Faucet", "RollupOwner", 30000, big.NewInt(9223372036854775807), nil), - l1info.PrepareTx("Faucet", "Sequencer", 30000, big.NewInt(9223372036854775807), nil), - l1info.PrepareTx("Faucet", "Validator", 30000, big.NewInt(9223372036854775807), nil), - l1info.PrepareTx("Faucet", "User", 30000, big.NewInt(9223372036854775807), nil)}) - - l1TransactionOpts := l1info.GetDefaultTransactOpts("RollupOwner", ctx) + parentChainInfo.GenerateAccount("RollupOwner") + parentChainInfo.GenerateAccount("Sequencer") + parentChainInfo.GenerateAccount("Validator") + parentChainInfo.GenerateAccount("User") + + SendWaitTestTransactions(t, ctx, parentChainClient, []*types.Transaction{ + parentChainInfo.PrepareTx("Faucet", "RollupOwner", parentChainInfo.TransferGas, big.NewInt(9223372036854775807), nil), + parentChainInfo.PrepareTx("Faucet", "Sequencer", parentChainInfo.TransferGas, big.NewInt(9223372036854775807), nil), + parentChainInfo.PrepareTx("Faucet", "Validator", parentChainInfo.TransferGas, big.NewInt(9223372036854775807), nil), + parentChainInfo.PrepareTx("Faucet", "User", parentChainInfo.TransferGas, big.NewInt(9223372036854775807), nil)}) + + parentChainTransactionOpts := parentChainInfo.GetDefaultTransactOpts("RollupOwner", ctx) serializedChainConfig, err := json.Marshal(chainConfig) Require(t, err) - arbSys, _ := precompilesgen.NewArbSys(types.ArbSysAddress, l1client) - l1Reader, err := headerreader.New(ctx, l1client, func() *headerreader.Config { return &headerreader.TestConfig }, arbSys) + arbSys, _ := precompilesgen.NewArbSys(types.ArbSysAddress, parentChainClient) + parentChainReader, err := headerreader.New(ctx, parentChainClient, func() *headerreader.Config { return parentChainReaderConfig }, arbSys) Require(t, err) - l1Reader.Start(ctx) - defer l1Reader.StopAndWait() + parentChainReader.Start(ctx) + defer parentChainReader.StopAndWait() nativeToken := common.Address{} maxDataSize := big.NewInt(117964) - addresses, err := deploy.DeployOnL1( + addresses, err := deploy.DeployOnParentChain( ctx, - l1Reader, - &l1TransactionOpts, - []common.Address{l1info.GetAddress("Sequencer")}, - l1info.GetAddress("RollupOwner"), + parentChainReader, + &parentChainTransactionOpts, + []common.Address{parentChainInfo.GetAddress("Sequencer")}, + parentChainInfo.GetAddress("RollupOwner"), 0, - arbnode.GenerateRollupConfig(prodConfirmPeriodBlocks, wasmModuleRoot, l1info.GetAddress("RollupOwner"), chainConfig, serializedChainConfig, common.Address{}), + arbnode.GenerateRollupConfig(prodConfirmPeriodBlocks, wasmModuleRoot, parentChainInfo.GetAddress("RollupOwner"), chainConfig, serializedChainConfig, common.Address{}), nativeToken, maxDataSize, + chainSupportsBlobs, ) Require(t, err) - l1info.SetContract("Bridge", addresses.Bridge) - l1info.SetContract("SequencerInbox", addresses.SequencerInbox) - l1info.SetContract("Inbox", addresses.Inbox) - l1info.SetContract("UpgradeExecutor", addresses.UpgradeExecutor) - initMessage := getInitMessage(ctx, t, l1client, addresses) + parentChainInfo.SetContract("Bridge", addresses.Bridge) + parentChainInfo.SetContract("SequencerInbox", addresses.SequencerInbox) + parentChainInfo.SetContract("Inbox", addresses.Inbox) + parentChainInfo.SetContract("UpgradeExecutor", addresses.UpgradeExecutor) + initMessage := getInitMessage(ctx, t, parentChainClient, addresses) return addresses, initMessage } func createL2BlockChain( - t *testing.T, l2info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, execConfig *gethexec.Config, + t *testing.T, l2info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, execConfig *gethexec.Config, wasmCacheTag uint32, ) (*BlockchainTestInfo, *node.Node, ethdb.Database, ethdb.Database, *core.BlockChain) { - return createL2BlockChainWithStackConfig(t, l2info, dataDir, chainConfig, nil, nil, execConfig) + return createNonL1BlockChainWithStackConfig(t, l2info, dataDir, chainConfig, nil, nil, execConfig, wasmCacheTag) } -func createL2BlockChainWithStackConfig( - t *testing.T, l2info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, +func createNonL1BlockChainWithStackConfig( + t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, wasmCacheTag uint32, ) (*BlockchainTestInfo, *node.Node, ethdb.Database, ethdb.Database, *core.BlockChain) { - if l2info == nil { - l2info = NewArbTestInfo(t, chainConfig.ChainID) + if info == nil { + info = NewArbTestInfo(t, chainConfig.ChainID) } if stackConfig == nil { stackConfig = testhelpers.CreateStackConfigForTest(dataDir) @@ -1076,11 +1330,11 @@ func createL2BlockChainWithStackConfig( Require(t, err) wasmData, err := stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, 0, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, wasmCacheTag, execConfig.StylusTarget.WasmTargets()) arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) - initReader := statetransfer.NewMemoryInitDataReader(&l2info.ArbInitData) + initReader := statetransfer.NewMemoryInitDataReader(&info.ArbInitData) if initMessage == nil { serializedChainConfig, err := json.Marshal(chainConfig) Require(t, err) @@ -1095,7 +1349,7 @@ func createL2BlockChainWithStackConfig( blockchain, err := gethexec.WriteOrTestBlockChain(chainDb, coreCacheConfig, initReader, chainConfig, initMessage, ExecConfigDefaultTest(t).TxLookupLimit, 0) Require(t, err) - return l2info, stack, chainDb, arbDb, blockchain + return info, stack, chainDb, arbDb, blockchain } func ClientForStack(t *testing.T, backend *node.Node) *ethclient.Client { @@ -1138,15 +1392,16 @@ func Create2ndNodeWithConfig( t *testing.T, ctx context.Context, first *arbnode.Node, - l1stack *node.Node, - l1info *BlockchainTestInfo, - l2InitData *statetransfer.ArbosInitializationInfo, + parentChainStack *node.Node, + parentChainInfo *BlockchainTestInfo, + chainInitData *statetransfer.ArbosInitializationInfo, nodeConfig *arbnode.Config, execConfig *gethexec.Config, stackConfig *node.Config, valnodeConfig *valnode.Config, addresses *chaininfo.RollupAddresses, initMessage *arbostypes.ParsedInitMessage, + wasmCacheTag uint32, ) (*ethclient.Client, *arbnode.Node) { if nodeConfig == nil { nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() @@ -1155,34 +1410,34 @@ func Create2ndNodeWithConfig( execConfig = ExecConfigDefaultNonSequencerTest(t) } feedErrChan := make(chan error, 10) - l1rpcClient := l1stack.Attach() - l1client := ethclient.NewClient(l1rpcClient) + parentChainRpcClient := parentChainStack.Attach() + parentChainClient := ethclient.NewClient(parentChainRpcClient) if stackConfig == nil { stackConfig = testhelpers.CreateStackConfigForTest(t.TempDir()) } - l2stack, err := node.New(stackConfig) + chainStack, err := node.New(stackConfig) Require(t, err) - l2chainData, err := l2stack.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) + chainData, err := chainStack.OpenDatabaseWithExtraOptions("l2chaindata", 0, 0, "l2chaindata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("l2chaindata")) Require(t, err) - wasmData, err := l2stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) + wasmData, err := chainStack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - l2chainDb := rawdb.WrapDatabaseWithWasm(l2chainData, wasmData, 0, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, wasmCacheTag, execConfig.StylusTarget.WasmTargets()) - l2arbDb, err := l2stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) + arbDb, err := chainStack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) - initReader := statetransfer.NewMemoryInitDataReader(l2InitData) + initReader := statetransfer.NewMemoryInitDataReader(chainInitData) - dataSigner := signature.DataSignerFromPrivateKey(l1info.GetInfoWithPrivKey("Sequencer").PrivateKey) - sequencerTxOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) - validatorTxOpts := l1info.GetDefaultTransactOpts("Validator", ctx) + dataSigner := signature.DataSignerFromPrivateKey(parentChainInfo.GetInfoWithPrivKey("Sequencer").PrivateKey) + sequencerTxOpts := parentChainInfo.GetDefaultTransactOpts("Sequencer", ctx) + validatorTxOpts := parentChainInfo.GetDefaultTransactOpts("Validator", ctx) firstExec := getExecNode(t, first) chainConfig := firstExec.ArbInterface.BlockChain().Config() - coreCacheConfig := gethexec.DefaultCacheConfigFor(l2stack, &execConfig.Caching) - l2blockchain, err := gethexec.WriteOrTestBlockChain(l2chainDb, coreCacheConfig, initReader, chainConfig, initMessage, ExecConfigDefaultTest(t).TxLookupLimit, 0) + coreCacheConfig := gethexec.DefaultCacheConfigFor(chainStack, &execConfig.Caching) + blockchain, err := gethexec.WriteOrTestBlockChain(chainDb, coreCacheConfig, initReader, chainConfig, initMessage, ExecConfigDefaultTest(t).TxLookupLimit, 0) Require(t, err) AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", valnodeConfig.Wasm.RootPath) @@ -1190,19 +1445,19 @@ func Create2ndNodeWithConfig( Require(t, execConfig.Validate()) Require(t, nodeConfig.Validate()) configFetcher := func() *gethexec.Config { return execConfig } - currentExec, err := gethexec.CreateExecutionNode(ctx, l2stack, l2chainDb, l2blockchain, l1client, configFetcher) + currentExec, err := gethexec.CreateExecutionNode(ctx, chainStack, chainDb, blockchain, parentChainClient, configFetcher) Require(t, err) - currentNode, err := arbnode.CreateNode(ctx, l2stack, currentExec, l2arbDb, NewFetcherFromConfig(nodeConfig), l2blockchain.Config(), l1client, addresses, &validatorTxOpts, &sequencerTxOpts, dataSigner, feedErrChan, big.NewInt(1337), nil) + currentNode, err := arbnode.CreateNode(ctx, chainStack, currentExec, arbDb, NewFetcherFromConfig(nodeConfig), blockchain.Config(), parentChainClient, addresses, &validatorTxOpts, &sequencerTxOpts, dataSigner, feedErrChan, big.NewInt(1337), nil) Require(t, err) err = currentNode.Start(ctx) Require(t, err) - l2client := ClientForStack(t, l2stack) + chainClient := ClientForStack(t, chainStack) StartWatchChanErr(t, ctx, feedErrChan, currentNode) - return l2client, currentNode + return chainClient, currentNode } func GetBalance(t *testing.T, ctx context.Context, client *ethclient.Client, account common.Address) *big.Int { @@ -1222,7 +1477,7 @@ func authorizeDASKeyset( ctx context.Context, dasSignerKey *blsSignatures.PublicKey, l1info info, - l1client arbutil.L1Interface, + l1client *ethclient.Client, ) { if dasSignerKey == nil { return @@ -1462,6 +1717,57 @@ func logParser[T any](t *testing.T, source string, name string) func(*types.Log) } } +var ( + recordBlockInputsEnable = flag.Bool("recordBlockInputs.enable", true, "Whether to record block inputs as a json file") + recordBlockInputsWithSlug = flag.String("recordBlockInputs.WithSlug", "", "Slug directory for validationInputsWriter") + recordBlockInputsWithBaseDir = flag.String("recordBlockInputs.WithBaseDir", "", "Base directory for validationInputsWriter") + recordBlockInputsWithTimestampDirEnabled = flag.Bool("recordBlockInputs.WithTimestampDirEnabled", true, "Whether to add timestamp directory while recording block inputs") + recordBlockInputsWithBlockIdInFileNameEnabled = flag.Bool("recordBlockInputs.WithBlockIdInFileNameEnabled", true, "Whether to record block inputs using test specific block_id") +) + +// recordBlock writes a json file with all of the data needed to validate a block. +// +// This can be used as an input to the arbitrator prover to validate a block. +func recordBlock(t *testing.T, block uint64, builder *NodeBuilder, targets ...ethdb.WasmTarget) { + t.Helper() + flag.Parse() + if !*recordBlockInputsEnable { + return + } + ctx := builder.ctx + inboxPos := arbutil.MessageIndex(block) + for { + time.Sleep(250 * time.Millisecond) + batches, err := builder.L2.ConsensusNode.InboxTracker.GetBatchCount() + Require(t, err) + haveMessages, err := builder.L2.ConsensusNode.InboxTracker.GetBatchMessageCount(batches - 1) + Require(t, err) + if haveMessages >= inboxPos { + break + } + } + var options []inputs.WriterOption + options = append(options, inputs.WithTimestampDirEnabled(*recordBlockInputsWithTimestampDirEnabled)) + options = append(options, inputs.WithBlockIdInFileNameEnabled(*recordBlockInputsWithBlockIdInFileNameEnabled)) + if *recordBlockInputsWithBaseDir != "" { + options = append(options, inputs.WithBaseDir(*recordBlockInputsWithBaseDir)) + } + if *recordBlockInputsWithSlug != "" { + options = append(options, inputs.WithSlug(*recordBlockInputsWithSlug)) + } else { + options = append(options, inputs.WithSlug(t.Name())) + } + validationInputsWriter, err := inputs.NewWriter(options...) + Require(t, err) + inputJson, err := builder.L2.ConsensusNode.StatelessBlockValidator.ValidationInputsAt(ctx, inboxPos, targets...) + if err != nil { + Fatal(t, "failed to get validation inputs", block, err) + } + if err := validationInputsWriter.Write(&inputJson); err != nil { + Fatal(t, "failed to write validation inputs", block, err) + } +} + func populateMachineDir(t *testing.T, cr *github.ConsensusRelease) string { baseDir := t.TempDir() machineDir := baseDir + "/machines" diff --git a/system_tests/das_test.go b/system_tests/das_test.go index 9f4d153b6f..ed3844d528 100644 --- a/system_tests/das_test.go +++ b/system_tests/das_test.go @@ -6,6 +6,7 @@ package arbtest import ( "context" "encoding/base64" + "errors" "io" "math/big" "net" @@ -22,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/das" @@ -37,25 +37,20 @@ func startLocalDASServer( t *testing.T, ctx context.Context, dataDir string, - l1client arbutil.L1Interface, + l1client *ethclient.Client, seqInboxAddress common.Address, ) (*http.Server, *blsSignatures.PublicKey, das.BackendConfig, *das.RestfulDasServer, string) { keyDir := t.TempDir() pubkey, _, err := das.GenerateAndStoreKeys(keyDir) Require(t, err) - config := das.DataAvailabilityConfig{ - Enable: true, - Key: das.KeyConfig{ - KeyDir: keyDir, - }, - LocalFileStorage: das.LocalFileStorageConfig{ - Enable: true, - DataDir: dataDir, - }, - ParentChainNodeURL: "none", - RequestTimeout: 5 * time.Second, - } + config := das.DefaultDataAvailabilityConfig + config.Enable = true + config.Key = das.KeyConfig{KeyDir: keyDir} + config.ParentChainNodeURL = "none" + config.LocalFileStorage = das.DefaultLocalFileStorageConfig + config.LocalFileStorage.Enable = true + config.LocalFileStorage.DataDir = dataDir storageService, lifecycleManager, err := das.CreatePersistentStorageService(ctx, &config) defer lifecycleManager.StopAndWaitUntil(time.Second) @@ -327,3 +322,80 @@ func initTest(t *testing.T) { enableLogging(logLvl) } } + +func TestDASBatchPosterFallback(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Setup L1 + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.chainConfig = params.ArbitrumDevTestDASChainConfig() + builder.BuildL1(t) + l1client := builder.L1.Client + l1info := builder.L1Info + + // Setup DAS server + dasDataDir := t.TempDir() + dasRpcServer, pubkey, backendConfig, _, restServerUrl := startLocalDASServer( + t, ctx, dasDataDir, l1client, builder.addresses.SequencerInbox) + authorizeDASKeyset(t, ctx, pubkey, l1info, l1client) + + // Setup sequence/batch-poster L2 node + builder.nodeConfig.DataAvailability.Enable = true + builder.nodeConfig.DataAvailability.RPCAggregator = aggConfigForBackend(backendConfig) + builder.nodeConfig.DataAvailability.RestAggregator = das.DefaultRestfulClientAggregatorConfig + builder.nodeConfig.DataAvailability.RestAggregator.Enable = true + builder.nodeConfig.DataAvailability.RestAggregator.Urls = []string{restServerUrl} + builder.nodeConfig.DataAvailability.ParentChainNodeURL = "none" + builder.nodeConfig.BatchPoster.DisableDapFallbackStoreDataOnChain = true // Disable DAS fallback + builder.nodeConfig.BatchPoster.ErrorDelay = time.Millisecond * 250 // Increase error delay because we expect errors + builder.L2Info = NewArbTestInfo(t, builder.chainConfig.ChainID) + builder.L2Info.GenerateAccount("User2") + cleanup := builder.BuildL2OnL1(t) + defer cleanup() + l2client := builder.L2.Client + l2info := builder.L2Info + + // Setup secondary L2 node + nodeConfigB := arbnode.ConfigDefaultL1NonSequencerTest() + nodeConfigB.BlockValidator.Enable = false + nodeConfigB.DataAvailability.Enable = true + nodeConfigB.DataAvailability.RestAggregator = das.DefaultRestfulClientAggregatorConfig + nodeConfigB.DataAvailability.RestAggregator.Enable = true + nodeConfigB.DataAvailability.RestAggregator.Urls = []string{restServerUrl} + nodeConfigB.DataAvailability.ParentChainNodeURL = "none" + nodeBParams := SecondNodeParams{ + nodeConfig: nodeConfigB, + initData: &l2info.ArbInitData, + } + l2B, cleanupB := builder.Build2ndNode(t, &nodeBParams) + defer cleanupB() + + // Check batch posting using the DAS + checkBatchPosting(t, ctx, l1client, l2client, l1info, l2info, big.NewInt(1e12), l2B.Client) + + // Shutdown the DAS + err := dasRpcServer.Shutdown(ctx) + Require(t, err) + + // Send 2nd transaction and check it doesn't arrive on second node + tx, _ := TransferBalanceTo(t, "Owner", l2info.GetAddress("User2"), big.NewInt(1e12), l2info, l2client, ctx) + _, err = WaitForTx(ctx, l2B.Client, tx.Hash(), time.Second*3) + if err == nil || !errors.Is(err, context.DeadlineExceeded) { + Fatal(t, "expected context-deadline exceeded error, but got:", err) + } + + // Enable the DAP fallback and check the transaction on the second node. + // (We don't need to restart the node because of the hot-reload.) + builder.nodeConfig.BatchPoster.DisableDapFallbackStoreDataOnChain = false + _, err = WaitForTx(ctx, l2B.Client, tx.Hash(), time.Second*3) + Require(t, err) + l2balance, err := l2B.Client.BalanceAt(ctx, l2info.GetAddress("User2"), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(2e12)) != 0 { + Fatal(t, "Unexpected balance:", l2balance) + } + + // Send another transaction with fallback on + checkBatchPosting(t, ctx, l1client, l2client, l1info, l2info, big.NewInt(3e12), l2B.Client) +} diff --git a/system_tests/eth_sync_test.go b/system_tests/eth_sync_test.go index 1f07f7c45f..ce9994fb1e 100644 --- a/system_tests/eth_sync_test.go +++ b/system_tests/eth_sync_test.go @@ -71,7 +71,7 @@ func TestEthSyncing(t *testing.T) { if progress == nil { Fatal(t, "eth_syncing returned nil but shouldn't have") } - for testClientB.ConsensusNode.TxStreamer.ExecuteNextMsg(ctx, testClientB.ExecNode) { + for testClientB.ConsensusNode.TxStreamer.ExecuteNextMsg(ctx) { } progress, err = testClientB.Client.SyncProgress(ctx) Require(t, err) diff --git a/system_tests/forwarder_test.go b/system_tests/forwarder_test.go index 57381ca84e..6a1d1c68d8 100644 --- a/system_tests/forwarder_test.go +++ b/system_tests/forwarder_test.go @@ -170,7 +170,7 @@ func waitForSequencerLockout(ctx context.Context, node *arbnode.Node, duration t case <-time.After(duration): return fmt.Errorf("no sequencer was chosen") default: - if c, err := node.SeqCoordinator.CurrentChosenSequencer(ctx); err == nil && c != "" { + if c, err := node.SeqCoordinator.RedisCoordinator().CurrentChosenSequencer(ctx); err == nil && c != "" { return nil } time.Sleep(100 * time.Millisecond) diff --git a/system_tests/full_challenge_impl_test.go b/system_tests/full_challenge_impl_test.go index ddc229074c..bf30c928d8 100644 --- a/system_tests/full_challenge_impl_test.go +++ b/system_tests/full_challenge_impl_test.go @@ -27,7 +27,6 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbstate" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/solgen/go/challengegen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/ospgen" @@ -178,7 +177,7 @@ func makeBatch(t *testing.T, l2Node *arbnode.Node, l2Info *BlockchainTestInfo, b Require(t, err, "failed to get batch metadata after adding batch:") } -func confirmLatestBlock(ctx context.Context, t *testing.T, l1Info *BlockchainTestInfo, backend arbutil.L1Interface) { +func confirmLatestBlock(ctx context.Context, t *testing.T, l1Info *BlockchainTestInfo, backend *ethclient.Client) { t.Helper() // With SimulatedBeacon running in on-demand block production mode, the // finalized block is considered to be be the nearest multiple of 32 less @@ -190,7 +189,7 @@ func confirmLatestBlock(ctx context.Context, t *testing.T, l1Info *BlockchainTes } } -func setupSequencerInboxStub(ctx context.Context, t *testing.T, l1Info *BlockchainTestInfo, l1Client arbutil.L1Interface, chainConfig *params.ChainConfig) (common.Address, *mocksgen.SequencerInboxStub, common.Address) { +func setupSequencerInboxStub(ctx context.Context, t *testing.T, l1Info *BlockchainTestInfo, l1Client *ethclient.Client, chainConfig *params.ChainConfig) (common.Address, *mocksgen.SequencerInboxStub, common.Address) { txOpts := l1Info.GetDefaultTransactOpts("deployer", ctx) bridgeAddr, tx, bridge, err := mocksgen.DeployBridgeUnproxied(&txOpts, l1Client) Require(t, err) diff --git a/system_tests/l3_test.go b/system_tests/l3_test.go new file mode 100644 index 0000000000..97eabcee78 --- /dev/null +++ b/system_tests/l3_test.go @@ -0,0 +1,53 @@ +package arbtest + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/offchainlabs/nitro/arbnode" +) + +func TestSimpleL3(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + cleanupL1AndL2 := builder.Build(t) + defer cleanupL1AndL2() + + cleanupL3FirstNode := builder.BuildL3OnL2(t) + defer cleanupL3FirstNode() + firstNodeTestClient := builder.L3 + + secondNodeNodeConfig := arbnode.ConfigDefaultL1NonSequencerTest() + secondNodeTestClient, cleanupL3SecondNode := builder.Build2ndNodeOnL3(t, &SecondNodeParams{nodeConfig: secondNodeNodeConfig}) + defer cleanupL3SecondNode() + + accountName := "User2" + builder.L3Info.GenerateAccount(accountName) + tx := builder.L3Info.PrepareTx("Owner", accountName, builder.L3Info.TransferGas, big.NewInt(1e12), nil) + + err := firstNodeTestClient.Client.SendTransaction(ctx, tx) + Require(t, err) + + // Checks that first node has the correct balance + _, err = firstNodeTestClient.EnsureTxSucceeded(tx) + Require(t, err) + l2balance, err := firstNodeTestClient.Client.BalanceAt(ctx, builder.L3Info.GetAddress(accountName), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(1e12)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } + + // Checks that second node has the correct balance + _, err = WaitForTx(ctx, secondNodeTestClient.Client, tx.Hash(), time.Second*15) + Require(t, err) + l2balance, err = secondNodeTestClient.Client.BalanceAt(ctx, builder.L3Info.GetAddress(accountName), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(1e12)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } +} diff --git a/system_tests/program_gas_test.go b/system_tests/program_gas_test.go new file mode 100644 index 0000000000..119897cbfe --- /dev/null +++ b/system_tests/program_gas_test.go @@ -0,0 +1,458 @@ +package arbtest + +import ( + "context" + "fmt" + "math" + "math/big" + "regexp" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +func TestProgramSimpleCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) + evmProgram := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.HostioTestMetaData) + otherProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("storage")) + matchSnake := regexp.MustCompile("_[a-z]") + + for _, tc := range []struct { + hostio string + opcode vm.OpCode + params []any + maxDiff float64 + }{ + {hostio: "exit_early", opcode: vm.STOP}, + {hostio: "transient_load_bytes32", opcode: vm.TLOAD, params: []any{common.HexToHash("dead")}}, + {hostio: "transient_store_bytes32", opcode: vm.TSTORE, params: []any{common.HexToHash("dead"), common.HexToHash("beef")}}, + {hostio: "return_data_size", opcode: vm.RETURNDATASIZE, maxDiff: 1.5}, + {hostio: "account_balance", opcode: vm.BALANCE, params: []any{builder.L2Info.GetAddress("Owner")}}, + {hostio: "account_code", opcode: vm.EXTCODECOPY, params: []any{otherProgram}}, + {hostio: "account_code_size", opcode: vm.EXTCODESIZE, params: []any{otherProgram}, maxDiff: 0.3}, + {hostio: "account_codehash", opcode: vm.EXTCODEHASH, params: []any{otherProgram}}, + {hostio: "evm_gas_left", opcode: vm.GAS, maxDiff: 1.5}, + {hostio: "evm_ink_left", opcode: vm.GAS, maxDiff: 1.5}, + {hostio: "block_basefee", opcode: vm.BASEFEE, maxDiff: 0.5}, + {hostio: "chainid", opcode: vm.CHAINID, maxDiff: 1.5}, + {hostio: "block_coinbase", opcode: vm.COINBASE, maxDiff: 0.5}, + {hostio: "block_gas_limit", opcode: vm.GASLIMIT, maxDiff: 1.5}, + {hostio: "block_number", opcode: vm.NUMBER, maxDiff: 1.5}, + {hostio: "block_timestamp", opcode: vm.TIMESTAMP, maxDiff: 1.5}, + {hostio: "contract_address", opcode: vm.ADDRESS, maxDiff: 0.5}, + {hostio: "math_div", opcode: vm.DIV, params: []any{big.NewInt(1), big.NewInt(3)}}, + {hostio: "math_mod", opcode: vm.MOD, params: []any{big.NewInt(1), big.NewInt(3)}}, + {hostio: "math_add_mod", opcode: vm.ADDMOD, params: []any{big.NewInt(1), big.NewInt(3), big.NewInt(5)}, maxDiff: 0.7}, + {hostio: "math_mul_mod", opcode: vm.MULMOD, params: []any{big.NewInt(1), big.NewInt(3), big.NewInt(5)}, maxDiff: 0.7}, + {hostio: "msg_sender", opcode: vm.CALLER, maxDiff: 0.5}, + {hostio: "msg_value", opcode: vm.CALLVALUE, maxDiff: 0.5}, + {hostio: "tx_gas_price", opcode: vm.GASPRICE, maxDiff: 0.5}, + {hostio: "tx_ink_price", opcode: vm.GASPRICE, maxDiff: 1.5}, + {hostio: "tx_origin", opcode: vm.ORIGIN, maxDiff: 0.5}, + } { + t.Run(tc.hostio, func(t *testing.T) { + solFunc := matchSnake.ReplaceAllStringFunc(tc.hostio, func(s string) string { + return strings.ToUpper(strings.TrimPrefix(s, "_")) + }) + packer, _ := util.NewCallParser(mocksgen.HostioTestABI, solFunc) + data, err := packer(tc.params...) + Require(t, err) + compareGasUsage(t, builder, evmProgram, stylusProgram, data, nil, compareGasForEach, tc.maxDiff, compareGasPair{tc.opcode, tc.hostio}) + }) + } +} + +func TestProgramPowCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) + evmProgram := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.HostioTestMetaData) + packer, _ := util.NewCallParser(mocksgen.HostioTestABI, "mathPow") + + for _, exponentNumBytes := range []uint{1, 2, 10, 32} { + name := fmt.Sprintf("exponentNumBytes%v", exponentNumBytes) + t.Run(name, func(t *testing.T) { + exponent := new(big.Int).Lsh(big.NewInt(1), exponentNumBytes*8-1) + params := []any{big.NewInt(1), exponent} + data, err := packer(params...) + Require(t, err) + evmGasUsage, stylusGasUsage := measureGasUsage(t, builder, evmProgram, stylusProgram, data, nil) + expectedGas := 2.652 + 1.75*float64(exponentNumBytes+1) + t.Logf("evm EXP usage: %v - stylus math_pow usage: %v - expected math_pow usage: %v", + evmGasUsage[vm.EXP][0], stylusGasUsage["math_pow"][0], expectedGas) + // The math_pow HostIO uses significally less gas than the EXP opcode. So, + // instead of comparing it to EVM, we compare it to the expected gas usage + // for each test case. + checkPercentDiff(t, stylusGasUsage["math_pow"][0], expectedGas, 0.001) + }) + } +} + +func TestProgramStorageCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusMulticall := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("multicall")) + evmMulticall := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.MultiCallTestMetaData) + + const numSlots = 42 + rander := testhelpers.NewPseudoRandomDataSource(t, 0) + readData := multicallEmptyArgs() + writeRandAData := multicallEmptyArgs() + writeRandBData := multicallEmptyArgs() + writeZeroData := multicallEmptyArgs() + for i := 0; i < numSlots; i++ { + slot := rander.GetHash() + readData = multicallAppendLoad(readData, slot, false) + writeRandAData = multicallAppendStore(writeRandAData, slot, rander.GetHash(), false) + writeRandBData = multicallAppendStore(writeRandBData, slot, rander.GetHash(), false) + writeZeroData = multicallAppendStore(writeZeroData, slot, common.Hash{}, false) + } + + for _, tc := range []struct { + name string + data []byte + }{ + {"initialWrite", writeRandAData}, + {"read", readData}, + {"writeAgain", writeRandBData}, + {"delete", writeZeroData}, + {"readZeros", readData}, + {"writeAgainAgain", writeRandAData}, + } { + t.Run(tc.name, func(t *testing.T) { + compareGasUsage(t, builder, evmMulticall, stylusMulticall, tc.data, nil, compareGasSum, 0, + compareGasPair{vm.SSTORE, "storage_flush_cache"}, compareGasPair{vm.SLOAD, "storage_load_bytes32"}) + }) + } +} + +func TestProgramLogCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) + evmProgram := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.HostioTestMetaData) + packer, _ := util.NewCallParser(mocksgen.HostioTestABI, "emitLog") + + for ntopics := int8(0); ntopics < 5; ntopics++ { + for _, dataSize := range []uint64{10, 100, 1000} { + name := fmt.Sprintf("emitLog%dData%d", ntopics, dataSize) + t.Run(name, func(t *testing.T) { + args := []any{ + testhelpers.RandomSlice(dataSize), + ntopics, + } + for t := 0; t < 4; t++ { + args = append(args, testhelpers.RandomHash()) + } + data, err := packer(args...) + Require(t, err) + opcode := vm.LOG0 + vm.OpCode(ntopics) + compareGasUsage(t, builder, evmProgram, stylusProgram, data, nil, compareGasForEach, 0, compareGasPair{opcode, "emit_log"}) + }) + } + } + +} + +func TestProgramCallCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusMulticall := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("multicall")) + evmMulticall := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.MultiCallTestMetaData) + otherStylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) + otherEvmProgram := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.HostioTestMetaData) + packer, _ := util.NewCallParser(mocksgen.HostioTestABI, "msgValue") + otherData, err := packer() + Require(t, err) + + for _, pair := range []compareGasPair{ + {vm.CALL, "call_contract"}, + {vm.DELEGATECALL, "delegate_call_contract"}, + {vm.STATICCALL, "static_call_contract"}, + } { + t.Run(pair.hostio+"/burnGas", func(t *testing.T) { + arbTest := common.HexToAddress("0x0000000000000000000000000000000000000069") + burnArbGas, _ := util.NewCallParser(precompilesgen.ArbosTestABI, "burnArbGas") + burnData, err := burnArbGas(big.NewInt(0)) + Require(t, err) + data := argsForMulticall(pair.opcode, arbTest, nil, burnData) + compareGasUsage(t, builder, evmMulticall, stylusMulticall, data, nil, compareGasForEach, 0, pair) + }) + + t.Run(pair.hostio+"/evmContract", func(t *testing.T) { + data := argsForMulticall(pair.opcode, otherEvmProgram, nil, otherData) + compareGasUsage(t, builder, evmMulticall, stylusMulticall, data, nil, compareGasForEach, 0, pair, + compareGasPair{vm.RETURNDATACOPY, "read_return_data"}) // also test read_return_data + }) + + t.Run(pair.hostio+"/stylusContract", func(t *testing.T) { + data := argsForMulticall(pair.opcode, otherStylusProgram, nil, otherData) + compareGasUsage(t, builder, evmMulticall, stylusMulticall, data, nil, compareGasForEach, 0, pair, + compareGasPair{vm.RETURNDATACOPY, "read_return_data"}) // also test read_return_data + }) + + t.Run(pair.hostio+"/multipleTimes", func(t *testing.T) { + data := multicallEmptyArgs() + for i := 0; i < 9; i++ { + data = multicallAppend(data, pair.opcode, otherEvmProgram, otherData) + } + compareGasUsage(t, builder, evmMulticall, stylusMulticall, data, nil, compareGasForEach, 0, pair) + }) + } + + t.Run("call_contract/evmContractWithValue", func(t *testing.T) { + value := big.NewInt(1000) + data := argsForMulticall(vm.CALL, otherEvmProgram, value, otherData) + compareGasUsage(t, builder, evmMulticall, stylusMulticall, data, value, compareGasForEach, 0, compareGasPair{vm.CALL, "call_contract"}) + }) +} + +func TestProgramCreateCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusCreate := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("create")) + evmCreate := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.CreateTestMetaData) + deployCode := common.FromHex(mocksgen.ProgramTestMetaData.Bin) + + t.Run("create1", func(t *testing.T) { + data := []byte{0x01} + data = append(data, (common.Hash{}).Bytes()...) // endowment + data = append(data, deployCode...) + compareGasUsage(t, builder, evmCreate, stylusCreate, data, nil, compareGasForEach, 0, compareGasPair{vm.CREATE, "create1"}) + }) + + t.Run("create2", func(t *testing.T) { + data := []byte{0x02} + data = append(data, (common.Hash{}).Bytes()...) // endowment + data = append(data, (common.HexToHash("beef")).Bytes()...) // salt + data = append(data, deployCode...) + compareGasUsage(t, builder, evmCreate, stylusCreate, data, nil, compareGasForEach, 0, compareGasPair{vm.CREATE2, "create2"}) + }) +} + +func TestProgramKeccakCost(t *testing.T) { + builder := setupGasCostTest(t) + auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) + stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) + evmProgram := deployEvmContract(t, builder.ctx, auth, builder.L2.Client, mocksgen.HostioTestMetaData) + packer, _ := util.NewCallParser(mocksgen.HostioTestABI, "keccak") + + for i := 1; i < 5; i++ { + size := uint64(math.Pow10(i)) + name := fmt.Sprintf("keccak%d", size) + t.Run(name, func(t *testing.T) { + preImage := testhelpers.RandomSlice(size) + preImage[len(preImage)-1] = 0 + data, err := packer(preImage) + Require(t, err) + const maxDiff = 2.5 // stylus keccak charges significantly less gas + compareGasUsage(t, builder, evmProgram, stylusProgram, data, nil, compareGasForEach, maxDiff, compareGasPair{vm.KECCAK256, "native_keccak256"}) + }) + } +} + +func setupGasCostTest(t *testing.T) *NodeBuilder { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + cleanup := builder.Build(t) + t.Cleanup(cleanup) + return builder +} + +// deployEvmContract deploys an Evm contract and return its address. +func deployEvmContract(t *testing.T, ctx context.Context, auth bind.TransactOpts, client *ethclient.Client, metadata *bind.MetaData) common.Address { + t.Helper() + parsed, err := metadata.GetAbi() + Require(t, err) + address, tx, _, err := bind.DeployContract(&auth, *parsed, common.FromHex(metadata.Bin), client) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + return address +} + +// measureGasUsage calls an EVM and a Wasm contract passing the same data and the same value. +func measureGasUsage( + t *testing.T, + builder *NodeBuilder, + evmContract common.Address, + stylusContract common.Address, + txData []byte, + txValue *big.Int, +) (map[vm.OpCode][]uint64, map[string][]float64) { + const txGas uint64 = 32_000_000 + txs := []*types.Transaction{ + builder.L2Info.PrepareTxTo("Owner", &evmContract, txGas, txValue, txData), + builder.L2Info.PrepareTxTo("Owner", &stylusContract, txGas, txValue, txData), + } + receipts := builder.L2.SendWaitTestTransactions(t, txs) + + evmGas := receipts[0].GasUsedForL2() + evmGasUsage, err := evmOpcodesGasUsage(builder.ctx, builder.L2.Client.Client(), txs[0]) + Require(t, err) + + stylusGas := receipts[1].GasUsedForL2() + stylusGasUsage, err := stylusHostiosGasUsage(builder.ctx, builder.L2.Client.Client(), txs[1]) + Require(t, err) + + t.Logf("evm total usage: %v - stylus total usage: %v", evmGas, stylusGas) + + return evmGasUsage, stylusGasUsage +} + +type compareGasPair struct { + opcode vm.OpCode + hostio string +} + +type compareGasMode int + +const ( + compareGasForEach compareGasMode = iota + compareGasSum +) + +// compareGasUsage calls measureGasUsage and then it ensures the given opcodes and hostios cost +// roughly the same amount of gas. +func compareGasUsage( + t *testing.T, + builder *NodeBuilder, + evmContract common.Address, + stylusContract common.Address, + txData []byte, + txValue *big.Int, + mode compareGasMode, + maxAllowedDifference float64, + pairs ...compareGasPair, +) { + if evmContract == stylusContract { + Fatal(t, "evm and stylus contract are the same") + } + evmGasUsage, stylusGasUsage := measureGasUsage(t, builder, evmContract, stylusContract, txData, txValue) + for i := range pairs { + opcode := pairs[i].opcode + hostio := pairs[i].hostio + switch mode { + case compareGasForEach: + if len(evmGasUsage[opcode]) != len(stylusGasUsage[hostio]) { + Fatal(t, "mismatch between hostios and opcodes", evmGasUsage, stylusGasUsage) + } + for i := range evmGasUsage[opcode] { + opcodeGas := evmGasUsage[opcode][i] + hostioGas := stylusGasUsage[hostio][i] + t.Logf("evm %v usage: %v - stylus %v usage: %v", opcode, opcodeGas, hostio, hostioGas) + checkPercentDiff(t, float64(opcodeGas), hostioGas, maxAllowedDifference) + } + case compareGasSum: + evmSum := float64(0) + stylusSum := float64(0) + for i := range evmGasUsage[opcode] { + evmSum += float64(evmGasUsage[opcode][i]) + stylusSum += stylusGasUsage[hostio][i] + } + t.Logf("evm %v usage: %v - stylus %v usage: %v", opcode, evmSum, hostio, stylusSum) + checkPercentDiff(t, evmSum, stylusSum, maxAllowedDifference) + } + } +} + +func evmOpcodesGasUsage(ctx context.Context, rpcClient rpc.ClientInterface, tx *types.Transaction) ( + map[vm.OpCode][]uint64, error) { + + var result logger.ExecutionResult + err := rpcClient.CallContext(ctx, &result, "debug_traceTransaction", tx.Hash(), nil) + if err != nil { + return nil, fmt.Errorf("failed to trace evm call: %w", err) + } + + gasUsage := map[vm.OpCode][]uint64{} + for i := range result.StructLogs { + op := vm.StringToOp(result.StructLogs[i].Op) + gasUsed := uint64(0) + if op == vm.CALL || op == vm.STATICCALL || op == vm.DELEGATECALL || op == vm.CREATE || op == vm.CREATE2 { + // For the CALL* opcodes, the GasCost in the tracer represents the gas sent + // to the callee contract, which is 63/64 of the remaining gas. This happens + // because the tracer is evaluated before the call is executed, so the EVM + // doesn't know how much gas will being used. + // + // In the case of the Stylus tracer, the trace is emitted after the + // execution, so the EndInk field is set to the ink after the call returned. + // Hence, it also includes the ink spent by the callee contract. + // + // To make a precise comparison between the EVM and Stylus, we modify the + // EVM measurement to include the gas spent by the callee contract. To do + // so, we go through the opcodes after CALL until we find the first opcode + // in the caller's depth. Then, we subtract the gas before the call by the + // gas after the call returned. + var gasAfterCall uint64 + for j := i + 1; j < len(result.StructLogs); j++ { + if result.StructLogs[j].Depth == result.StructLogs[i].Depth { + // back to the original call + gasAfterCall = result.StructLogs[j].Gas + result.StructLogs[j].GasCost + break + } + } + if gasAfterCall == 0 { + return nil, fmt.Errorf("malformed log: didn't get back to call original depth") + } + if i == 0 { + return nil, fmt.Errorf("malformed log: call is first opcode") + } + gasUsed = result.StructLogs[i-1].Gas - gasAfterCall + } else { + gasUsed = result.StructLogs[i].GasCost + } + gasUsage[op] = append(gasUsage[op], gasUsed) + } + return gasUsage, nil +} + +func stylusHostiosGasUsage(ctx context.Context, rpcClient rpc.ClientInterface, tx *types.Transaction) ( + map[string][]float64, error) { + + traceOpts := struct { + Tracer string `json:"tracer"` + }{ + Tracer: "stylusTracer", + } + var result []gethexec.HostioTraceInfo + err := rpcClient.CallContext(ctx, &result, "debug_traceTransaction", tx.Hash(), traceOpts) + if err != nil { + return nil, fmt.Errorf("failed to trace stylus call: %w", err) + } + + const InkPerGas = 10000 + gasUsage := map[string][]float64{} + for _, hostioLog := range result { + gasCost := float64(hostioLog.StartInk-hostioLog.EndInk) / InkPerGas + gasUsage[hostioLog.Name] = append(gasUsage[hostioLog.Name], gasCost) + } + return gasUsage, nil +} + +// checkPercentDiff checks whether the two values are close enough. +func checkPercentDiff(t *testing.T, a, b float64, maxAllowedDifference float64) { + t.Helper() + if maxAllowedDifference == 0 { + maxAllowedDifference = 0.25 + } + percentageDifference := (max(a, b) / min(a, b)) - 1 + if percentageDifference > maxAllowedDifference { + Fatal(t, fmt.Sprintf("gas usages are too different; got %v, max allowed is %v", percentageDifference, maxAllowedDifference)) + } +} diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 83c066fdb5..4c896d1791 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -417,10 +417,15 @@ func storageTest(t *testing.T, jit bool) { key := testhelpers.RandomHash() value := testhelpers.RandomHash() tx := l2info.PrepareTxTo("Owner", &programAddress, l2info.TransferGas, nil, argsForStorageWrite(key, value)) - ensure(tx, l2client.SendTransaction(ctx, tx)) + receipt := ensure(tx, l2client.SendTransaction(ctx, tx)) + assertStorageAt(t, ctx, l2client, programAddress, key, value) validateBlocks(t, 2, jit, builder) + + // Captures a block_inputs json file for the block that included the + // storage write transaction. Include wasm targets necessary for arbitrator prover and jit binaries + recordBlock(t, receipt.BlockNumber.Uint64(), builder, rawdb.TargetWavm, rawdb.LocalTarget()) } func TestProgramTransientStorage(t *testing.T) { @@ -1380,7 +1385,7 @@ func TestProgramCacheManager(t *testing.T) { isManager, err := arbWasmCache.IsCacheManager(nil, manager) assert(!isManager, err) - // athorize the manager + // authorize the manager ensure(arbOwner.AddWasmCacheManager(&ownerAuth, manager)) assert(arbWasmCache.IsCacheManager(nil, manager)) all, err := arbWasmCache.AllCacheManagers(nil) @@ -2007,3 +2012,403 @@ func checkWasmStoreContent(t *testing.T, wasmDb ethdb.KeyValueStore, targets []s } } } + +func deployWasmAndGetEntrySizeEstimateBytes( + t *testing.T, + builder *NodeBuilder, + auth bind.TransactOpts, + wasmName string, +) (common.Address, uint64) { + ctx := builder.ctx + l2client := builder.L2.Client + + wasm, _ := readWasmFile(t, rustFile(wasmName)) + arbWasm, err := pgen.NewArbWasm(types.ArbWasmAddress, l2client) + Require(t, err, ", wasmName:", wasmName) + + programAddress := deployContract(t, ctx, auth, l2client, wasm) + tx, err := arbWasm.ActivateProgram(&auth, programAddress) + Require(t, err, ", wasmName:", wasmName) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err, ", wasmName:", wasmName) + + if len(receipt.Logs) != 1 { + Fatal(t, "expected 1 log while activating, got ", len(receipt.Logs), ", wasmName:", wasmName) + } + log, err := arbWasm.ParseProgramActivated(*receipt.Logs[0]) + Require(t, err, ", wasmName:", wasmName) + + statedb, err := builder.L2.ExecNode.Backend.ArbInterface().BlockChain().State() + Require(t, err, ", wasmName:", wasmName) + + module, err := statedb.TryGetActivatedAsm(rawdb.LocalTarget(), log.ModuleHash) + Require(t, err, ", wasmName:", wasmName) + + entrySizeEstimateBytes := programs.GetEntrySizeEstimateBytes(module, log.Version, true) + // just a sanity check + if entrySizeEstimateBytes == 0 { + Fatal(t, "entrySizeEstimateBytes is 0, wasmName:", wasmName) + } + return programAddress, entrySizeEstimateBytes +} + +func TestWasmLruCache(t *testing.T) { + builder, auth, cleanup := setupProgramTest(t, true) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + auth.GasLimit = 32000000 + auth.Value = oneEth + + fallibleProgramAddress, fallibleEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "fallible") + keccakProgramAddress, keccakEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "keccak") + mathProgramAddress, mathEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, auth, "math") + t.Log( + "entrySizeEstimateBytes, ", + "fallible:", fallibleEntrySize, + "keccak:", keccakEntrySize, + "math:", mathEntrySize, + ) + if fallibleEntrySize == keccakEntrySize || fallibleEntrySize == mathEntrySize || keccakEntrySize == mathEntrySize { + Fatal(t, "at least two programs have the same entry size") + } + + programs.ClearWasmLruCache() + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + programs.SetWasmLruCacheCapacity(fallibleEntrySize - 1) + // fallible wasm program will not be cached since its size is greater than lru cache capacity + tx := l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + programs.SetWasmLruCacheCapacity( + fallibleEntrySize + keccakEntrySize + mathEntrySize - 1, + ) + // fallible wasm program will be cached + tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // keccak wasm program will be cached + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + + // math wasm program will be cached, but fallible will be evicted since (fallible + keccak + math) > lruCacheCapacity + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: keccakEntrySize + mathEntrySize, + }) +} + +func checkLongTermCacheMetrics(t *testing.T, expected programs.WasmLongTermCacheMetrics) { + t.Helper() + longTermMetrics := programs.GetWasmCacheMetrics().LongTerm + if longTermMetrics.Count != expected.Count { + t.Fatalf("longTermMetrics.Count, expected: %v, actual: %v", expected.Count, longTermMetrics.Count) + } + if longTermMetrics.SizeBytes != expected.SizeBytes { + t.Fatalf("longTermMetrics.SizeBytes, expected: %v, actual: %v", expected.SizeBytes, longTermMetrics.SizeBytes) + } +} + +func checkLruCacheMetrics(t *testing.T, expected programs.WasmLruCacheMetrics) { + t.Helper() + lruMetrics := programs.GetWasmCacheMetrics().Lru + if lruMetrics.Count != expected.Count { + t.Fatalf("lruMetrics.Count, expected: %v, actual: %v", expected.Count, lruMetrics.Count) + } + if lruMetrics.SizeBytes != expected.SizeBytes { + t.Fatalf("lruMetrics.SizeBytes, expected: %v, actual: %v", expected.SizeBytes, lruMetrics.SizeBytes) + } +} + +func TestWasmLongTermCache(t *testing.T) { + builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { + builder.WithStylusLongTermCache(true) + }) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + arbWasmCache, err := pgen.NewArbWasmCache(types.ArbWasmCacheAddress, builder.L2.Client) + Require(t, err) + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&ownerAuth, 10_000)) + + ownerAuth.GasLimit = 32000000 + ownerAuth.Value = oneEth + + fallibleProgramAddress, fallibleEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "fallible") + keccakProgramAddress, keccakEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "keccak") + mathProgramAddress, mathEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "math") + t.Log( + "entrySizeEstimateBytes, ", + "fallible:", fallibleEntrySize, + "keccak:", keccakEntrySize, + "math:", mathEntrySize, + ) + if fallibleEntrySize == keccakEntrySize || fallibleEntrySize == mathEntrySize || keccakEntrySize == mathEntrySize { + Fatal(t, "at least two programs have the same entry size") + } + + ownerAuth.Value = common.Big0 + + programs.ClearWasmLongTermCache() + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + // fallible wasm program will not be cached since caching is not set for this program + tx := l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + ensure(arbWasmCache.CacheProgram(&ownerAuth, fallibleProgramAddress)) + // fallible wasm program will be cached + tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // keccak wasm program will be cached + ensure(arbWasmCache.CacheProgram(&ownerAuth, keccakProgramAddress)) + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + + // math wasm program will not be cached + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + + // math wasm program will be cached + ensure(arbWasmCache.CacheProgram(&ownerAuth, mathProgramAddress)) + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 3, + SizeBytes: fallibleEntrySize + keccakEntrySize + mathEntrySize, + }) + + statedb, err := builder.L2.ExecNode.Backend.ArbInterface().BlockChain().State() + Require(t, err) + fallibleProgramHash := statedb.GetCodeHash(fallibleProgramAddress) + keccakProgramHash := statedb.GetCodeHash(keccakProgramAddress) + mathProgramHash := statedb.GetCodeHash(mathProgramAddress) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, keccakProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + mathEntrySize, + }) + + // keccak wasm program will not be cached + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + mathEntrySize, + }) + + // keccak wasm program will be cached + ensure(arbWasmCache.CacheProgram(&ownerAuth, keccakProgramAddress)) + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 3, + SizeBytes: fallibleEntrySize + keccakEntrySize + mathEntrySize, + }) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, fallibleProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 2, + SizeBytes: keccakEntrySize + mathEntrySize, + }) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, mathProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: keccakEntrySize, + }) + + ensure(arbWasmCache.EvictCodehash(&ownerAuth, keccakProgramHash)) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) +} + +func TestRepopulateWasmLongTermCacheFromLru(t *testing.T) { + builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { + builder.WithStylusLongTermCache(true) + }) + ctx := builder.ctx + l2info := builder.L2Info + l2client := builder.L2.Client + defer cleanup() + + ensure := func(tx *types.Transaction, err error) *types.Receipt { + t.Helper() + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + return receipt + } + + arbWasmCache, err := pgen.NewArbWasmCache(types.ArbWasmCacheAddress, builder.L2.Client) + Require(t, err) + arbOwner, err := pgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + ensure(arbOwner.SetInkPrice(&ownerAuth, 10_000)) + + ownerAuth.GasLimit = 32000000 + ownerAuth.Value = oneEth + + fallibleProgramAddress, fallibleEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "fallible") + keccakProgramAddress, keccakEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "keccak") + mathProgramAddress, mathEntrySize := deployWasmAndGetEntrySizeEstimateBytes(t, builder, ownerAuth, "math") + if fallibleEntrySize == keccakEntrySize || fallibleEntrySize == mathEntrySize || keccakEntrySize == mathEntrySize { + Fatal(t, "at least two programs have the same entry size") + } + + ownerAuth.Value = common.Big0 + + programs.ClearWasmLongTermCache() + programs.ClearWasmLruCache() + // only 2 out of 3 programs should fit lru + programs.SetWasmLruCacheCapacity( + fallibleEntrySize + keccakEntrySize + mathEntrySize - 1, + ) + + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + ensure(arbWasmCache.CacheProgram(&ownerAuth, fallibleProgramAddress)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // clear long term cache to emulate restart + programs.ClearWasmLongTermCache() + programs.ClearWasmLruCache() + + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + nonce := builder.L2Info.GetInfoWithPrivKey("Owner").Nonce.Load() + tx := l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + _, err = arbutil.SendTxAsCall(ctx, l2client, tx, l2info.GetAddress("Owner"), nil, true) + Require(t, err) + // restore nonce in L2Info + builder.L2Info.GetInfoWithPrivKey("Owner").Nonce.Store(nonce) + // fallibleProgram should be added only to lru cache as the api call should be processed with wasm cache tag = 0 + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + tx = l2info.PrepareTxTo("Owner", &keccakProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 0, + SizeBytes: 0, + }) + + tx = l2info.PrepareTxTo("Owner", &fallibleProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: fallibleEntrySize + keccakEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) + + // mathProgram should end up in lru cache and + // as result fallibleProgram should be evicted as least recently used item (tx that restores the program back to long term cache shouldn't promote the lru item); + // fallibleProgram should remain in long term cache + tx = l2info.PrepareTxTo("Owner", &mathProgramAddress, l2info.TransferGas, nil, []byte{0x01}) + ensure(tx, l2client.SendTransaction(ctx, tx)) + checkLruCacheMetrics(t, programs.WasmLruCacheMetrics{ + Count: 2, + SizeBytes: keccakEntrySize + mathEntrySize, + }) + checkLongTermCacheMetrics(t, programs.WasmLongTermCacheMetrics{ + Count: 1, + SizeBytes: fallibleEntrySize, + }) +} diff --git a/system_tests/recreatestate_rpc_test.go b/system_tests/recreatestate_rpc_test.go index 4833d35536..22329a1be5 100644 --- a/system_tests/recreatestate_rpc_test.go +++ b/system_tests/recreatestate_rpc_test.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "math/big" + "runtime" "strings" "sync" "testing" @@ -338,6 +339,7 @@ func TestRecreateStateForRPCBlockNotFoundWhileRecreating(t *testing.T) { } func testSkippingSavingStateAndRecreatingAfterRestart(t *testing.T, cacheConfig *gethexec.CachingConfig, txCount int) { + t.Parallel() maxRecreateStateDepth := int64(30 * 1000 * 1000) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -451,20 +453,26 @@ func TestSkippingSavingStateAndRecreatingAfterRestart(t *testing.T) { cacheConfig.SnapshotCache = 0 // disable snapshots cacheConfig.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are + runTestCase := func(t *testing.T, cacheConfig gethexec.CachingConfig, txes int) { + t.Run(fmt.Sprintf("skip-blocks-%d-skip-gas-%d-txes-%d", cacheConfig.MaxNumberOfBlocksToSkipStateSaving, cacheConfig.MaxAmountOfGasToSkipStateSaving, txes), func(t *testing.T) { + testSkippingSavingStateAndRecreatingAfterRestart(t, &cacheConfig, txes) + }) + } + // test defaults - testSkippingSavingStateAndRecreatingAfterRestart(t, &cacheConfig, 512) + runTestCase(t, cacheConfig, 512) cacheConfig.MaxNumberOfBlocksToSkipStateSaving = 127 cacheConfig.MaxAmountOfGasToSkipStateSaving = 0 - testSkippingSavingStateAndRecreatingAfterRestart(t, &cacheConfig, 512) + runTestCase(t, cacheConfig, 512) cacheConfig.MaxNumberOfBlocksToSkipStateSaving = 0 cacheConfig.MaxAmountOfGasToSkipStateSaving = 15 * 1000 * 1000 - testSkippingSavingStateAndRecreatingAfterRestart(t, &cacheConfig, 512) + runTestCase(t, cacheConfig, 512) cacheConfig.MaxNumberOfBlocksToSkipStateSaving = 127 cacheConfig.MaxAmountOfGasToSkipStateSaving = 15 * 1000 * 1000 - testSkippingSavingStateAndRecreatingAfterRestart(t, &cacheConfig, 512) + runTestCase(t, cacheConfig, 512) // lower number of blocks in triegc below 100 blocks, to be able to check for nonexistence in testSkippingSavingStateAndRecreatingAfterRestart (it doesn't check last BlockCount blocks as some of them may be persisted on node shutdown) cacheConfig.BlockCount = 16 @@ -480,21 +488,16 @@ func TestSkippingSavingStateAndRecreatingAfterRestart(t *testing.T) { cacheConfig.MaxAmountOfGasToSkipStateSaving = skipGas // #nosec G115 cacheConfig.MaxNumberOfBlocksToSkipStateSaving = uint32(skipBlocks) - testSkippingSavingStateAndRecreatingAfterRestart(t, &cacheConfig, 100) + runTestCase(t, cacheConfig, 100) } } } -func TestGettingStateForRPCFullNode(t *testing.T) { +func testGettingState(t *testing.T, execConfig *gethexec.Config) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - execConfig := ExecConfigDefaultTest(t) - execConfig.Caching.SnapshotCache = 0 // disable snapshots - execConfig.Caching.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 16) - execNode, _ := builder.L2.ExecNode, builder.L2.Client + execNode := builder.L2.ExecNode defer cancelNode() bc := execNode.Backend.ArbInterface().BlockChain() api := execNode.Backend.APIBackend() @@ -521,18 +524,40 @@ func TestGettingStateForRPCFullNode(t *testing.T) { blockCountRequiredToFlushDirties := builder.execConfig.Caching.BlockCount makeSomeTransfers(t, ctx, builder, blockCountRequiredToFlushDirties) + // force garbage collection to check if it won't break anything + runtime.GC() + exists = state.Exist(addr) err = state.Error() Require(t, err) if !exists { Fatal(t, "User2 address does not exist in the state") } + + // force garbage collection of StateDB object, what should cause the state finalizer to run + state = nil + runtime.GC() + _, err = bc.StateAt(header.Root) + if err == nil { + Fatal(t, "StateAndHeaderByNumber didn't failed as expected") + } + expectedErr := &trie.MissingNodeError{} + if !errors.As(err, &expectedErr) { + Fatal(t, "StateAndHeaderByNumber failed with unexpected error:", err) + } } -func TestGettingStateForRPCHybridArchiveNode(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() +func TestGettingState(t *testing.T) { execConfig := ExecConfigDefaultTest(t) + execConfig.Caching.SnapshotCache = 0 // disable snapshots + execConfig.Caching.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are + execConfig.Sequencer.MaxBlockSpeed = 0 + execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + t.Run("full-node", func(t *testing.T) { + testGettingState(t, execConfig) + }) + + execConfig = ExecConfigDefaultTest(t) execConfig.Caching.Archive = true // For now Archive node should use HashScheme execConfig.Caching.StateScheme = rawdb.HashScheme @@ -542,42 +567,13 @@ func TestGettingStateForRPCHybridArchiveNode(t *testing.T) { execConfig.Caching.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are execConfig.Sequencer.MaxBlockSpeed = 0 execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 16) - execNode, _ := builder.L2.ExecNode, builder.L2.Client - defer cancelNode() - bc := execNode.Backend.ArbInterface().BlockChain() - api := execNode.Backend.APIBackend() - - header := bc.CurrentBlock() - if header == nil { - Fatal(t, "failed to get current block header") - } - // #nosec G115 - state, _, err := api.StateAndHeaderByNumber(ctx, rpc.BlockNumber(header.Number.Uint64())) - Require(t, err) - addr := builder.L2Info.GetAddress("User2") - exists := state.Exist(addr) - err = state.Error() - Require(t, err) - if !exists { - Fatal(t, "User2 address does not exist in the state") - } - // Get the state again to avoid caching - // #nosec G115 - state, _, err = api.StateAndHeaderByNumber(ctx, rpc.BlockNumber(header.Number.Uint64())) - Require(t, err) - - blockCountRequiredToFlushDirties := builder.execConfig.Caching.BlockCount - makeSomeTransfers(t, ctx, builder, blockCountRequiredToFlushDirties) - - exists = state.Exist(addr) - err = state.Error() - Require(t, err) - if !exists { - Fatal(t, "User2 address does not exist in the state") - } + t.Run("archive-node", func(t *testing.T) { + testGettingState(t, execConfig) + }) } +// regression test for issue caused by accessing block state that has just been committed to TrieDB but not yet referenced in core.BlockChain.writeBlockWithState (here called state of "recent" block) +// before the corresponding fix, access to the recent block state caused premature garbage collection of the head block state func TestStateAndHeaderForRecentBlock(t *testing.T) { threads := 32 ctx, cancel := context.WithCancel(context.Background()) @@ -618,15 +614,22 @@ func TestStateAndHeaderForRecentBlock(t *testing.T) { }() api := builder.L2.ExecNode.Backend.APIBackend() db := builder.L2.ExecNode.Backend.ChainDb() - i := 1 + + recentBlock := 1 var mtx sync.RWMutex var wgCallers sync.WaitGroup for j := 0; j < threads && ctx.Err() == nil; j++ { wgCallers.Add(1) + // each thread attempts to get state for a block that is just being created (here called recent): + // 1. Before state trie node is referenced in core.BlockChain.writeBlockWithState, block body is written to database with key prefix `b` followed by block number and then block hash (see: rawdb.blockBodyKey) + // 2. Each thread tries to read the block body entry to: a. extract recent block hash b. congest resource usage to slow down execution of core.BlockChain.writeBlockWithState + // 3. After extracting the hash from block body entry key, StateAndHeaderByNumberOfHash is called for the hash. It is expected that it will: + // a. either fail with "ahead of current block" if we made it before rawdb.WriteCanonicalHash is called in core.BlockChain.writeHeadBlock, which is called after writeBlockWithState finishes, + // b. or it will succeed if the canonical hash was written for the block meaning that writeBlockWithState was fully executed (i.a. state root trie node correctly referenced) - then the recentBlock is advanced go func() { defer wgCallers.Done() mtx.RLock() - blockNumber := i + blockNumber := recentBlock mtx.RUnlock() for blockNumber < 300 && ctx.Err() == nil { prefix := make([]byte, 8) @@ -645,8 +648,8 @@ func TestStateAndHeaderForRecentBlock(t *testing.T) { _, _, err := api.StateAndHeaderByNumberOrHash(ctx, rpc.BlockNumberOrHash{BlockHash: &blockHash}) if err == nil { mtx.Lock() - if blockNumber == i { - i++ + if blockNumber == recentBlock { + recentBlock++ } mtx.Unlock() break @@ -666,7 +669,7 @@ func TestStateAndHeaderForRecentBlock(t *testing.T) { } it.Release() mtx.RLock() - blockNumber = i + blockNumber = recentBlock mtx.RUnlock() } }() diff --git a/system_tests/seq_coordinator_test.go b/system_tests/seq_coordinator_test.go index 1b8926a1b9..e7d8bf6b39 100644 --- a/system_tests/seq_coordinator_test.go +++ b/system_tests/seq_coordinator_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -373,3 +373,77 @@ func TestRedisSeqCoordinatorMessageSync(t *testing.T) { func TestRedisSeqCoordinatorWrongKeyMessageSync(t *testing.T) { testCoordinatorMessageSync(t, false) } + +func TestRedisSwitchover(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.nodeConfig.SeqCoordinator.Enable = true + builder.nodeConfig.SeqCoordinator.RedisUrl = redisutil.CreateTestRedis(ctx, t) + builder.nodeConfig.SeqCoordinator.NewRedisUrl = redisutil.CreateTestRedis(ctx, t) + builder.nodeConfig.BatchPoster.Enable = false + + nodeNames := []string{"stdio://A", "stdio://B"} + initRedisForTest(t, ctx, builder.nodeConfig.SeqCoordinator.RedisUrl, nodeNames) + initRedisForTest(t, ctx, builder.nodeConfig.SeqCoordinator.NewRedisUrl, nodeNames) + builder.nodeConfig.SeqCoordinator.MyUrl = nodeNames[0] + + cleanup := builder.Build(t) + defer cleanup() + + redisClient, err := redisutil.RedisClientFromURL(builder.nodeConfig.SeqCoordinator.RedisUrl) + Require(t, err) + + // wait for sequencerA to become master + for { + err := redisClient.Get(ctx, redisutil.CHOSENSEQ_KEY).Err() + if errors.Is(err, redis.Nil) { + time.Sleep(builder.nodeConfig.SeqCoordinator.UpdateInterval) + continue + } + Require(t, err) + break + } + + builder.L2Info.GenerateAccount("User2") + + nodeConfigDup := *builder.nodeConfig + builder.nodeConfig = &nodeConfigDup + builder.nodeConfig.Feed.Output = *newBroadcasterConfigTest() + builder.nodeConfig.SeqCoordinator.MyUrl = nodeNames[1] + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: builder.nodeConfig}) + defer cleanupB() + + verifyTxIsProcessed(t, ctx, builder, testClientB, 1e12) + + redisClient.Set(ctx, redisutil.CHOSENSEQ_KEY, redisutil.SWITCHED_REDIS, time.Duration(-1)) + + verifyTxIsProcessed(t, ctx, builder, testClientB, 1e12*2) + + // Wait for all messages to be processed, before closing old redisClient + time.Sleep(1 * time.Second) + err = redisClient.Close() + Require(t, err) + + verifyTxIsProcessed(t, ctx, builder, testClientB, 1e12*3) + +} + +func verifyTxIsProcessed(t *testing.T, ctx context.Context, builder *NodeBuilder, testClientB *TestClient, balance int64) { + tx := builder.L2Info.PrepareTx("Owner", "User2", builder.L2Info.TransferGas, big.NewInt(1e12), nil) + + err := builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + _, err = WaitForTx(ctx, testClientB.Client, tx.Hash(), time.Second*5) + Require(t, err) + l2balance, err := testClientB.Client.BalanceAt(ctx, builder.L2Info.GetAddress("User2"), nil) + Require(t, err) + if l2balance.Cmp(big.NewInt(balance)) != 0 { + t.Fatal("Unexpected balance:", l2balance) + } +} diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 24140e480d..c8312350e6 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -38,6 +38,7 @@ func BuildBlock( chainConfig *params.ChainConfig, inbox arbstate.InboxBackend, seqBatch []byte, + runMode core.MessageRunMode, ) (*types.Block, error) { var delayedMessagesRead uint64 if lastBlockHeader != nil { @@ -59,11 +60,13 @@ func BuildBlock( } err = l1Message.FillInBatchGasCost(batchFetcher) if err != nil { - return nil, err + // skip malformed batch posting report + // nolint:nilerr + return nil, nil } block, _, err := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, + l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, chainConfig, false, runMode, ) return block, err } @@ -127,7 +130,7 @@ func (c noopChainContext) GetHeader(common.Hash, uint64) *types.Header { } func FuzzStateTransition(f *testing.F) { - f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte) { + f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte, runModeSeed uint8) { if len(seqMsg) > 0 && daprovider.IsL1AuthenticatedMessageHeaderByte(seqMsg[0]) { return } @@ -201,7 +204,9 @@ func FuzzStateTransition(f *testing.F) { positionWithinMessage: 0, delayedMessages: delayedMessages, } - _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch) + numberOfMessageRunModes := uint8(core.MessageReplayMode) + 1 // TODO update number of run modes when new mode is added + runMode := core.MessageRunMode(runModeSeed % numberOfMessageRunModes) + _, err = BuildBlock(statedb, genesis, noopChainContext{}, params.ArbitrumOneChainConfig(), inbox, seqBatch, runMode) if err != nil { // With the fixed header it shouldn't be possible to read a delayed message, // and no other type of error should be possible. diff --git a/system_tests/stylus_trace_test.go b/system_tests/stylus_trace_test.go index 5c4463d9f7..52039df460 100644 --- a/system_tests/stylus_trace_test.go +++ b/system_tests/stylus_trace_test.go @@ -6,6 +6,7 @@ package arbtest import ( "bytes" "encoding/binary" + "math" "math/big" "testing" @@ -478,3 +479,17 @@ func TestStylusOpcodeTraceEquivalence(t *testing.T) { checkOpcode(t, wasmResult, 12, vm.RETURN, offset, returnLen) checkOpcode(t, evmResult, 5078, vm.RETURN, offset, returnLen) } + +func TestStylusHugeWriteResultTrace(t *testing.T) { + const jit = false + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + program := deployWasm(t, ctx, auth, l2client, watFile("write-result-len")) + const returnLen = math.MaxUint16 + 1 + args := binary.LittleEndian.AppendUint32(nil, returnLen) + result := sendAndTraceTransaction(t, builder, program, nil, args) + checkOpcode(t, result, 3, vm.RETURN, nil, intToBe32(returnLen)) +} diff --git a/system_tests/validation_mock_test.go b/system_tests/validation_mock_test.go index 2739c7545e..912b48ea6a 100644 --- a/system_tests/validation_mock_test.go +++ b/system_tests/validation_mock_test.go @@ -96,10 +96,6 @@ func (s *mockSpawner) LatestWasmModuleRoot() containers.PromiseInterface[common. return containers.NewReadyPromise[common.Hash](mockWasmModuleRoots[0], nil) } -func (s *mockSpawner) WriteToFile(input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] { - return containers.NewReadyPromise[struct{}](struct{}{}, nil) -} - type mockValRun struct { containers.Promise[validator.GoGlobalState] root common.Hash diff --git a/system_tests/wrap_transaction_test.go b/system_tests/wrap_transaction_test.go index bd561ad5e5..36052fb2db 100644 --- a/system_tests/wrap_transaction_test.go +++ b/system_tests/wrap_transaction_test.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbutil" @@ -22,7 +23,7 @@ import ( "github.com/offchainlabs/nitro/util/headerreader" ) -func GetPendingBlockNumber(ctx context.Context, client arbutil.L1Interface) (*big.Int, error) { +func GetPendingBlockNumber(ctx context.Context, client *ethclient.Client) (*big.Int, error) { // Attempt to get the block number from ArbSys, if it exists arbSys, err := precompilesgen.NewArbSys(common.BigToAddress(big.NewInt(100)), client) if err != nil { @@ -37,7 +38,7 @@ func GetPendingBlockNumber(ctx context.Context, client arbutil.L1Interface) (*bi } // Will wait until txhash is in the blockchain and return its receipt -func WaitForTx(ctxinput context.Context, client arbutil.L1Interface, txhash common.Hash, timeout time.Duration) (*types.Receipt, error) { +func WaitForTx(ctxinput context.Context, client *ethclient.Client, txhash common.Hash, timeout time.Duration) (*types.Receipt, error) { ctx, cancel := context.WithTimeout(ctxinput, timeout) defer cancel() @@ -75,11 +76,11 @@ func WaitForTx(ctxinput context.Context, client arbutil.L1Interface, txhash comm } } -func EnsureTxSucceeded(ctx context.Context, client arbutil.L1Interface, tx *types.Transaction) (*types.Receipt, error) { +func EnsureTxSucceeded(ctx context.Context, client *ethclient.Client, tx *types.Transaction) (*types.Receipt, error) { return EnsureTxSucceededWithTimeout(ctx, client, tx, time.Second*5) } -func EnsureTxSucceededWithTimeout(ctx context.Context, client arbutil.L1Interface, tx *types.Transaction, timeout time.Duration) (*types.Receipt, error) { +func EnsureTxSucceededWithTimeout(ctx context.Context, client *ethclient.Client, tx *types.Transaction, timeout time.Duration) (*types.Receipt, error) { receipt, err := WaitForTx(ctx, client, tx.Hash(), timeout) if err != nil { return nil, fmt.Errorf("waitFoxTx (tx=%s) got: %w", tx.Hash().Hex(), err) @@ -103,12 +104,12 @@ func EnsureTxSucceededWithTimeout(ctx context.Context, client arbutil.L1Interfac return receipt, arbutil.DetailTxError(ctx, client, tx, receipt) } -func EnsureTxFailed(t *testing.T, ctx context.Context, client arbutil.L1Interface, tx *types.Transaction) *types.Receipt { +func EnsureTxFailed(t *testing.T, ctx context.Context, client *ethclient.Client, tx *types.Transaction) *types.Receipt { t.Helper() return EnsureTxFailedWithTimeout(t, ctx, client, tx, time.Second*5) } -func EnsureTxFailedWithTimeout(t *testing.T, ctx context.Context, client arbutil.L1Interface, tx *types.Transaction, timeout time.Duration) *types.Receipt { +func EnsureTxFailedWithTimeout(t *testing.T, ctx context.Context, client *ethclient.Client, tx *types.Transaction, timeout time.Duration) *types.Receipt { t.Helper() receipt, err := WaitForTx(ctx, client, tx.Hash(), timeout) Require(t, err) diff --git a/util/headerreader/blob_client.go b/util/headerreader/blob_client.go index 160323cf60..4831994bba 100644 --- a/util/headerreader/blob_client.go +++ b/util/headerreader/blob_client.go @@ -18,8 +18,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/util/blobs" "github.com/offchainlabs/nitro/util/jsonapi" "github.com/offchainlabs/nitro/util/pretty" @@ -28,7 +28,7 @@ import ( ) type BlobClient struct { - ec arbutil.L1Interface + ec *ethclient.Client beaconUrl *url.URL secondaryBeaconUrl *url.URL httpClient *http.Client @@ -63,7 +63,7 @@ func BlobClientAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".authorization", DefaultBlobClientConfig.Authorization, "Value to send with the HTTP Authorization: header for Beacon REST requests, must include both scheme and scheme parameters") } -func NewBlobClient(config BlobClientConfig, ec arbutil.L1Interface) (*BlobClient, error) { +func NewBlobClient(config BlobClientConfig, ec *ethclient.Client) (*BlobClient, error) { beaconUrl, err := url.Parse(config.BeaconUrl) if err != nil { return nil, fmt.Errorf("failed to parse beacon chain URL: %w", err) diff --git a/util/headerreader/header_reader.go b/util/headerreader/header_reader.go index c8041dc871..98f778dee8 100644 --- a/util/headerreader/header_reader.go +++ b/util/headerreader/header_reader.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbutil" @@ -33,7 +34,7 @@ type ArbSysInterface interface { type HeaderReader struct { stopwaiter.StopWaiter config ConfigFetcher - client arbutil.L1Interface + client *ethclient.Client isParentChainArbitrum bool arbSys ArbSysInterface @@ -120,7 +121,7 @@ var TestConfig = Config{ }, } -func New(ctx context.Context, client arbutil.L1Interface, config ConfigFetcher, arbSysPrecompile ArbSysInterface) (*HeaderReader, error) { +func New(ctx context.Context, client *ethclient.Client, config ConfigFetcher, arbSysPrecompile ArbSysInterface) (*HeaderReader, error) { isParentChainArbitrum := false var arbSys ArbSysInterface if arbSysPrecompile != nil { @@ -522,7 +523,7 @@ func (s *HeaderReader) LatestFinalizedBlockNr(ctx context.Context) (uint64, erro return header.Number.Uint64(), nil } -func (s *HeaderReader) Client() arbutil.L1Interface { +func (s *HeaderReader) Client() *ethclient.Client { return s.client } diff --git a/util/redisutil/redis_coordinator.go b/util/redisutil/redis_coordinator.go index 2c12ffec50..39db7c8645 100644 --- a/util/redisutil/redis_coordinator.go +++ b/util/redisutil/redis_coordinator.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/go-redis/redis/v8" + "github.com/redis/go-redis/v9" "github.com/ethereum/go-ethereum/log" @@ -21,6 +21,7 @@ const WANTS_LOCKOUT_KEY_PREFIX string = "coordinator.liveliness." // Per se const MESSAGE_KEY_PREFIX string = "coordinator.msg." // Per Message. Only written by sequencer holding CHOSEN const SIGNATURE_KEY_PREFIX string = "coordinator.msg.sig." // Per Message. Only written by sequencer holding CHOSEN const WANTS_LOCKOUT_VAL string = "OK" +const SWITCHED_REDIS string = "SWITCHED_REDIS" const INVALID_VAL string = "INVALID" const INVALID_URL string = "" diff --git a/util/redisutil/redisutil.go b/util/redisutil/redisutil.go index f89c250e9a..01ba836d5b 100644 --- a/util/redisutil/redisutil.go +++ b/util/redisutil/redisutil.go @@ -1,6 +1,6 @@ package redisutil -import "github.com/go-redis/redis/v8" +import "github.com/redis/go-redis/v9" func RedisClientFromURL(url string) (redis.UniversalClient, error) { if url == "" { diff --git a/validator/client/redis/producer.go b/validator/client/redis/producer.go index adc2f34af5..c5726ffe8b 100644 --- a/validator/client/redis/producer.go +++ b/validator/client/redis/producer.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/pubsub" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/redisutil" @@ -17,6 +16,7 @@ import ( "github.com/offchainlabs/nitro/validator" "github.com/offchainlabs/nitro/validator/server_api" "github.com/offchainlabs/nitro/validator/server_common" + "github.com/redis/go-redis/v9" "github.com/spf13/pflag" ) diff --git a/validator/client/validation_client.go b/validator/client/validation_client.go index 3b18ad1851..934362f00a 100644 --- a/validator/client/validation_client.go +++ b/validator/client/validation_client.go @@ -188,19 +188,6 @@ func (c *ExecutionClient) LatestWasmModuleRoot() containers.PromiseInterface[com }) } -func (c *ExecutionClient) WriteToFile(input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] { - jsonInput := server_api.ValidationInputToJson(input) - if err := jsonInput.WriteToFile(); err != nil { - return stopwaiter.LaunchPromiseThread[struct{}](c, func(ctx context.Context) (struct{}, error) { - return struct{}{}, err - }) - } - return stopwaiter.LaunchPromiseThread[struct{}](c, func(ctx context.Context) (struct{}, error) { - err := c.client.CallContext(ctx, nil, server_api.Namespace+"_writeToFile", jsonInput, expOut, moduleRoot) - return struct{}{}, err - }) -} - func (r *ExecutionClientRun) SendKeepAlive(ctx context.Context) time.Duration { err := r.client.client.CallContext(ctx, nil, server_api.Namespace+"_execKeepAlive", r.id) if err != nil { diff --git a/validator/inputs/writer.go b/validator/inputs/writer.go new file mode 100644 index 0000000000..1a476c52a3 --- /dev/null +++ b/validator/inputs/writer.go @@ -0,0 +1,157 @@ +package inputs + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/offchainlabs/nitro/validator/server_api" +) + +// Writer is a configurable writer of InputJSON files. +// +// The default Writer will write to a path like: +// +// $HOME/.arbuitrum/validation-inputs//block_inputs_.json +// +// The path can be nested under a slug directory so callers can provide a +// recognizable name to differentiate various contexts in which the InputJSON +// is being written. If the Writer is configured by calling WithSlug, then the +// path will be like: +// +// $HOME/.arbuitrum/validation-inputs///block_inputs_.json +// +// The inclusion of BlockId in the file's name is on by default, however that can be disabled +// by calling WithBlockIdInFileNameEnabled(false). In which case, the path will be like: +// +// $HOME/.arbuitrum/validation-inputs///block_inputs.json +// +// The inclusion of a timestamp directory is on by default to avoid conflicts which +// would result in files being overwritten. However, the Writer can be configured +// to not use a timestamp directory. If the Writer is configured by calling +// WithTimestampDirEnabled(false), then the path will be like: +// +// $HOME/.arbuitrum/validation-inputs//block_inputs_.json +// +// Finally, to give complete control to the clients, the base directory can be +// set directly with WithBaseDir. In which case, the path will be like: +// +// /block_inputs_.json +// or +// //block_inputs_.json +// or +// ///block_inputs_.json +type Writer struct { + clock Clock + baseDir string + slug string + useTimestampDir bool + useBlockIdInFileName bool +} + +// WriterOption is a function that configures a Writer. +type WriterOption func(*Writer) + +// Clock is an interface for getting the current time. +type Clock interface { + Now() time.Time +} + +type realClock struct{} + +func (realClock) Now() time.Time { + return time.Now() +} + +// NewWriter creates a new Writer with default settings. +func NewWriter(options ...WriterOption) (*Writer, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, err + } + baseDir := filepath.Join(homeDir, ".arbitrum", "validation-inputs") + w := &Writer{ + clock: realClock{}, + baseDir: baseDir, + slug: "", + useTimestampDir: true, + useBlockIdInFileName: true, + } + for _, o := range options { + o(w) + } + return w, nil +} + +// withTestClock configures the Writer to use the given clock. +// +// This is only intended for testing. +func withTestClock(clock Clock) WriterOption { + return func(w *Writer) { + w.clock = clock + } +} + +// WithSlug configures the Writer to use the given slug as a directory name. +func WithSlug(slug string) WriterOption { + return func(w *Writer) { + w.slug = slug + } +} + +// WithoutSlug clears the slug configuration. +// +// This is equivalent to the WithSlug("") option but is more readable. +func WithoutSlug() WriterOption { + return WithSlug("") +} + +// WithBaseDir configures the Writer to use the given base directory. +func WithBaseDir(baseDir string) WriterOption { + return func(w *Writer) { + w.baseDir = baseDir + } +} + +// WithTimestampDirEnabled controls the addition of a timestamp directory. +func WithTimestampDirEnabled(useTimestampDir bool) WriterOption { + return func(w *Writer) { + w.useTimestampDir = useTimestampDir + } +} + +// WithBlockIdInFileNameEnabled controls the inclusion of Block Id in the input json file's name +func WithBlockIdInFileNameEnabled(useBlockIdInFileName bool) WriterOption { + return func(w *Writer) { + w.useBlockIdInFileName = useBlockIdInFileName + } +} + +// Write writes the given InputJSON to a file in JSON format. +func (w *Writer) Write(json *server_api.InputJSON) error { + dir := w.baseDir + if w.slug != "" { + dir = filepath.Join(dir, w.slug) + } + if w.useTimestampDir { + t := w.clock.Now() + tStr := t.Format("20060102_150405") + dir = filepath.Join(dir, tStr) + } + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + contents, err := json.Marshal() + if err != nil { + return err + } + fileName := "block_inputs.json" + if w.useBlockIdInFileName { + fileName = fmt.Sprintf("block_inputs_%d.json", json.Id) + } + if err = os.WriteFile(filepath.Join(dir, fileName), contents, 0600); err != nil { + return err + } + return nil +} diff --git a/validator/inputs/writer_test.go b/validator/inputs/writer_test.go new file mode 100644 index 0000000000..59cb63dae7 --- /dev/null +++ b/validator/inputs/writer_test.go @@ -0,0 +1,92 @@ +package inputs + +import ( + "os" + "testing" + "time" + + "github.com/offchainlabs/nitro/validator/server_api" +) + +func TestDefaultBaseDir(t *testing.T) { + // Simply testing that the default baseDir is set relative to the user's home directory. + // This way, the other tests can all override the baseDir to a temporary directory. + w, err := NewWriter() + if err != nil { + t.Fatal(err) + } + homeDir, err := os.UserHomeDir() + if err != nil { + t.Fatal(err) + } + if w.baseDir != homeDir+"/.arbitrum/validation-inputs" { + t.Errorf("unexpected baseDir: %v", w.baseDir) + } +} + +type fakeClock struct { + now time.Time +} + +func (c fakeClock) Now() time.Time { + return c.now +} + +func TestWriting(t *testing.T) { + dir := t.TempDir() + w, err := NewWriter( + withTestClock(fakeClock{now: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC)}), + WithBaseDir(dir), + ) + if err != nil { + t.Fatal(err) + } + err = w.Write(&server_api.InputJSON{Id: 24601}) + if err != nil { + t.Fatal(err) + } + // The file should exist. + if _, err := os.Stat(dir + "/20210102_030405/block_inputs_24601.json"); err != nil { + t.Error(err) + } +} + +func TestWritingWithSlug(t *testing.T) { + dir := t.TempDir() + w, err := NewWriter( + withTestClock(fakeClock{now: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC)}), + WithBaseDir(dir), + WithSlug("foo"), + ) + if err != nil { + t.Fatal(err) + } + err = w.Write(&server_api.InputJSON{Id: 24601}) + if err != nil { + t.Fatal(err) + } + // The file should exist. + if _, err := os.Stat(dir + "/foo/20210102_030405/block_inputs_24601.json"); err != nil { + t.Error(err) + } +} + +func TestWritingWithoutTimestampDir(t *testing.T) { + dir := t.TempDir() + w, err := NewWriter( + withTestClock(fakeClock{now: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC)}), + WithBaseDir(dir), + WithTimestampDirEnabled(false), + ) + if err != nil { + t.Fatal(err) + } + err = w.Write(&server_api.InputJSON{Id: 24601}) + if err != nil { + t.Fatal(err) + } + // The file should exist. + if _, err := os.Stat(dir + "/block_inputs_24601.json"); err != nil { + t.Error(err) + } +} diff --git a/validator/interface.go b/validator/interface.go index af08629137..9fb831ca0d 100644 --- a/validator/interface.go +++ b/validator/interface.go @@ -27,7 +27,6 @@ type ExecutionSpawner interface { ValidationSpawner CreateExecutionRun(wasmModuleRoot common.Hash, input *ValidationInput) containers.PromiseInterface[ExecutionRun] LatestWasmModuleRoot() containers.PromiseInterface[common.Hash] - WriteToFile(input *ValidationInput, expOut GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] } type ExecutionRun interface { diff --git a/validator/server_api/json.go b/validator/server_api/json.go index 6fe936e17d..8dfbc8446a 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -8,7 +8,6 @@ import ( "encoding/json" "errors" "fmt" - "os" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -68,15 +67,9 @@ type InputJSON struct { DebugChain bool } -func (i *InputJSON) WriteToFile() error { - contents, err := json.MarshalIndent(i, "", " ") - if err != nil { - return err - } - if err = os.WriteFile(fmt.Sprintf("block_inputs_%d.json", i.Id), contents, 0600); err != nil { - return err - } - return nil +// Marshal returns the JSON encoding of the InputJSON. +func (i *InputJSON) Marshal() ([]byte, error) { + return json.MarshalIndent(i, "", " ") } type BatchInfoJson struct { diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index e77e071d70..1e73e6b212 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -4,7 +4,7 @@ package server_arb /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #include "arbitrator.h" ResolvedPreimage preimageResolverC(size_t context, uint8_t preimageType, const uint8_t* hash); @@ -92,7 +92,7 @@ func LoadSimpleMachine(wasm string, libraries []string, debugChain bool) (*Arbit cWasm := C.CString(wasm) cLibraries := CreateCStringList(libraries) debug := usize(arbmath.BoolToUint32(debugChain)) - mach := C.arbitrator_load_machine(cWasm, cLibraries, C.long(len(libraries)), debug, false) + mach := C.arbitrator_load_machine(cWasm, cLibraries, C.long(len(libraries)), debug) C.free(unsafe.Pointer(cWasm)) FreeCStringList(cLibraries, len(libraries)) if mach == nil { diff --git a/validator/server_arb/nitro_machine.go b/validator/server_arb/nitro_machine.go index 2b2cb230b6..926b1e8930 100644 --- a/validator/server_arb/nitro_machine.go +++ b/validator/server_arb/nitro_machine.go @@ -4,7 +4,7 @@ package server_arb /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #include "arbitrator.h" #include */ diff --git a/validator/server_arb/preimage_resolver.go b/validator/server_arb/preimage_resolver.go index cd4ea40e28..f01d79f4dd 100644 --- a/validator/server_arb/preimage_resolver.go +++ b/validator/server_arb/preimage_resolver.go @@ -4,7 +4,7 @@ package server_arb /* -#cgo CFLAGS: -g -Wall -I../../target/include/ +#cgo CFLAGS: -g -I../../target/include/ #include "arbitrator.h" extern ResolvedPreimage preimageResolver(size_t context, uint8_t preimageType, const uint8_t* hash); diff --git a/validator/server_arb/prover_interface.go b/validator/server_arb/prover_interface.go index bdd81ed588..3010d2138d 100644 --- a/validator/server_arb/prover_interface.go +++ b/validator/server_arb/prover_interface.go @@ -4,7 +4,7 @@ package server_arb /* -#cgo CFLAGS: -g -Wall -I../target/include/ +#cgo CFLAGS: -g -I../target/include/ #cgo LDFLAGS: ${SRCDIR}/../../target/lib/libstylus.a -ldl -lm #include "arbitrator.h" #include diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index 6f0d0cee1d..07971e2ba5 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -2,11 +2,8 @@ package server_arb import ( "context" - "encoding/binary" "errors" "fmt" - "os" - "path/filepath" "runtime" "sync/atomic" "time" @@ -98,7 +95,7 @@ func (s *ArbitratorSpawner) Name() string { return "arbitrator" } -func (v *ArbitratorSpawner) loadEntryToMachine(ctx context.Context, entry *validator.ValidationInput, mach *ArbitratorMachine) error { +func (v *ArbitratorSpawner) loadEntryToMachine(_ context.Context, entry *validator.ValidationInput, mach *ArbitratorMachine) error { resolver := func(ty arbutil.PreimageType, hash common.Hash) ([]byte, error) { // Check if it's a known preimage if preimage, ok := entry.Preimages[ty][hash]; ok { @@ -192,6 +189,7 @@ func (v *ArbitratorSpawner) execute( } func (v *ArbitratorSpawner) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { + println("LAUCHING ARBITRATOR VALIDATION") v.count.Add(1) promise := stopwaiter.LaunchPromiseThread[validator.GoGlobalState](v, func(ctx context.Context) (validator.GoGlobalState, error) { defer v.count.Add(-1) @@ -208,139 +206,6 @@ func (v *ArbitratorSpawner) Room() int { return avail } -var launchTime = time.Now().Format("2006_01_02__15_04") - -//nolint:gosec -func (v *ArbitratorSpawner) writeToFile(ctx context.Context, input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) error { - outDirPath := filepath.Join(v.locator.RootPath(), v.config().OutputPath, launchTime, fmt.Sprintf("block_%d", input.Id)) - err := os.MkdirAll(outDirPath, 0755) - if err != nil { - return err - } - if ctx.Err() != nil { - return ctx.Err() - } - - rootPathAssign := "" - if executable, err := os.Executable(); err == nil { - rootPathAssign = "ROOTPATH=\"" + filepath.Dir(executable) + "\"\n" - } - cmdFile, err := os.OpenFile(filepath.Join(outDirPath, "run-prover.sh"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - return err - } - defer cmdFile.Close() - _, err = cmdFile.WriteString("#!/bin/bash\n" + - fmt.Sprintf("# expected output: batch %d, postion %d, hash %s\n", expOut.Batch, expOut.PosInBatch, expOut.BlockHash) + - "MACHPATH=\"" + v.locator.GetMachinePath(moduleRoot) + "\"\n" + - rootPathAssign + - "if (( $# > 1 )); then\n" + - " if [[ $1 == \"-m\" ]]; then\n" + - " MACHPATH=$2\n" + - " shift\n" + - " shift\n" + - " fi\n" + - "fi\n" + - "${ROOTPATH}/bin/prover ${MACHPATH}/replay.wasm") - if err != nil { - return err - } - if ctx.Err() != nil { - return ctx.Err() - } - - libraries := []string{"soft-float.wasm", "wasi_stub.wasm", "go_stub.wasm", "host_io.wasm", "brotli.wasm"} - for _, module := range libraries { - _, err = cmdFile.WriteString(" -l " + "${MACHPATH}/" + module) - if err != nil { - return err - } - } - _, err = cmdFile.WriteString(fmt.Sprintf(" --inbox-position %d --position-within-message %d --last-block-hash %s", input.StartState.Batch, input.StartState.PosInBatch, input.StartState.BlockHash)) - if err != nil { - return err - } - - for _, msg := range input.BatchInfo { - if ctx.Err() != nil { - return ctx.Err() - } - sequencerFileName := fmt.Sprintf("sequencer_%d.bin", msg.Number) - err = os.WriteFile(filepath.Join(outDirPath, sequencerFileName), msg.Data, 0644) - if err != nil { - return err - } - _, err = cmdFile.WriteString(" --inbox " + sequencerFileName) - if err != nil { - return err - } - } - - preimageFile, err := os.Create(filepath.Join(outDirPath, "preimages.bin")) - if err != nil { - return err - } - defer preimageFile.Close() - for ty, preimages := range input.Preimages { - _, err = preimageFile.Write([]byte{byte(ty)}) - if err != nil { - return err - } - for _, data := range preimages { - if ctx.Err() != nil { - return ctx.Err() - } - lenbytes := make([]byte, 8) - binary.LittleEndian.PutUint64(lenbytes, uint64(len(data))) - _, err := preimageFile.Write(lenbytes) - if err != nil { - return err - } - _, err = preimageFile.Write(data) - if err != nil { - return err - } - } - } - - _, err = cmdFile.WriteString(" --preimages preimages.bin") - if err != nil { - return err - } - - if input.HasDelayedMsg { - if ctx.Err() != nil { - return ctx.Err() - } - _, err = cmdFile.WriteString(fmt.Sprintf(" --delayed-inbox-position %d", input.DelayedMsgNr)) - if err != nil { - return err - } - filename := fmt.Sprintf("delayed_%d.bin", input.DelayedMsgNr) - err = os.WriteFile(filepath.Join(outDirPath, filename), input.DelayedMsg, 0644) - if err != nil { - return err - } - _, err = cmdFile.WriteString(fmt.Sprintf(" --delayed-inbox %s", filename)) - if err != nil { - return err - } - } - - _, err = cmdFile.WriteString(" \"$@\"\n") - if err != nil { - return err - } - return nil -} - -func (v *ArbitratorSpawner) WriteToFile(input *validator.ValidationInput, expOut validator.GoGlobalState, moduleRoot common.Hash) containers.PromiseInterface[struct{}] { - return stopwaiter.LaunchPromiseThread[struct{}](v, func(ctx context.Context) (struct{}, error) { - err := v.writeToFile(ctx, input, expOut, moduleRoot) - return struct{}{}, err - }) -} - func (v *ArbitratorSpawner) CreateExecutionRun(wasmModuleRoot common.Hash, input *validator.ValidationInput) containers.PromiseInterface[validator.ExecutionRun] { getMachine := func(ctx context.Context) (MachineInterface, error) { initialFrozenMachine, err := v.machineLoader.GetZeroStepMachine(ctx, wasmModuleRoot) diff --git a/validator/server_jit/jit_machine.go b/validator/server_jit/jit_machine.go index 06c451bda1..0748101277 100644 --- a/validator/server_jit/jit_machine.go +++ b/validator/server_jit/jit_machine.go @@ -30,9 +30,10 @@ type JitMachine struct { process *exec.Cmd stdin io.WriteCloser wasmMemoryUsageLimit int + maxExecutionTime time.Duration } -func createJitMachine(jitBinary string, binaryPath string, cranelift bool, wasmMemoryUsageLimit int, moduleRoot common.Hash, fatalErrChan chan error) (*JitMachine, error) { +func createJitMachine(jitBinary string, binaryPath string, cranelift bool, wasmMemoryUsageLimit int, maxExecutionTime time.Duration, _ common.Hash, fatalErrChan chan error) (*JitMachine, error) { invocation := []string{"--binary", binaryPath, "--forks"} if cranelift { invocation = append(invocation, "--cranelift") @@ -55,6 +56,7 @@ func createJitMachine(jitBinary string, binaryPath string, cranelift bool, wasmM process: process, stdin: stdin, wasmMemoryUsageLimit: wasmMemoryUsageLimit, + maxExecutionTime: maxExecutionTime, } return machine, nil } @@ -73,7 +75,7 @@ func (machine *JitMachine) prove( defer cancel() // ensure our cleanup functions run when we're done state := validator.GoGlobalState{} - timeout := time.Now().Add(60 * time.Second) + timeout := time.Now().Add(machine.maxExecutionTime) tcp, err := net.ListenTCP("tcp4", &net.TCPAddr{ IP: []byte{127, 0, 0, 1}, }) diff --git a/validator/server_jit/machine_loader.go b/validator/server_jit/machine_loader.go index cfa475370c..3d8b01367f 100644 --- a/validator/server_jit/machine_loader.go +++ b/validator/server_jit/machine_loader.go @@ -7,6 +7,7 @@ import ( "path/filepath" "runtime" "strings" + "time" "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/validator/server_common" @@ -52,14 +53,14 @@ type JitMachineLoader struct { stopped bool } -func NewJitMachineLoader(config *JitMachineConfig, locator *server_common.MachineLocator, fatalErrChan chan error) (*JitMachineLoader, error) { +func NewJitMachineLoader(config *JitMachineConfig, locator *server_common.MachineLocator, maxExecutionTime time.Duration, fatalErrChan chan error) (*JitMachineLoader, error) { jitPath, err := getJitPath() if err != nil { return nil, err } createMachineThreadFunc := func(ctx context.Context, moduleRoot common.Hash) (*JitMachine, error) { binPath := filepath.Join(locator.GetMachinePath(moduleRoot), config.ProverBinPath) - return createJitMachine(jitPath, binPath, config.JitCranelift, config.WasmMemoryUsageLimit, moduleRoot, fatalErrChan) + return createJitMachine(jitPath, binPath, config.JitCranelift, config.WasmMemoryUsageLimit, maxExecutionTime, moduleRoot, fatalErrChan) } return &JitMachineLoader{ MachineLoader: *server_common.NewMachineLoader[JitMachine](locator, createMachineThreadFunc), diff --git a/validator/server_jit/spawner.go b/validator/server_jit/spawner.go index d77317d218..f30b6e181a 100644 --- a/validator/server_jit/spawner.go +++ b/validator/server_jit/spawner.go @@ -3,10 +3,10 @@ package server_jit import ( "context" "fmt" + flag "github.com/spf13/pflag" "runtime" "sync/atomic" - - flag "github.com/spf13/pflag" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -18,8 +18,9 @@ import ( ) type JitSpawnerConfig struct { - Workers int `koanf:"workers" reload:"hot"` - Cranelift bool `koanf:"cranelift"` + Workers int `koanf:"workers" reload:"hot"` + Cranelift bool `koanf:"cranelift"` + MaxExecutionTime time.Duration `koanf:"max-execution-time" reload:"hot"` // TODO: change WasmMemoryUsageLimit to a string and use resourcemanager.ParseMemLimit WasmMemoryUsageLimit int `koanf:"wasm-memory-usage-limit"` @@ -30,6 +31,7 @@ type JitSpawnerConfigFecher func() *JitSpawnerConfig var DefaultJitSpawnerConfig = JitSpawnerConfig{ Workers: 0, Cranelift: true, + MaxExecutionTime: time.Minute * 10, WasmMemoryUsageLimit: 4294967296, // 2^32 WASM memeory limit } @@ -37,6 +39,7 @@ func JitSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Int(prefix+".workers", DefaultJitSpawnerConfig.Workers, "number of concurrent validation threads") f.Bool(prefix+".cranelift", DefaultJitSpawnerConfig.Cranelift, "use Cranelift instead of LLVM when validating blocks using the jit-accelerated block validator") f.Int(prefix+".wasm-memory-usage-limit", DefaultJitSpawnerConfig.WasmMemoryUsageLimit, "if memory used by a jit wasm exceeds this limit, a warning is logged") + f.Duration(prefix+".max-execution-time", DefaultJitSpawnerConfig.MaxExecutionTime, "if execution time used by a jit wasm exceeds this limit, a rpc error is returned") } type JitSpawner struct { @@ -52,7 +55,8 @@ func NewJitSpawner(locator *server_common.MachineLocator, config JitSpawnerConfi machineConfig := DefaultJitMachineConfig machineConfig.JitCranelift = config().Cranelift machineConfig.WasmMemoryUsageLimit = config().WasmMemoryUsageLimit - loader, err := NewJitMachineLoader(&machineConfig, locator, fatalErrChan) + maxExecutionTime := config().MaxExecutionTime + loader, err := NewJitMachineLoader(&machineConfig, locator, maxExecutionTime, fatalErrChan) if err != nil { return nil, err } diff --git a/validator/validation_entry.go b/validator/validation_entry.go index d340993fa2..4ec6919d3b 100644 --- a/validator/validation_entry.go +++ b/validator/validation_entry.go @@ -7,9 +7,8 @@ import ( ) type BatchInfo struct { - Number uint64 - BlockHash common.Hash - Data []byte + Number uint64 + Data []byte } type ValidationInput struct { diff --git a/validator/valnode/redis/consumer.go b/validator/valnode/redis/consumer.go index 2b025600cc..4392a3c91e 100644 --- a/validator/valnode/redis/consumer.go +++ b/validator/valnode/redis/consumer.go @@ -103,11 +103,13 @@ func (s *ValidationServer) Start(ctx_in context.Context) { case <-ready: // Wait until the stream exists and start consuming iteratively. } s.StopWaiter.CallIteratively(func(ctx context.Context) time.Duration { + log.Debug("waiting for request token", "cid", c.Id()) select { case <-ctx.Done(): return 0 case <-requestTokenQueue: } + log.Debug("got request token", "cid", c.Id()) req, err := c.Consume(ctx) if err != nil { log.Error("Consuming request", "error", err) @@ -115,10 +117,12 @@ func (s *ValidationServer) Start(ctx_in context.Context) { return 0 } if req == nil { + log.Debug("consumed nil", "cid", c.Id()) // There's nothing in the queue requestTokenQueue <- struct{}{} return time.Second } + log.Debug("forwarding work", "cid", c.Id(), "workid", req.ID) select { case <-ctx.Done(): case workQueue <- workUnit{req, moduleRoot}: @@ -131,7 +135,7 @@ func (s *ValidationServer) Start(ctx_in context.Context) { for { select { case <-readyStreams: - log.Trace("At least one stream is ready") + log.Debug("At least one stream is ready") return // Don't block Start if at least one of the stream is ready. case <-time.After(s.config.StreamTimeout): log.Error("Waiting for redis streams timed out") @@ -142,22 +146,31 @@ func (s *ValidationServer) Start(ctx_in context.Context) { } }) for i := 0; i < workers; i++ { + i := i s.StopWaiter.LaunchThread(func(ctx context.Context) { for { + log.Debug("waiting for work", "thread", i) var work workUnit select { case <-ctx.Done(): return case work = <-workQueue: } + log.Debug("got work", "thread", i, "workid", work.req.ID) valRun := s.spawner.Launch(work.req.Value, work.moduleRoot) res, err := valRun.Await(ctx) if err != nil { log.Error("Error validating", "request value", work.req.Value, "error", err) + work.req.Ack() } else { - if err := s.consumers[work.moduleRoot].SetResult(ctx, work.req.ID, res); err != nil { + log.Debug("done work", "thread", i, "workid", work.req.ID) + err := s.consumers[work.moduleRoot].SetResult(ctx, work.req.ID, res) + // Even in error we close ackNotifier as there's no retry mechanism here and closing it will alow other consumers to autoclaim + work.req.Ack() + if err != nil { log.Error("Error setting result for request", "id", work.req.ID, "result", res, "error", err) } + log.Debug("set result", "thread", i, "workid", work.req.ID) } select { case <-ctx.Done(): diff --git a/validator/valnode/validation_api.go b/validator/valnode/validation_api.go index a10d931dfc..ef3e1b2c49 100644 --- a/validator/valnode/validation_api.go +++ b/validator/valnode/validation_api.go @@ -118,15 +118,6 @@ func (a *ExecServerAPI) Start(ctx_in context.Context) { a.CallIteratively(a.removeOldRuns) } -func (a *ExecServerAPI) WriteToFile(ctx context.Context, jsonInput *server_api.InputJSON, expOut validator.GoGlobalState, moduleRoot common.Hash) error { - input, err := server_api.ValidationInputFromJson(jsonInput) - if err != nil { - return err - } - _, err = a.execSpawner.WriteToFile(input, expOut, moduleRoot).Await(ctx) - return err -} - var errRunNotFound error = errors.New("run not found") func (a *ExecServerAPI) getRun(id uint64) (validator.ExecutionRun, error) {