From 3776d80a2e4b972a8a5157cf876060c2b5d9dd94 Mon Sep 17 00:00:00 2001 From: Tim B <79199034+timbrinded@users.noreply.github.com> Date: Tue, 6 May 2025 21:20:02 +0100 Subject: [PATCH] =?UTF-8?q?test:=20=E2=9A=A1=EF=B8=8F=20CI=20Refactor=20(#?= =?UTF-8?q?59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eventually our CI will be required to run two private blockchains locally plus associated relayers. This PR is to prepare for this fate by improving run times and refactoring our existing CIs so they are a bit easier to reason about. ### Refactors - **_We now run ALL CIs on every PR!_** This is so that we decomplexify the logic around conditional builds and fetching built binaries from another source. This reduces the surface area of code we have to maintain at the cost of execution time - This penalty is ameliorated by a layered caching system. At best, it will be less than a minute to complete a build since everything will be cached. On GH runners this is about 6 minutes sadly. - We will no longer be at risk of important CIs being skipped erroneously which hide true failures. - Caching is a low-risk approach because at worst it has to build from scratch. A bad cache hit will never imply the wrong thing gets build since cargo is smart enough to just throw away any inappropriate build artefacts. - `setup-rust` action created so we have a unified way of setting up runner and unifying our approach to caching - Use a unique caching key for different activities and it will fallback to shared cache if no matches - we are using `mainnet` kurtosis config so that it works with relayer assumptions ### Additions - We can specify the ethereum block time via a new cli arg `--slot-time ` - We can specify arbitrary network_param args which get passed into the generated yaml - e.g. giving `bun cli --kurtosis-network-args="pet=cat food=fish" will add: ```yml network_params: # existing params... pet: cat food: fish ``` - We now have the ability to programmatically modify the yaml - This means we are back down to a single `minimal.yml` kurtosis config so we dont have to maintain changes between them - Flow is: `add new cli arg` -> `add if() block which mutates yaml` -> `profit` --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Facundo Farall <37149322+ffarall@users.noreply.github.com> --- .github/workflows/CI.yml | 38 ++++++ .../workflows/actions/setup-env/action.yml | 59 ++++++++ .github/workflows/foundry-tests.yml | 55 -------- .github/workflows/rust-lint.yml | 129 ------------------ .github/workflows/rust-tests.yml | 128 ----------------- .github/workflows/task-build-operator.yml | 57 ++++++++ .github/workflows/{e2e.yml => task-e2e.yml} | 41 ++++-- .github/workflows/task-foundry-tests.yml | 56 ++++++++ .github/workflows/task-rust-lint.yml | 72 ++++++++++ .github/workflows/task-rust-tests.yml | 88 ++++++++++++ .../{ts-build.yml => task-ts-build.yml} | 5 +- .../{ts-lint.yml => task-ts-lint.yml} | 5 +- .../{rust-audit.yml => weekly-audit.yml} | 1 + contracts/src/DataHavenTest.sol | 24 ++++ operator/Cargo.toml | 6 + test/bun.lock | 41 ++++-- test/cli/handlers/launch/index.ts | 26 ++-- .../handlers/launch/kurtosis.ts} | 63 ++++++--- test/cli/index.ts | 14 +- test/configs/kurtosis/minimal-with-bs.yaml | 20 --- test/configs/kurtosis/minimal.yaml | 30 ++-- test/package.json | 6 +- .../datahaven-integration-test-flow.md | 8 +- test/scripts/deploy-contracts.ts | 21 +-- test/scripts/setup-validators.ts | 22 +-- test/utils/index.ts | 9 +- test/utils/input.ts | 13 +- test/utils/shell.ts | 57 ++++++++ 28 files changed, 633 insertions(+), 461 deletions(-) create mode 100644 .github/workflows/CI.yml create mode 100644 .github/workflows/actions/setup-env/action.yml delete mode 100644 .github/workflows/foundry-tests.yml delete mode 100644 .github/workflows/rust-lint.yml delete mode 100644 .github/workflows/rust-tests.yml create mode 100644 .github/workflows/task-build-operator.yml rename .github/workflows/{e2e.yml => task-e2e.yml} (54%) create mode 100644 .github/workflows/task-foundry-tests.yml create mode 100644 .github/workflows/task-rust-lint.yml create mode 100644 .github/workflows/task-rust-tests.yml rename .github/workflows/{ts-build.yml => task-ts-build.yml} (96%) rename .github/workflows/{ts-lint.yml => task-ts-lint.yml} (95%) rename .github/workflows/{rust-audit.yml => weekly-audit.yml} (96%) create mode 100644 contracts/src/DataHavenTest.sol rename test/{scripts/launch-kurtosis.ts => cli/handlers/launch/kurtosis.ts} (66%) delete mode 100644 test/configs/kurtosis/minimal-with-bs.yaml create mode 100644 test/utils/shell.ts diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 00000000..4b842733 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,38 @@ +#! Main CI Specification for DataHaven Repository + +name: CI + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + branches: [main] + +concurrency: + group: pr-checks-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + # First Tier + build-operator: + uses: ./.github/workflows/task-build-operator.yml + secrets: inherit # For demonstrative purposes, we don't use any secrets yet. + ts-build: + uses: ./.github/workflows/task-ts-build.yml + ts-lint: + uses: ./.github/workflows/task-ts-lint.yml + unit-tests: + uses: ./.github/workflows/task-rust-tests.yml + contract-tests: + uses: ./.github/workflows/task-foundry-tests.yml + rust-lint: + uses: ./.github/workflows/task-rust-lint.yml + + # Second Tier + e2e-tests: + needs: [build-operator] + uses: ./.github/workflows/task-e2e.yml + with: + binary-hash: ${{ needs.build-operator.outputs.binary-hash }} diff --git a/.github/workflows/actions/setup-env/action.yml b/.github/workflows/actions/setup-env/action.yml new file mode 100644 index 00000000..7e3bad1e --- /dev/null +++ b/.github/workflows/actions/setup-env/action.yml @@ -0,0 +1,59 @@ +name: "Setup Rust Environment" +description: "Creates a Rust environment with the specified toolchain, cache, and dependencies" + +inputs: + cache-key: + description: "Cache key used to retrieve built data. Usually matches the profile of the build" + required: false + default: "cache" + + cache-targets: + description: "Whether to cache targets" + required: false + default: "true" + + + +runs: + using: "composite" + steps: + - name: Set Rust version + shell: bash + run: | + echo "BUILD_RUST_VERSION=$(rustc --version)" >> $GITHUB_ENV + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.9 + + - name: Set Rust caching env vars + if: github.event_name != 'release' && github.event_name != 'workflow_dispatch' + shell: bash + run: | + echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV + sccache --show-stats + + - name: Cache cargo + target + sccache2 + uses: Swatinem/rust-cache@v2 + with: + cache-targets: true + cache-all-crates: true + shared-key: ${{ runner.os }}-${{inputs.cache-key}}-${{ hashFiles('operator/rust-toolchain.toml') }}-${{ hashFiles('operator/Cargo.lock') }} + workspaces: | + operator -> target + cache-on-failure: true + cache-bin: true + cache-directories: | + ~/.cache/sccache + ~/.cargo/registry + ~/.cargo/git + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - uses: rui314/setup-mold@v1 + - name: Install libpq-dev + shell: bash + run: sudo apt-get update && sudo apt-get install -y libpq-dev libclang-dev + - name: Install Protoc + uses: arduino/setup-protoc@v3 diff --git a/.github/workflows/foundry-tests.yml b/.github/workflows/foundry-tests.yml deleted file mode 100644 index 6d5b9f4c..00000000 --- a/.github/workflows/foundry-tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Foundry Tests: CI for Foundry components (smart contracts for EigenLayer and Snowbridge interaction) -# -# Overview: -# 1. All Foundry Tests: Executes the full suite of Foundry tests found within the `./contracts` directory - -name: Foundry AVS Smart Contract Tests - -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - -env: - FOUNDRY_PROFILE: ci - -jobs: - test: - strategy: - fail-fast: false - matrix: - partition: [1] - - name: Foundry Tests - runs-on: ubuntu-latest - defaults: - run: - working-directory: contracts - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Show Forge version - run: | - forge --version - - - name: Run Forge fmt - run: | - forge fmt --check - id: fmt - - - name: Run Forge build - run: | - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/.github/workflows/rust-lint.yml b/.github/workflows/rust-lint.yml deleted file mode 100644 index 171296cd..00000000 --- a/.github/workflows/rust-lint.yml +++ /dev/null @@ -1,129 +0,0 @@ -# Lint and Format: CI for Rust components (DataHaven runtime and node Rust tests) -# -# Overview: -# 1. Check Rust Format: Check that the Rust code is formatted correctly -# 2. Check Rust Lint: Check that the Rust code is linted correctly - -name: Lint and Format - -on: - pull_request: - push: - branches: - - main - - perm-* - workflow_dispatch: - inputs: - pull_request: - description: set to pull_request number to execute on external pr - required: false - -env: - CARGO_TERM_COLOR: always - WORKING_DIR: operator - -jobs: - setup: - runs-on: ubuntu-latest - outputs: - node_changed: ${{ steps.node_check.outputs.changed }} - env: - SKIP_BUILD_LABEL_PRESENT: ${{ contains(github.event.pull_request.labels.*.name, 'skip-node-build') }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Check if Substrate Node needs rebuild - id: node_check - run: | - BASE_SHA="${{ github.event.pull_request.base.sha || github.event.before }}" - HEAD_SHA="${{ github.sha }}" - - if [[ "${{ env.SKIP_BUILD_LABEL_PRESENT }}" != "true" ]] && git diff --name-only $BASE_SHA $HEAD_SHA | grep -E '^operator/(client|node|pallets|runtime)/|^operator/Cargo\.toml$'; then - echo "changed=true" >> $GITHUB_OUTPUT - else - echo "Comparing changes from $BASE_SHA to $HEAD_SHA" - echo "changed=false" >> $GITHUB_OUTPUT - fi - - cargo-fmt: - needs: [ setup ] - if: needs.setup.outputs.node_changed == 'true' - name: "Check format with rustfmt" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ${{ env.WORKING_DIR }} - steps: - - uses: actions/checkout@v4 - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - - # ! If this action starts failing, it may be because of this, - # ! For now this is not needed, and makes the workflow slower - # - name: Install protoc - # run: | - # sudo apt-get update - # sudo apt-get install -y protobuf-compiler - # protoc --version - - - name: Run cargo fmt - run: cargo fmt --all -- --check - - check-rust-lint: - needs: [ setup ] - if: needs.setup.outputs.node_changed == 'true' - name: "Check lint with clippy" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ${{ env.WORKING_DIR }} - steps: - - uses: actions/checkout@v4 - - - name: Install protoc - run: | - sudo apt-get update - sudo apt-get install -y protobuf-compiler - protoc --version - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - - name: Cache dependencies - uses: Swatinem/rust-cache@v2 - - - name: Install libpq-dev - run: sudo apt-get update && sudo apt-get install -y libpq-dev - - - name: Install protoc - run: | - sudo apt-get update - sudo apt-get install -y protobuf-compiler - protoc --version - - - name: Run cargo clippy - run: SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo clippy --features try-runtime,runtime-benchmarks --locked - env: - RUSTFLAGS: -D warnings - - check-cargo-sort: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: ${{ env.WORKING_DIR }} - - steps: - - name: Check out the repository to the runner - uses: actions/checkout@v4 - - - name: Make script executable - run: chmod +x scripts/sort-cargo-deps.sh - - - name: Run the script on all Cargo.toml files - run: find . -name "Cargo.toml" -print0 | xargs -0 -n1 -I{} bash -c 'scripts/sort-cargo-deps.sh "{}" check' || exit 1 \ No newline at end of file diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml deleted file mode 100644 index aad17f18..00000000 --- a/.github/workflows/rust-tests.yml +++ /dev/null @@ -1,128 +0,0 @@ -# Rust Tests: CI for Rust components (DataHaven runtime and node Rust tests) -# -# Overview: -# 1. Prepare: This job handles the setup phase where the cargo nextest archive is created -# and uploaded to the workflow for use in the subsequent jobs -# 2. All Rust Tests: Executes the full suite of Rust tests across two partitions to -# to reduce total execution time. - -name: DataHave Operator Rust Tests - -on: - pull_request: - push: - branches: - - main - workflow_dispatch: - -jobs: - setup: - runs-on: ubuntu-latest - outputs: - node_changed: ${{ steps.node_check.outputs.changed }} - env: - SKIP_BUILD_LABEL_PRESENT: ${{ contains(github.event.pull_request.labels.*.name, 'skip-node-build') }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Check if Substrate Node needs rebuild - id: node_check - run: | - BASE_SHA="${{ github.event.pull_request.base.sha || github.event.before }}" - HEAD_SHA="${{ github.sha }}" - - if [[ "${{ env.SKIP_BUILD_LABEL_PRESENT }}" != "true" ]] && git diff --name-only $BASE_SHA $HEAD_SHA | grep -E '^operator/(client|node|pallets|runtime)/|^operator/Cargo\.toml$'; then - echo "changed=true" >> $GITHUB_OUTPUT - else - echo "Comparing changes from $BASE_SHA to $HEAD_SHA" - echo "changed=false" >> $GITHUB_OUTPUT - fi - - prepare: - needs: [setup] - if: needs.setup.outputs.node_changed == 'true' - name: Prepare artifacts for Rust tests - runs-on: ubuntu-latest - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" - CARGO_INCREMENTAL: "0" - CARGO_TERM_COLOR: always - defaults: - run: - working-directory: ./operator - steps: - - uses: actions/checkout@v4 - with: - # By default actions/checkout checks out a merge commit. Check out the PR head instead. - # https://github.com/actions/checkout#checkout-pull-request-head-commit-instead-of-merge-commit - ref: ${{ github.event.pull_request.head.sha }} - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.9 - - uses: rui314/setup-mold@v1 - - name: Install nextest - uses: taiki-e/install-action@nextest - # Install libpq-dev - - name: Install libpq-dev - run: sudo apt-get update && sudo apt-get install -y libpq-dev - - name: Install Protoc - uses: arduino/setup-protoc@v3 - - name: Build and archive tests - run: cargo nextest archive --archive-file nextest-archive.tar.zst - - name: Upload archive to workflow - uses: actions/upload-artifact@v4 - with: - name: nextest-archive - path: ./operator/nextest-archive.tar.zst - - all-rust-tests: - needs: [setup, prepare] - if: needs.setup.outputs.node_changed == 'true' - name: Run all Operator Rust tests (/w partitioning) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - partition: [1, 2] - defaults: - run: - working-directory: ./operator - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Install nextest - uses: taiki-e/install-action@nextest - - name: Download archive - uses: actions/download-artifact@v4 - with: - name: nextest-archive - - name: Run Tests for All Projects! - run: | - ~/.cargo/bin/cargo-nextest nextest run \ - --archive-file ../nextest-archive.tar.zst \ - --partition count:${{ matrix.partition }}/2 - - tests-result-checker: - name: Check tests were successful - needs: [setup, all-rust-tests] - if: always() - runs-on: ubuntu-latest - steps: - - name: Validate test results - run: | - echo "node_changed: ${{ needs.setup.outputs.node_changed }}" - echo "matrix result: ${{ needs.all-rust-tests.result }}" - - if [ "${{ needs.setup.outputs.node_changed }}" == "true" ]; then - if [ "${{ needs.all-rust-tests.result }}" != "success" ]; then - echo "Rust tests failed or were cancelled" - exit 1 - else - echo "Rust tests passed" - fi - else - echo "No relevant changes — skipping rust tests" - fi diff --git a/.github/workflows/task-build-operator.yml b/.github/workflows/task-build-operator.yml new file mode 100644 index 00000000..abf20fd6 --- /dev/null +++ b/.github/workflows/task-build-operator.yml @@ -0,0 +1,57 @@ +# Build Operator: CI for building the operator binary + +name: DataHave Operator Rust Tests + +on: + workflow_dispatch: + workflow_call: + outputs: + binary-hash: + description: "The hash of the operator binary" + value: ${{ jobs.build-node.outputs.binary-hash }} + +jobs: + build-node: + outputs: + binary-hash: ${{ steps.hash-binary.outputs.datahaven-node-hash }} + name: Build operator binary + runs-on: ubuntu-latest + env: + RUSTC_WRAPPER: "sccache" + CARGO_INCREMENTAL: "0" + CARGO_TERM_COLOR: always + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=mold" + defaults: + run: + working-directory: ./operator + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 1 + - uses: ./.github/workflows/actions/setup-env + with: + cache-key: CI + - name: Build node binary + run: cargo build --profile ci --locked + - name: Hash binary + id: hash-binary + run: | + TIMESTAMP=$(date +%s) + + BINARY_PATH=./target/ci/datahaven-node + HASH=$(echo "$TIMESTAMP" | cat - $BINARY_PATH | sha256sum | awk '{ print $1 }') + echo "datahaven-node-hash=$HASH" >> $GITHUB_OUTPUT + echo "Hash of the datahaven-node is: $HASH (with timestamp: $TIMESTAMP)" + + - name: Upload binary to workflow + uses: actions/upload-artifact@v4 + with: + name: datahaven-node-${{ steps.hash-binary.outputs.datahaven-node-hash }} + path: operator/target/ci/datahaven-node + retention-days: 1 + + - name: Build Stats + run: | + sccache --show-stats + diff --git a/.github/workflows/e2e.yml b/.github/workflows/task-e2e.yml similarity index 54% rename from .github/workflows/e2e.yml rename to .github/workflows/task-e2e.yml index 05c90d9b..a6a50279 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/task-e2e.yml @@ -8,15 +8,13 @@ name: E2E - Kurtosis Deploy and Verify on: - push: - branches: - - main - pull_request: workflow_dispatch: - -concurrency: - group: "tests-${{ github.head_ref }}" - cancel-in-progress: true + workflow_call: + inputs: + binary-hash: + description: "The hash of the operator binary" + required: true + type: string env: FOUNDRY_PROFILE: ci @@ -50,12 +48,31 @@ jobs: restore-keys: | ${{ runner.os }}-bun- - - uses: actions/cache@v4 + - name: Cache Foundry libraries + uses: actions/cache/restore@v4 with: - path: ~/.foundry/cache - key: ${{ runner.os }}-foundry-${{ hashFiles('**/foundry.toml') }} + path: ../contracts/lib + key: ${{ runner.os }}-foundry-libs-${{ hashFiles('.gitmodules') }} restore-keys: | - ${{ runner.os }}-foundry- + ${{ runner.os }}-foundry-libs- + + - name: Cache Foundry build artifacts + uses: actions/cache/restore@v4 + with: + path: | + ../contracts/out + ../contracts/cache + key: ${{ runner.os }}-foundry-build-${{ hashFiles('contracts/foundry.toml', 'contracts/**/*.sol') }} + restore-keys: | + ${{ runner.os }}-foundry-build- + + - name: Download operator binary + uses: actions/download-artifact@v4 + with: + name: datahaven-node-${{ inputs.binary-hash }} + path: operator/target/release/ + - run: chmod +x ../operator/target/release/datahaven-node + - run: ../operator/target/release/datahaven-node --help - run: bun install - run: bun start:e2e:ci diff --git a/.github/workflows/task-foundry-tests.yml b/.github/workflows/task-foundry-tests.yml new file mode 100644 index 00000000..41fffc94 --- /dev/null +++ b/.github/workflows/task-foundry-tests.yml @@ -0,0 +1,56 @@ +# Foundry Tests: CI for Foundry components (smart contracts for EigenLayer and Snowbridge interaction) +# +# Overview: +# 1. All Foundry Tests: Executes the full suite of Foundry tests found within the `./contracts` directory + +name: Foundry AVS Smart Contract Tests + +on: + workflow_dispatch: + workflow_call: + +env: + FOUNDRY_PROFILE: ci + +jobs: + test: + strategy: + fail-fast: false + matrix: + partition: [1] + + name: Foundry Tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: contracts + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Cache Foundry libraries + uses: actions/cache@v4 + with: + path: contracts/lib + key: ${{ runner.os }}-foundry-libs-${{ hashFiles('contracts/.gitmodules') }} + restore-keys: | + ${{ runner.os }}-foundry-libs- + + - name: Cache Foundry build artifacts + uses: actions/cache@v4 + with: + path: | + contracts/out + contracts/cache + key: ${{ runner.os }}-foundry-build-${{ hashFiles('contracts/foundry.toml', 'contracts/**/*.sol') }} + restore-keys: | + ${{ runner.os }}-foundry-build- + + - run: forge --version + - run: forge fmt --check + - run: forge build --sizes + - run: forge test -vvv diff --git a/.github/workflows/task-rust-lint.yml b/.github/workflows/task-rust-lint.yml new file mode 100644 index 00000000..26779a4d --- /dev/null +++ b/.github/workflows/task-rust-lint.yml @@ -0,0 +1,72 @@ +# Lint and Format: CI for Rust components (DataHaven runtime and node Rust tests) +# +# Overview: +# 1. Check Rust Format: Check that the Rust code is formatted correctly +# 2. Check Rust Lint: Check that the Rust code is linted correctly + +name: Lint and Format + +on: + workflow_call: + workflow_dispatch: + inputs: + pull_request: + description: set to pull_request number to execute on external pr + required: false + +env: + CARGO_TERM_COLOR: always + WORKING_DIR: operator + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=mold" + +jobs: + cargo-fmt: + name: "Check format with rustfmt" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKING_DIR }} + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/workflows/actions/setup-env + with: + cache-key: FMT + cache-targets: false + + - name: Run cargo fmt + run: cargo fmt --all -- --check + + check-rust-lint: + name: "Check lint with clippy" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ env.WORKING_DIR }} + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/workflows/actions/setup-env + with: + cache-key: LINT + + - name: Run cargo clippy + run: SKIP_WASM_BUILD=1 cargo clippy --features try-runtime,runtime-benchmarks --locked --release + + check-cargo-sort: + name: "Check Cargo sort" + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ${{ env.WORKING_DIR }} + + steps: + - name: Check out the repository to the runner + uses: actions/checkout@v4 + + - name: Make script executable + run: chmod +x scripts/sort-cargo-deps.sh + + - name: Run the script on all Cargo.toml files + run: find . -name "Cargo.toml" -print0 | xargs -0 -n1 -I{} bash -c 'scripts/sort-cargo-deps.sh "{}" check' || exit 1 diff --git a/.github/workflows/task-rust-tests.yml b/.github/workflows/task-rust-tests.yml new file mode 100644 index 00000000..3fd4e57c --- /dev/null +++ b/.github/workflows/task-rust-tests.yml @@ -0,0 +1,88 @@ +# Rust Tests: CI for Rust components (DataHaven runtime and node Rust tests) +# +# Overview: +# 1. Prepare: This job handles the setup phase where the cargo nextest archive is created +# and uploaded to the workflow for use in the subsequent jobs +# 2. All Rust Tests: Executes the full suite of Rust tests across two partitions to +# to reduce total execution time. + +name: DataHave Operator Rust Tests + +on: + workflow_dispatch: + workflow_call: + +jobs: + + prepare: + name: Prepare artifacts for Rust tests + runs-on: ubuntu-latest + env: + RUSTC_WRAPPER: "sccache" + CARGO_INCREMENTAL: "0" + CARGO_TERM_COLOR: always + RUSTFLAGS: "-C linker=clang -C link-arg=-fuse-ld=mold" + defaults: + run: + working-directory: ./operator + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + - uses: ./.github/workflows/actions/setup-env + with: + cache-key: "TEST" + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Build and archive tests + run: cargo nextest archive --archive-file nextest-archive.tar.zst + - name: Upload archive to workflow + uses: actions/upload-artifact@v4 + with: + name: nextest-archive + path: ./operator/nextest-archive.tar.zst + + all-rust-tests: + name: Run all Operator Rust tests (/w partitioning) + needs: [prepare] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + partition: [1, 2] + defaults: + run: + working-directory: ./operator + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Install nextest + uses: taiki-e/install-action@nextest + - name: Download archive + uses: actions/download-artifact@v4 + with: + name: nextest-archive + - name: Run Tests for All Projects! + run: | + ~/.cargo/bin/cargo-nextest nextest run \ + --archive-file ../nextest-archive.tar.zst \ + --partition count:${{ matrix.partition }}/2 + + tests-result-checker: + name: Check tests were successful + needs: [all-rust-tests] + runs-on: ubuntu-latest + steps: + - name: Validate test results + run: | + echo "matrix result: ${{ needs.all-rust-tests.result }}" + + if [ "${{ needs.all-rust-tests.result }}" != "success" ]; then + echo "Rust tests failed or were cancelled" + exit 1 + else + echo "Rust tests passed" + fi + diff --git a/.github/workflows/ts-build.yml b/.github/workflows/task-ts-build.yml similarity index 96% rename from .github/workflows/ts-build.yml rename to .github/workflows/task-ts-build.yml index 46bb0b73..6be6683c 100644 --- a/.github/workflows/ts-build.yml +++ b/.github/workflows/task-ts-build.yml @@ -1,11 +1,8 @@ name: TS Build on: - push: - branches: - - main - pull_request: workflow_dispatch: + workflow_call: jobs: generate-wagmi: diff --git a/.github/workflows/ts-lint.yml b/.github/workflows/task-ts-lint.yml similarity index 95% rename from .github/workflows/ts-lint.yml rename to .github/workflows/task-ts-lint.yml index a0a40b45..9420b225 100644 --- a/.github/workflows/ts-lint.yml +++ b/.github/workflows/task-ts-lint.yml @@ -1,11 +1,8 @@ name: TS Lint & Format on: - push: - branches: - - main - pull_request: workflow_dispatch: + workflow_call: jobs: typecheck: diff --git a/.github/workflows/rust-audit.yml b/.github/workflows/weekly-audit.yml similarity index 96% rename from .github/workflows/rust-audit.yml rename to .github/workflows/weekly-audit.yml index 87e8b89a..302ffaed 100644 --- a/.github/workflows/rust-audit.yml +++ b/.github/workflows/weekly-audit.yml @@ -6,6 +6,7 @@ name: Audit Rust dependencies on: + workflow_dispatch: schedule: - cron: '0 0 * * 0' push: diff --git a/contracts/src/DataHavenTest.sol b/contracts/src/DataHavenTest.sol new file mode 100644 index 00000000..701bbeed --- /dev/null +++ b/contracts/src/DataHavenTest.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0 +// This is a test contract for the DataHaven project +// It is deployed as pure bytecode in kurtosis +pragma solidity >=0.8.2 <0.9.0; + +contract DataHavenTest { + uint256 public number; + address public owner; + + constructor() { + number = 10; + owner = msg.sender; + } + + function decrement() external { + require(number > 0, "Number should be greater than 0"); + number = number - 1; + } + + function reset() external { + require(msg.sender == owner, "Only callable by owner!"); + number = 10; + } +} diff --git a/operator/Cargo.toml b/operator/Cargo.toml index b21337de..cd915465 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -206,3 +206,9 @@ fc-mapping-sync = { git = "https://github.com/polkadot-evm/frontier", branch = " fc-rpc = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false } fc-rpc-core = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false } fc-storage = { git = "https://github.com/polkadot-evm/frontier", branch = "stable2412", default-features = false } + +[profile.ci] +inherits = "release" +incremental = false +codegen-units = 16 +lto = false diff --git a/test/bun.lock b/test/bun.lock index d31bdce5..2cf75af1 100644 --- a/test/bun.lock +++ b/test/bun.lock @@ -23,6 +23,7 @@ "tiny-invariant": "^1.3.3", "viem": "^2.28.0", "wagmi": "^2.15.0", + "yaml": "^2.7.1", "zod": "^3.24.3", }, "devDependencies": { @@ -194,7 +195,7 @@ "@metamask/utils": ["@metamask/utils@8.5.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ=="], - "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], + "@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], "@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], @@ -932,6 +933,8 @@ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], @@ -942,6 +945,8 @@ "zustand": ["zustand@5.0.0", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ=="], + "@coinbase/wallet-sdk/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -956,6 +961,8 @@ "@metamask/sdk-communication-layer/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "@metamask/utils/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@reown/appkit/@walletconnect/types": ["@walletconnect/types@2.19.2", "", { "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "events": "3.3.0" } }, "sha512-/LZWhkVCUN+fcTgQUxArxhn2R8DF+LSd/6Wh9FnpjeK/Sdupx1EPS8okWG6WPAqq2f404PRoNAfQytQ82Xdl3g=="], @@ -992,8 +999,6 @@ "@walletconnect/time/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - "@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - "@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], @@ -1016,6 +1021,8 @@ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], @@ -1038,6 +1045,8 @@ "obj-multiplex/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], @@ -1060,6 +1069,8 @@ "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils": ["@metamask/utils@8.5.0", "", { "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", "@noble/hashes": "^1.3.1", "@scure/base": "^1.1.3", "@types/debug": "^4.1.7", "debug": "^4.3.4", "pony-cause": "^2.1.10", "semver": "^7.5.4", "uuid": "^9.0.1" } }, "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ=="], + "@metamask/rpc-errors/@metamask/utils/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@metamask/rpc-errors/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/sign-client": ["@walletconnect/sign-client@2.19.2", "", { "dependencies": { "@walletconnect/core": "2.19.2", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.19.2", "@walletconnect/utils": "2.19.2", "events": "3.3.0" } }, "sha512-a/K5PRIFPCjfHq5xx3WYKHAAF8Ft2I1LtxloyibqiQOoUtNLfKgFB1r8sdMvXM7/PADNPe4iAw4uSE6PrARrfg=="], @@ -1128,12 +1139,12 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.19.2", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.19.2", "@walletconnect/utils": "2.19.2", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-iu0mgLj51AXcKpdNj8+4EdNNBd/mkNjLEhZn6UMc/r7BM9WbmpPMEydA39WeRLbdLO4kbpmq4wTbiskI1rg+HA=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], @@ -1142,8 +1153,6 @@ "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.19.2", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.19.2", "@walletconnect/utils": "2.19.2", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-iu0mgLj51AXcKpdNj8+4EdNNBd/mkNjLEhZn6UMc/r7BM9WbmpPMEydA39WeRLbdLO4kbpmq4wTbiskI1rg+HA=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], @@ -1152,8 +1161,6 @@ "@reown/appkit/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.19.2", "", { "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", "@walletconnect/types": "2.19.2", "@walletconnect/utils": "2.19.2", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-iu0mgLj51AXcKpdNj8+4EdNNBd/mkNjLEhZn6UMc/r7BM9WbmpPMEydA39WeRLbdLO4kbpmq4wTbiskI1rg+HA=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/@noble/curves": ["@noble/curves@1.8.1", "", { "dependencies": { "@noble/hashes": "1.7.1" } }, "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ=="], "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/@noble/hashes": ["@noble/hashes@1.7.1", "", {}, "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ=="], @@ -1162,7 +1169,7 @@ "@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], - "@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + "@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1184,20 +1191,28 @@ "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "@walletconnect/utils/viem/ox/@noble/curves/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + "qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "qrcode/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + + "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], + + "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="], } } diff --git a/test/cli/handlers/launch/index.ts b/test/cli/handlers/launch/index.ts index a70a173d..f7e1267c 100644 --- a/test/cli/handlers/launch/index.ts +++ b/test/cli/handlers/launch/index.ts @@ -1,6 +1,5 @@ import type { Command } from "@commander-js/extra-typings"; import { deployContracts } from "scripts/deploy-contracts"; -import { launchKurtosis } from "scripts/launch-kurtosis"; import sendTxn from "scripts/send-txn"; import invariant from "tiny-invariant"; import { @@ -12,6 +11,7 @@ import { } from "utils"; import { checkDependencies } from "./checks"; import { performDatahavenOperations } from "./datahaven"; +import { launchKurtosis } from "./kurtosis"; import { LaunchedNetwork } from "./launchedNetwork"; import { performRelayerOperations } from "./relayer"; import { performSummaryOperations } from "./summary"; @@ -28,13 +28,16 @@ export interface LaunchOptions { relayer?: boolean; relayerBinPath?: string; skipCleaning?: boolean; + alwaysClean?: boolean; datahavenBinPath?: string; datahaven?: boolean; + kurtosisNetworkArgs?: string; + slotTime?: number; } export const BASE_SERVICES = [ "cl-1-lighthouse-reth", - "cl-1-lighthouse-reth", + "cl-2-lighthouse-reth", "el-1-reth-lighthouse", "el-2-reth-lighthouse", "dora" @@ -53,11 +56,7 @@ const launchFunction = async (options: LaunchOptions, launchedNetwork: LaunchedN await checkDependencies(); logger.trace("Launching Kurtosis enclave"); - await launchKurtosis({ - launchKurtosis: options.launchKurtosis, - blockscout: options.blockscout, - skipCleaning: options.skipCleaning - }); + await launchKurtosis(options); logger.trace("Kurtosis enclave launched"); logger.trace("Send test transaction"); @@ -116,7 +115,9 @@ const launchFunction = async (options: LaunchOptions, launchedNetwork: LaunchedN printDivider(); performSummaryOperations(options, launchedNetwork); - logger.debug("Launch function completed successfully"); + const fullEnd = performance.now(); + const fullMinutes = ((fullEnd - timeStart) / (1000 * 60)).toFixed(1); + logger.info(`Launch function completed successfully in ${fullMinutes} minutes`); }; export const launch = async (options: LaunchOptions) => { @@ -132,14 +133,7 @@ export const launch = async (options: LaunchOptions) => { export const launchPreActionHook = ( thisCmd: Command<[], LaunchOptions & { [key: string]: any }> ) => { - const { - blockscout, - verified, - fundValidators, - setupValidators, - updateValidatorSet, - deployContracts - } = thisCmd.opts(); + const { blockscout, verified, fundValidators, setupValidators, deployContracts } = thisCmd.opts(); if (verified && !blockscout) { thisCmd.error("--verified requires --blockscout to be set"); } diff --git a/test/scripts/launch-kurtosis.ts b/test/cli/handlers/launch/kurtosis.ts similarity index 66% rename from test/scripts/launch-kurtosis.ts rename to test/cli/handlers/launch/kurtosis.ts index 6948842c..6eff88f7 100644 --- a/test/scripts/launch-kurtosis.ts +++ b/test/cli/handlers/launch/kurtosis.ts @@ -1,4 +1,6 @@ import { $ } from "bun"; +import type { LaunchOptions } from "cli/handlers"; +import invariant from "tiny-invariant"; import { type KurtosisService, confirmWithTimeout, @@ -7,27 +9,18 @@ import { printDivider, printHeader } from "utils"; +import { parse, stringify } from "yaml"; /** * Launches a Kurtosis Ethereum network enclave for testing. * - * This function checks if a Kurtosis network is already running. If it is: - * - With `launchKurtosis: false` - keeps the existing enclave - * - With `launchKurtosis: true` - cleans and relaunches the enclave - * - With `launchKurtosis: undefined` - prompts the user to decide whether to relaunch - * - * If no network is running, it launches a new one. - * * @param options - Configuration options - * @param options.launchKurtosis - Whether to forcibly launch Kurtosis (true), keep existing (false), or prompt user (undefined) - * @param options.blockscout - Whether to add Blockscout service (true/undefined) or not (false) - * @param options.skipCleaning - Whether to skip cleaning Kurtosis (true) or not (false) * @returns Object containing success status and Docker services information */ export const launchKurtosis = async ( - options: { launchKurtosis?: boolean; blockscout?: boolean; skipCleaning?: boolean } = {} + options: LaunchOptions = {} ): Promise> => { - if (await checkKurtosisRunning()) { + if ((await checkKurtosisRunning()) && !options.alwaysClean) { logger.info("â„šī¸ Kurtosis network is already running."); logger.trace("Checking if launchKurtosis option was set via flags"); @@ -73,10 +66,9 @@ export const launchKurtosis = async ( } logger.info("🚀 Starting Kurtosis enclave..."); - const configFile = - options.blockscout === true - ? "configs/kurtosis/minimal-with-bs.yaml" - : "configs/kurtosis/minimal.yaml"; + + const configFile = await modifyConfig(options, "configs/kurtosis/minimal.yaml"); + logger.info(`Using Kurtosis config file: ${configFile}`); const { stderr, stdout, exitCode } = @@ -107,3 +99,42 @@ const checkKurtosisRunning = async (): Promise => { const text = await $`kurtosis enclave ls | grep "datahaven-ethereum" | grep RUNNING`.text(); return text.length > 0; }; + +const modifyConfig = async (options: LaunchOptions, configFile: string) => { + const outputDir = "tmp/configs"; + logger.debug(`Ensuring output directory exists: ${outputDir}`); + await $`mkdir -p ${outputDir}`.quiet(); + + const file = Bun.file(configFile); + invariant(file, `❌ Config file ${configFile} not found`); + + const config = await file.text(); + logger.debug(`Parsing config at ${configFile}`); + logger.trace(config); + + const parsedConfig = parse(config); + + if (options.blockscout) { + parsedConfig.additional_services.push("blockscout"); + } + + if (options.slotTime) { + parsedConfig.network_params.seconds_per_slot = options.slotTime; + } + + if (options.kurtosisNetworkArgs) { + logger.debug(`Using custom Kurtosis network args: ${options.kurtosisNetworkArgs}`); + const args = options.kurtosisNetworkArgs.split(" "); + for (const arg of args) { + const [key, value] = arg.split("="); + parsedConfig.network_params[key] = value; + } + } + + logger.trace(parsedConfig); + const outputFile = `${outputDir}/modified-config.yaml`; + logger.debug(`Modified config saving to ${outputFile}`); + + await Bun.write(outputFile, stringify(parsedConfig)); + return outputFile; +}; diff --git a/test/cli/index.ts b/test/cli/index.ts index d3288e6a..0b9ae985 100644 --- a/test/cli/index.ts +++ b/test/cli/index.ts @@ -1,7 +1,16 @@ #!/usr/bin/env bun -import { Command } from "@commander-js/extra-typings"; +import { Command, InvalidArgumentError } from "@commander-js/extra-typings"; import { launch, launchPreActionHook } from "./handlers"; +// Function to parse integer +function parseIntValue(value: string): number { + const parsedValue = Number.parseInt(value, 10); + if (Number.isNaN(parsedValue)) { + throw new InvalidArgumentError("Not a number."); + } + return parsedValue; +} + // So far we only have the launch command // we can expand this to more commands in the future const program = new Command() @@ -14,13 +23,16 @@ const program = new Command() .option("-u, --update-validator-set", "Update validator set") .option("--no-update-validator-set", "Skip update validator set") .option("-b, --blockscout", "Enable Blockscout") + .option("--slot-time ", "Set slot time in seconds", parseIntValue) .option("--datahaven", "Enable Datahaven network to be launched") + .option("--kurtosis-network-args ", "CustomKurtosis network args") .option( "--datahaven-bin-path ", "Path to the datahaven binary", "../operator/target/release/datahaven-node" ) .option("-v, --verified", "Verify smart contracts with Blockscout") + .option("--always-clean", "Always clean Kurtosis", false) .option("-q, --skip-cleaning", "Skip cleaning Kurtosis") .option("-r, --relayer", "Enable Relayer") .option( diff --git a/test/configs/kurtosis/minimal-with-bs.yaml b/test/configs/kurtosis/minimal-with-bs.yaml deleted file mode 100644 index 81474637..00000000 --- a/test/configs/kurtosis/minimal-with-bs.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Ethereum Private Testnet Configuration -# This configuration file is used with the ethPandaOps Ethereum Package for Kurtosis -# (https://github.com/ethpandaops/ethereum-package) -# -# Purpose: Sets up a minimal Ethereum testnet with multiple execution and consensus clients -# Usage: kurtosis run github.com/ethpandaops/ethereum-package --args-file configs/kurtosis/minimal-with-bs.yaml - -participants: - - el_type: reth - cl_type: lighthouse - count: 2 - -additional_services: - - dora - - blockscout - -network_params: - preset: minimal - num_validator_keys_per_node: 128 - prefunded_accounts: '{"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": {"balance": "10ETH"}, "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": {"balance": "10ETH"}, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": {"balance": "10ETH"},"0x976ea74026e726554db657fa54763abd0c3a0aa9": {"balance": "10ETH"}}' \ No newline at end of file diff --git a/test/configs/kurtosis/minimal.yaml b/test/configs/kurtosis/minimal.yaml index b435ebe2..4e15b696 100644 --- a/test/configs/kurtosis/minimal.yaml +++ b/test/configs/kurtosis/minimal.yaml @@ -1,10 +1,3 @@ -# Ethereum Private Testnet Configuration -# This configuration file is used with the ethPandaOps Ethereum Package for Kurtosis -# (https://github.com/ethpandaops/ethereum-package) -# -# Purpose: Sets up a minimal Ethereum testnet with multiple execution and consensus clients -# Usage: kurtosis run github.com/ethpandaops/ethereum-package --args-file configs/kurtosis/minimal.yaml - participants: - el_type: reth cl_type: lighthouse @@ -14,6 +7,25 @@ additional_services: - dora network_params: - preset: minimal + preset: mainnet + seconds_per_slot: 2 num_validator_keys_per_node: 128 - prefunded_accounts: '{"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": {"balance": "10ETH"}, "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": {"balance": "10ETH"}, "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": {"balance": "10ETH"},"0x976ea74026e726554db657fa54763abd0c3a0aa9": {"balance": "10ETH"}}' \ No newline at end of file + prefunded_accounts: '{ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": {"balance": "10ETH"}, + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": {"balance": "10ETH"}, + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": {"balance": "10ETH"}, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": {"balance": "10ETH"} + }' + # Preloaded with DataHavenTest contract + additional_preloaded_contracts: | + { + "0x1111111111111111111111111111111111111111": { + "balance": "0ETH", + "code": "0x608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80632baeceb71461004e5780638381f58a146100585780638da5cb5b14610073578063d826f88f1461009e575b5f5ffd5b6100566100a6565b005b6100605f5481565b6040519081526020015b60405180910390f35b600154610086906001600160a01b031681565b6040516001600160a01b03909116815260200161006a565b61005661010d565b5f5f54116100fb5760405162461bcd60e51b815260206004820152601f60248201527f4e756d6265722073686f756c642062652067726561746572207468616e20300060448201526064015b60405180910390fd5b60015f54610109919061016d565b5f55565b6001546001600160a01b031633146101675760405162461bcd60e51b815260206004820152601760248201527f4f6e6c792063616c6c61626c65206279206f776e65722100000000000000000060448201526064016100f2565b600a5f55565b8181038181111561018c57634e487b7160e01b5f52601160045260245ffd5b9291505056fea2646970667358221220ac5899491afd834afd223fd632497d1c0c7593961eda22f04c58db4b504999cf64736f6c634300081c0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + }, + "nonce": "1" + } + } diff --git a/test/package.json b/test/package.json index 57d1b8d2..33c4a7de 100644 --- a/test/package.json +++ b/test/package.json @@ -10,9 +10,8 @@ "build:docker:relayer": "bun -e \"import build from './scripts/snowbridge-relayer.ts'; build()\"", "generate:wagmi": "wagmi generate", "generate:snowbridge-cfgs": "bun -e \"import {generateSnowbridgeConfigs} from './scripts/gen-snowbridge-cfgs.ts'; await generateSnowbridgeConfigs()\"", - "start:e2e:verified": "bun cli --verified --blockscout --deploy-contracts", - "start:e2e:minimal": "bun cli", - "start:e2e:ci": "bun cli -d --setup-validators --update-validator-set --fund-validators", + "start:e2e:verified": "bun cli --verified --blockscout --deploy-contracts --setup-validators --update-validator-set --fund-validators --slot-time 1", + "start:e2e:ci": "bun cli -d --setup-validators --update-validator-set --fund-validators --always-clean --slot-time 2", "start:e2e:minrelayer": "bun cli --relayer -d --no-setup-validators --no-update-validator-set --no-fund-validators --datahaven", "stop:e2e": "pkill datahaven ; kurtosis enclave stop datahaven-ethereum && kurtosis clean && kurtosis engine stop && docker container prune -f", "stop:e2e:verified": "bun stop:e2e", @@ -48,6 +47,7 @@ "tiny-invariant": "^1.3.3", "viem": "^2.28.0", "wagmi": "^2.15.0", + "yaml": "^2.7.1", "zod": "^3.24.3" }, "trustedDependencies": [ diff --git a/test/resources/datahaven-integration-test-flow.md b/test/resources/datahaven-integration-test-flow.md index c9baa9b2..8f1ecdf4 100644 --- a/test/resources/datahaven-integration-test-flow.md +++ b/test/resources/datahaven-integration-test-flow.md @@ -34,15 +34,9 @@ The first step involves setting up the testing infrastructure using Kurtosis, a ```bash # Start the E2E CLI environment with the minimal configuration bun cli -# Alternative -bun start:e2e:minimal # Start the E2E CLI environment with Blockscout and verified contracts bun start:e2e:verified - -# Behind the scenes both commands run: -bun run scripts/launch-kurtosis.ts -# And then continue setting up the environment with the next steps. ``` ## 2. Ethereum-side Contract Deployment @@ -85,12 +79,14 @@ In this phase, we register validators as operators in EigenLayer and sync the va ### Steps 1. **Fund Validators with Tokens** + - Use `fund-validators.ts` script to fund validators with necessary tokens - Transfers 5% of creator's tokens to each validator - Transfers 1% of creator's ETH to validators with zero balance - Ensures validators have sufficient funds for operations 2. **Register Operators in EigenLayer** + - Use `setup-validators.ts` script to register validators - Deposits stake and registers for operator sets - Sets up the validator set in the Ethereum side diff --git a/test/scripts/deploy-contracts.ts b/test/scripts/deploy-contracts.ts index 09d2192f..39dddc9d 100644 --- a/test/scripts/deploy-contracts.ts +++ b/test/scripts/deploy-contracts.ts @@ -1,6 +1,6 @@ import { $ } from "bun"; import invariant from "tiny-invariant"; -import { confirmWithTimeout, logger, printHeader } from "utils"; +import { confirmWithTimeout, logger, printHeader, runShellCommandWithLogger } from "utils"; interface DeployContractsOptions { rpcUrl: string; @@ -63,12 +63,7 @@ export const deployContracts = async (options: DeployContractsOptions): Promise< } logger.debug(buildStdout.toString()); - // Get forge path - const { stdout: forgePath } = await $`which forge`.quiet(); - const forgeExecutable = forgePath.toString().trim(); - - // Prepare deployment command - let deployCommand = `${forgeExecutable} script script/deploy/DeployLocal.s.sol --rpc-url ${rpcUrl} --color never -vv --no-rpc-rate-limit --non-interactive --broadcast`; + let deployCommand = `forge script script/deploy/DeployLocal.s.sol --rpc-url ${rpcUrl} --color never -vv --no-rpc-rate-limit --non-interactive --broadcast`; if (verified && blockscoutBackendUrl) { deployCommand += ` --verify --verifier blockscout --verifier-url ${blockscoutBackendUrl}/api/ --delay 0`; @@ -77,14 +72,8 @@ export const deployContracts = async (options: DeployContractsOptions): Promise< logger.info("âŗ Deploying contracts (this might take a few minutes)..."); - const { exitCode: deployExitCode, stderr: deployStderr } = await $`sh -c ${deployCommand}` - .cwd("../contracts") - .nothrow(); - - if (deployExitCode !== 0) { - logger.error(deployStderr.toString()); - throw Error("❌ Contracts have failed to deploy properly."); - } + // Using custom shell command to improve logging with forge's stdoutput + await runShellCommandWithLogger(deployCommand, { cwd: "../contracts" }); logger.success("Contracts deployed successfully"); return true; @@ -121,7 +110,6 @@ if (import.meta.main) { } } - // Check required parameters if (!options.rpcUrl) { console.error("Error: --rpc-url parameter is required"); process.exit(1); @@ -132,7 +120,6 @@ if (import.meta.main) { process.exit(1); } - // Run deployment deployContracts({ rpcUrl: options.rpcUrl, verified: options.verified, diff --git a/test/scripts/setup-validators.ts b/test/scripts/setup-validators.ts index b834e0b0..cfb1c5d2 100644 --- a/test/scripts/setup-validators.ts +++ b/test/scripts/setup-validators.ts @@ -1,9 +1,8 @@ import fs from "node:fs"; import path from "node:path"; -// Setup of validators for DataHaven import { $ } from "bun"; import invariant from "tiny-invariant"; -import { confirmWithTimeout, logger, printHeader } from "../utils/index"; +import { confirmWithTimeout, logger, printHeader, runShellCommandWithLogger } from "../utils/index"; interface SetupValidatorsOptions { rpcUrl: string; @@ -113,16 +112,11 @@ export const setupValidators = async (options: SetupValidatorsOptions): Promise< const validators = config.validators; logger.info(`Found ${validators.length} validators to register`); - // Get forge path - const { stdout: forgePath } = await $`which forge`.quiet(); - const forgeExecutable = forgePath.toString().trim(); - // Iterate through all validators to register them for (let i = 0; i < validators.length; i++) { const validator = validators[i]; logger.info(`Setting up validator ${i} (${validator.publicKey})`); - // Setting up the environment variables directly const env = { ...process.env, NETWORK: networkName, @@ -133,20 +127,10 @@ export const setupValidators = async (options: SetupValidatorsOptions): Promise< }; // Prepare command to register validator - const signupCommand = `${forgeExecutable} script script/transact/SignUpValidator.s.sol --rpc-url ${rpcUrl} --broadcast --no-rpc-rate-limit --non-interactive`; - + const signupCommand = `forge script script/transact/SignUpValidator.s.sol --rpc-url ${rpcUrl} --broadcast --no-rpc-rate-limit --non-interactive`; logger.debug(`Running command: ${signupCommand}`); - // Run with environment variables directly passed to the environment - const { exitCode, stderr } = await $`sh -c ${signupCommand}` - .cwd("../contracts") - .env(env) - .nothrow(); - - if (exitCode !== 0) { - logger.error(`Failed to register validator ${validator.publicKey}: ${stderr.toString()}`); - continue; - } + await runShellCommandWithLogger(signupCommand, { env, cwd: "../contracts" }); logger.success(`Successfully registered validator ${validator.publicKey}`); } diff --git a/test/utils/index.ts b/test/utils/index.ts index 7a91c9d7..a803da0f 100644 --- a/test/utils/index.ts +++ b/test/utils/index.ts @@ -1,10 +1,11 @@ export * from "./blockscout"; export * from "./constants"; +export * from "./contracts"; export * from "./docker"; export * from "./input"; -export * from "./logger"; -export * from "./rpc"; -export * from "./viem"; export * from "./kurtosis"; +export * from "./logger"; export * from "./parser"; -export * from "./contracts"; +export * from "./rpc"; +export * from "./shell"; +export * from "./viem"; diff --git a/test/utils/input.ts b/test/utils/input.ts index 2f26a222..66fa68d0 100644 --- a/test/utils/input.ts +++ b/test/utils/input.ts @@ -40,7 +40,7 @@ export const timeoutConfirm = createPrompt((cfg, clearInterval(id); done(cfg.default ?? true); } - }, 200); + }, 500); return () => clearInterval(id); }, []); @@ -64,7 +64,7 @@ export const timeoutConfirm = createPrompt((cfg, const main = `${prefix} ${theme.style.message(cfg.message, status)} \ ${defaultBadge} ${input}`; - const border = chalk.yellow("=".repeat(cfg.message.length + 40)); + const border = chalk.yellow("=".repeat(80)); const hint = theme.style.help( chalk.magenta( `⏱ Will default to ${chalk.bold(cfg.default ? "YES" : "NO")} in ${chalk.bold((left / 1000).toFixed(0))}s` @@ -77,12 +77,14 @@ ${main} ${border}`; }); -export const confirmWithTimeout = ( +export const confirmWithTimeout = async ( question: string, defaultValue: boolean, timeoutSeconds: number -) => - timeoutConfirm({ +) => { + await Bun.sleep(50); //debounce + + return timeoutConfirm({ message: question, default: defaultValue, timeoutMs: timeoutSeconds * 1000, @@ -93,3 +95,4 @@ export const confirmWithTimeout = ( } } }); +}; diff --git a/test/utils/shell.ts b/test/utils/shell.ts new file mode 100644 index 00000000..646634ec --- /dev/null +++ b/test/utils/shell.ts @@ -0,0 +1,57 @@ +import { existsSync } from "node:fs"; +import { spawn } from "bun"; +import { logger } from "./logger"; + +export const runShellCommandWithLogger = async ( + command: string, + options?: { cwd?: string; env?: object } +) => { + const { cwd = ".", env = {} } = options || {}; + + try { + if (!existsSync(cwd)) { + logger.error("❌ CWD does not exist:", cwd); + throw new Error("❌ CWD does not exist"); + } + + const proc = spawn(["sh", "-c", command], { + cwd, + stdout: "pipe", + stderr: "pipe", + env: { + ...process.env, + ...env + } + }); + + const stdoutReader = proc.stdout.getReader(); + const stderrReader = proc.stderr.getReader(); + + const readStream = async ( + reader: typeof stdoutReader | typeof stderrReader, + streamName: string + ) => { + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const text = new TextDecoder().decode(value); + const trimmedText = text.trim(); + logger.info(trimmedText.includes("\n") ? `\n${trimmedText}` : trimmedText); + } + } catch (err) { + logger.error(`Error reading from ${streamName} stream:`, err); + } finally { + reader.releaseLock(); + } + }; + + Promise.all([readStream(stdoutReader, "stdout"), readStream(stderrReader, "stderr")]); + + await proc.exited; + } catch (err) { + logger.error("❌ Error running shell command:", command, "in", cwd); + logger.error(err); + throw err; + } +};