diff --git a/.github/workflows/build_contract.yml b/.github/workflows/build_contract.yml index aefd0f6abe..f15307fa99 100644 --- a/.github/workflows/build_contract.yml +++ b/.github/workflows/build_contract.yml @@ -17,7 +17,7 @@ permissions: jobs: build-contract: name: "Build contract reproducibly" - runs-on: warp-ubuntu-2404-x64-16x + runs-on: warp-ubuntu-2604-x64-16x steps: - name: Checkout repository diff --git a/.github/workflows/bump-nearcore.yml b/.github/workflows/bump-nearcore.yml index ee315442a1..0a6b270f80 100644 --- a/.github/workflows/bump-nearcore.yml +++ b/.github/workflows/bump-nearcore.yml @@ -18,7 +18,7 @@ permissions: jobs: bump: - runs-on: ubuntu-latest + runs-on: warp-ubuntu-2604-x64-2x steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: diff --git a/.github/workflows/changes.yml b/.github/workflows/changes.yml index a8dacd9e0e..2615d7f101 100644 --- a/.github/workflows/changes.yml +++ b/.github/workflows/changes.yml @@ -16,7 +16,7 @@ on: jobs: detect: - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x permissions: contents: read pull-requests: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 878ae22960..686a0ab241 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: name: "Build MPC Node TEE Docker image" needs: changes if: ${{ !cancelled() && needs.changes.outputs.code_changed != 'false' }} - runs-on: warp-ubuntu-2404-x64-16x + runs-on: warp-ubuntu-2604-x64-16x timeout-minutes: 60 permissions: contents: read @@ -48,14 +48,13 @@ jobs: with: persist-credentials: false - - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + - name: Allow unprivileged user namespaces (needed by repro-env) run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Install repro-env run: | - wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' - echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - - sudo install -m755 repro-env -t /usr/bin + sudo apt-get update + sudo apt-get install -y repro-env - name: Build MPC Node binary and image run: | @@ -71,7 +70,7 @@ jobs: name: "Build Rust Launcher Docker image and verify" needs: changes if: ${{ !cancelled() && needs.changes.outputs.code_changed != 'false' }} - runs-on: warp-ubuntu-2404-x64-8x + runs-on: warp-ubuntu-2604-x64-8x timeout-minutes: 60 permissions: contents: read @@ -82,24 +81,18 @@ jobs: with: persist-credentials: false - - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + - name: Allow unprivileged user namespaces (needed by repro-env) run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Install build dependencies run: | sudo apt-get update - sudo apt-get install -y skopeo liblzma-dev podman - - - name: Install repro-env - run: | - wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' - echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - - sudo install -m755 repro-env -t /usr/bin + sudo apt-get install -y liblzma-dev repro-env - - name: Build Rust launcher docker image and verify its hash + - name: Build Rust launcher docker image shell: bash run: | - ./scripts/build-and-verify-rust-launcher-docker-image.sh + ./deployment/build-images.sh --rust-launcher - name: Run Rust launcher non-TEE runtime check shell: bash @@ -114,7 +107,7 @@ jobs: # Skipping at the job level only registers unexpanded template name and # required checks will be stuck. if: ${{ !cancelled() }} - runs-on: ${{ matrix.runner || 'warp-ubuntu-2404-x64-16x' }} + runs-on: ${{ matrix.runner || 'warp-ubuntu-2604-x64-16x' }} timeout-minutes: 60 permissions: contents: read @@ -127,7 +120,7 @@ jobs: - group: "contract" nextest-args: "--profile=ci-contract -p mpc-contract" extra-tools: true - runner: warp-ubuntu-2404-x64-32x + runner: warp-ubuntu-2604-x64-32x - group: "other" nextest-args: "--profile=ci-other" extra-tools: true @@ -166,7 +159,7 @@ jobs: name: "MPC contract reproducible build" needs: changes if: ${{ !cancelled() && needs.changes.outputs.code_changed != 'false' }} - runs-on: warp-ubuntu-2404-x64-16x + runs-on: warp-ubuntu-2604-x64-16x timeout-minutes: 60 permissions: contents: read @@ -201,7 +194,7 @@ jobs: name: "Nix build mpc-contract" needs: changes if: ${{ !cancelled() && needs.changes.outputs.code_changed != 'false' }} - runs-on: warp-ubuntu-2404-x64-16x + runs-on: warp-ubuntu-2604-x64-16x timeout-minutes: 60 permissions: contents: read @@ -231,7 +224,7 @@ jobs: fast-ci-checks: name: "Fast CI checks" - runs-on: warp-ubuntu-2404-x64-8x + runs-on: warp-ubuntu-2604-x64-8x permissions: contents: read steps: @@ -262,7 +255,7 @@ jobs: name: "MPC E2E tests" needs: changes if: ${{ !cancelled() && needs.changes.outputs.code_changed != 'false' }} - runs-on: warp-ubuntu-2404-x64-32x + runs-on: warp-ubuntu-2604-x64-32x timeout-minutes: 60 permissions: contents: read @@ -317,7 +310,7 @@ jobs: name: "Extra CI checks" needs: changes if: ${{ !cancelled() && needs.changes.outputs.code_changed != 'false' }} - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x permissions: contents: read steps: @@ -338,7 +331,7 @@ jobs: check-todo-closed-issues: name: "Check TODOs for issues closed by this PR" if: github.event_name == 'pull_request' - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x permissions: contents: read pull-requests: read diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 29d09ea046..5bc731df3b 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -19,7 +19,7 @@ jobs: (contains(github.event.comment.body, '@claude review') || contains(github.event.comment.body, '@claude code review'))) - runs-on: ubuntu-latest + runs-on: warp-ubuntu-2604-x64-2x permissions: contents: read pull-requests: read diff --git a/.github/workflows/docker_build_node.yml b/.github/workflows/docker_build_node.yml index 0960ff7c48..37f4389d93 100644 --- a/.github/workflows/docker_build_node.yml +++ b/.github/workflows/docker_build_node.yml @@ -15,7 +15,7 @@ on: jobs: build-and-push-images: name: "Build and push Docker node image with commit hash" - runs-on: warp-ubuntu-2404-x64-16x + runs-on: warp-ubuntu-2604-x64-16x environment: production permissions: contents: read @@ -32,19 +32,13 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PAT }} - - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + - name: Allow unprivileged user namespaces (needed by repro-env) run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Install repro-env - run: | - wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' - echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - - sudo install -m755 repro-env -t /usr/bin - - - name: Install skopeo run: | sudo apt-get update - sudo apt-get install -y skopeo + sudo apt-get install -y repro-env - name: Build and push node image run: | diff --git a/.github/workflows/docker_build_node_gcp.yml b/.github/workflows/docker_build_node_gcp.yml index e081f145bc..d3e214548c 100644 --- a/.github/workflows/docker_build_node_gcp.yml +++ b/.github/workflows/docker_build_node_gcp.yml @@ -15,7 +15,7 @@ on: jobs: build-and-push-images: name: "Build and push Docker node gcp image with commit hash" - runs-on: warp-ubuntu-2404-x64-16x + runs-on: warp-ubuntu-2604-x64-16x environment: production permissions: contents: read @@ -32,19 +32,13 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PAT }} - - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + - name: Allow unprivileged user namespaces (needed by repro-env) run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Install repro-env - run: | - wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' - echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - - sudo install -m755 repro-env -t /usr/bin - - - name: Install skopeo run: | sudo apt-get update - sudo apt-get install -y skopeo + sudo apt-get install -y repro-env - name: Build and push node gcp image run: | diff --git a/.github/workflows/docker_build_rust_launcher.yml b/.github/workflows/docker_build_rust_launcher.yml index 59f58ed44d..ae1c687a01 100644 --- a/.github/workflows/docker_build_rust_launcher.yml +++ b/.github/workflows/docker_build_rust_launcher.yml @@ -15,7 +15,7 @@ on: jobs: build-and-push-images: name: "Build and push Rust launcher Docker image with commit hash" - runs-on: warp-ubuntu-2404-x64-8x + runs-on: warp-ubuntu-2604-x64-8x environment: production permissions: contents: read @@ -26,19 +26,13 @@ jobs: with: persist-credentials: false - - name: Allow unprivileged user namespaces (needed by repro-env/podman on Ubuntu 24.04) + - name: Allow unprivileged user namespaces (needed by repro-env) run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Install build dependencies run: | sudo apt-get update - sudo apt-get install -y skopeo liblzma-dev podman - - - name: Install repro-env - run: | - wget 'https://github.com/kpcyrd/repro-env/releases/download/v0.4.3/repro-env' - echo '2a00b21ac5e990e0c6a0ccbf3b91e34a073660d1f4553b5f3cda2b09cc4d4d8a repro-env' | sha256sum -c - - sudo install -m755 repro-env -t /usr/bin + sudo apt-get install -y liblzma-dev repro-env - name: Login to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 diff --git a/.github/workflows/docker_launcher_release.yml b/.github/workflows/docker_launcher_release.yml index 13cef43204..8d41375133 100644 --- a/.github/workflows/docker_launcher_release.yml +++ b/.github/workflows/docker_launcher_release.yml @@ -28,7 +28,7 @@ on: jobs: retag-launcher-image: name: "Retag launcher image for release" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x environment: production permissions: contents: read @@ -40,11 +40,6 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PAT }} - - name: Install skopeo - run: | - sudo apt-get update - sudo apt-get install -y skopeo - - name: Verify source image exists env: SOURCE_TAG: ${{ inputs.source-tag }} diff --git a/.github/workflows/docker_node_release.yml b/.github/workflows/docker_node_release.yml index 0b3d4234ea..5c506a339b 100644 --- a/.github/workflows/docker_node_release.yml +++ b/.github/workflows/docker_node_release.yml @@ -36,7 +36,7 @@ on: jobs: retag-node-image: name: "Retag node image for release" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x environment: production permissions: contents: read @@ -48,11 +48,6 @@ jobs: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PAT }} - - name: Install skopeo - run: | - sudo apt-get update - sudo apt-get install -y skopeo - - name: Verify source image exists env: SOURCE_TAG: ${{ inputs.source-tag }} diff --git a/.github/workflows/external-services-tests.yml b/.github/workflows/external-services-tests.yml index 6f23e60a90..f93ae0dd51 100644 --- a/.github/workflows/external-services-tests.yml +++ b/.github/workflows/external-services-tests.yml @@ -17,7 +17,7 @@ on: jobs: external-services-tests: name: "Run tests that depend on external services" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x timeout-minutes: 15 permissions: contents: read diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml index a556cc6a24..92b14c9a87 100644 --- a/.github/workflows/nightly_build.yml +++ b/.github/workflows/nightly_build.yml @@ -14,7 +14,7 @@ on: jobs: build-mpc: - runs-on: warp-ubuntu-2404-x64-8x + runs-on: warp-ubuntu-2604-x64-8x environment: production permissions: contents: read diff --git a/.github/workflows/nix-build-mpc-node.yml b/.github/workflows/nix-build-mpc-node.yml index 44f42f96dd..0e99909087 100644 --- a/.github/workflows/nix-build-mpc-node.yml +++ b/.github/workflows/nix-build-mpc-node.yml @@ -14,7 +14,7 @@ on: jobs: nix-build-mpc-node: name: "Nix build mpc-node" - runs-on: warp-ubuntu-2404-x64-32x + runs-on: warp-ubuntu-2604-x64-32x timeout-minutes: 60 permissions: contents: read diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml index 3ba2e8257d..a011973d34 100644 --- a/.github/workflows/pr-title-lint.yml +++ b/.github/workflows/pr-title-lint.yml @@ -7,7 +7,7 @@ on: jobs: lint-pr-title: name: "Check PR title format" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x permissions: contents: read if: github.event_name == 'pull_request' || github.event_name == 'merge_group' @@ -31,7 +31,7 @@ jobs: check-pr-title-type: name: "Validate title type matches changes" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x permissions: contents: read pull-requests: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a6dc1343ec..073c6afa14 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ permissions: jobs: release: name: "Promote built artifacts to a release" - runs-on: warp-ubuntu-2404-x64-2x + runs-on: warp-ubuntu-2604-x64-2x environment: production permissions: contents: write @@ -86,11 +86,6 @@ jobs: exit 1 fi - - name: Install skopeo - run: | - sudo apt-get update - sudo apt-get install -y skopeo - - name: Login to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d181531e..081f1f02cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. This changelog is maintained using [git-cliff](https://git-cliff.org/) and [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). +## [3.14.1] - 2026-08-18 + +### ⚙️ Miscellaneous Tasks + +- (@gilcu3): Update contract migrations after 3.14 release (#4081) + +- (@pbeza): *(deny)* Ignore the unmaintained advisories for `im` and its dependencies (#4116) + +- (@dependabot[bot]): Bump the rust-minor-and-patch group with 4 updates (#4113) + +- (@barakeinav1): Install catatonit for repro-env podman builds (#4125) + +- (@barakeinav1): Install catatonit in the image-publishing workflows (#4128) + +- (@gilcu3): Move runner images to ubuntu 26.04 (#4134) + +- (@kevindeforth): *(contract)* Extend attestation expiry time during migration + + ## [3.14.0] - 2026-08-05 ### 🚀 Features diff --git a/Cargo.lock b/Cargo.lock index 4e146ba73b..1bca57edd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1047,7 +1047,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attestation" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "attestation", @@ -1069,7 +1069,7 @@ dependencies = [ [[package]] name = "attestation-cli" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "attestation", @@ -1278,7 +1278,7 @@ dependencies = [ [[package]] name = "backup-cli" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "clap", @@ -1880,7 +1880,7 @@ dependencies = [ [[package]] name = "chain-gateway" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "base64 0.23.0", @@ -1917,7 +1917,7 @@ dependencies = [ [[package]] name = "chain-gateway-test-contract" -version = "3.14.0" +version = "3.14.1" dependencies = [ "borsh", "near-contract-transport", @@ -1979,7 +1979,7 @@ dependencies = [ [[package]] name = "ckd-example-cli" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "blstrs", @@ -2009,9 +2009,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.4" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -2019,9 +2019,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -2225,7 +2225,7 @@ checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "contract-history" -version = "3.14.0" +version = "3.14.1" dependencies = [ "bs58 0.5.1", "reqwest 0.13.4", @@ -3249,7 +3249,7 @@ dependencies = [ [[package]] name = "e2e-tests" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "backon", @@ -3794,7 +3794,7 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "foreign-chain-config-tester" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "clap", @@ -3808,7 +3808,7 @@ dependencies = [ [[package]] name = "foreign-chain-health-check" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "assert_matches", @@ -3830,7 +3830,7 @@ dependencies = [ [[package]] name = "foreign-chain-inspector" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "bs58 0.5.1", @@ -3856,7 +3856,7 @@ dependencies = [ [[package]] name = "foreign-chain-rpc-auth" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "assert_matches", @@ -3868,7 +3868,7 @@ dependencies = [ [[package]] name = "foreign-chain-rpc-interfaces" -version = "3.14.0" +version = "3.14.1" dependencies = [ "derive_more 2.1.1", "ethereum-types", @@ -4545,9 +4545,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -4950,7 +4950,7 @@ dependencies = [ [[package]] name = "include-measurements" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "attestation", @@ -5476,7 +5476,7 @@ dependencies = [ [[package]] name = "launcher-interface" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "derive_more 2.1.1", @@ -5651,9 +5651,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6180140927ee907000b0aa540091f6ea512ead4447c92b8fc35bc72788a5a6" +checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a" dependencies = [ "hashbrown 0.17.0", ] @@ -5947,7 +5947,7 @@ checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "mpc-attestation" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "attestation", @@ -5969,7 +5969,7 @@ dependencies = [ [[package]] name = "mpc-contract" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "assert_matches", @@ -6020,7 +6020,7 @@ dependencies = [ [[package]] name = "mpc-devnet" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "blstrs", @@ -6053,7 +6053,7 @@ dependencies = [ [[package]] name = "mpc-node" -version = "3.14.0" +version = "3.14.1" dependencies = [ "aes-gcm", "anyhow", @@ -6092,7 +6092,7 @@ dependencies = [ "jemalloc_pprof", "k256", "launcher-interface", - "lru 0.18.1", + "lru 0.18.2", "mappings", "mockall", "mpc-attestation", @@ -6154,7 +6154,7 @@ dependencies = [ [[package]] name = "mpc-node-config" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "assert_matches", @@ -6176,7 +6176,7 @@ dependencies = [ [[package]] name = "mpc-primitives" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "borsh", @@ -6193,7 +6193,7 @@ dependencies = [ [[package]] name = "mpc-tls" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "ed25519-dalek", @@ -6596,7 +6596,7 @@ dependencies = [ [[package]] name = "near-contract-transport" -version = "3.14.0" +version = "3.14.1" dependencies = [ "derive_more 2.1.1", "near-account-id", @@ -7982,7 +7982,7 @@ dependencies = [ [[package]] name = "node-types" -version = "3.14.0" +version = "3.14.1" dependencies = [ "mpc-attestation", "near-mpc-crypto-types", @@ -10196,9 +10196,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -11382,7 +11382,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tee-authority" -version = "3.14.0" +version = "3.14.1" dependencies = [ "anyhow", "assert_matches", @@ -11409,7 +11409,7 @@ dependencies = [ [[package]] name = "tee-context" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "chain-gateway", @@ -11428,7 +11428,7 @@ dependencies = [ [[package]] name = "tee-launcher" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "backon", @@ -11449,7 +11449,7 @@ dependencies = [ [[package]] name = "tee-verifier" -version = "3.14.0" +version = "3.14.1" dependencies = [ "borsh", "dcap-qvl", @@ -11464,7 +11464,7 @@ dependencies = [ [[package]] name = "tee-verifier-conversions" -version = "3.14.0" +version = "3.14.1" dependencies = [ "borsh", "dcap-qvl", @@ -11474,7 +11474,7 @@ dependencies = [ [[package]] name = "tee-verifier-interface" -version = "3.14.0" +version = "3.14.1" dependencies = [ "borsh", "derive_more 2.1.1", @@ -11544,7 +11544,7 @@ dependencies = [ [[package]] name = "test-migration-contract" -version = "3.14.0" +version = "3.14.1" dependencies = [ "borsh", "near-sdk", @@ -11552,7 +11552,7 @@ dependencies = [ [[package]] name = "test-parallel-contract" -version = "3.14.0" +version = "3.14.1" dependencies = [ "assert_matches", "blstrs", @@ -11567,7 +11567,7 @@ dependencies = [ [[package]] name = "test-port-allocator" -version = "3.14.0" +version = "3.14.1" dependencies = [ "named-lock", "rand 0.8.6", @@ -11575,7 +11575,7 @@ dependencies = [ [[package]] name = "test-utils" -version = "3.14.0" +version = "3.14.1" dependencies = [ "cargo-near-build", "hex", diff --git a/Cargo.toml b/Cargo.toml index 316d45e8be..ced8d7481a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ members = [ ] [workspace.package] -version = "3.14.0" +version = "3.14.1" edition = "2024" license = "MIT" repository = "https://github.com/near/mpc" diff --git a/crates/contract-history/archive/signer-3_14_0.wasm b/crates/contract-history/archive/signer-3_14_0.wasm new file mode 100644 index 0000000000..0cf6cd76d3 Binary files /dev/null and b/crates/contract-history/archive/signer-3_14_0.wasm differ diff --git a/crates/contract-history/src/lib.rs b/crates/contract-history/src/lib.rs index f03faa5849..9c31524c71 100644 --- a/crates/contract-history/src/lib.rs +++ b/crates/contract-history/src/lib.rs @@ -1,9 +1,9 @@ pub const fn current_mainnet() -> &'static [u8] { - version_3_13_0() + version_3_14_0() } pub const fn current_testnet() -> &'static [u8] { - version_3_13_0() + version_3_14_0() } pub const fn version_2_2_0() -> &'static [u8; 566653] { @@ -70,6 +70,10 @@ pub const fn version_3_13_0() -> &'static [u8; 1509901] { include_bytes!("../archive/signer-3_13_0.wasm") } +pub const fn version_3_14_0() -> &'static [u8; 1187525] { + include_bytes!("../archive/signer-3_14_0.wasm") +} + #[cfg(test)] #[cfg(feature = "external-services-tests")] mod tests { diff --git a/crates/contract/src/config.rs b/crates/contract/src/config.rs index a6dc5b4889..9d2c5cd8dc 100644 --- a/crates/contract/src/config.rs +++ b/crates/contract/src/config.rs @@ -42,7 +42,7 @@ const DEFAULT_VERIFIER_TERA_GAS: u64 = 200; /// post-DCAP work (allowlist match, RTMR3 replay, app-compose validation, store). const DEFAULT_RESOLVE_VERIFICATION_TERA_GAS: u64 = 60; /// Default TTL after which a launcher image hash unused by any participant is evicted. -pub(crate) const DEFAULT_LAUNCHER_HASH_UNUSED_TTL_SECONDS: u64 = 14 * 24 * 60 * 60; // 14 days +pub(crate) const DEFAULT_LAUNCHER_HASH_UNUSED_TTL_SECONDS: u64 = 21 * 24 * 60 * 60; // 21 days /// Config for V2 of the contract. #[near(serializers=[borsh, json])] diff --git a/crates/contract/src/lib.rs b/crates/contract/src/lib.rs index f0515721c4..6919fdefaa 100644 --- a/crates/contract/src/lib.rs +++ b/crates/contract/src/lib.rs @@ -16,7 +16,7 @@ pub mod update; #[cfg(feature = "dev-utils")] pub mod utils; -pub mod v3_13_0_state; +pub mod v3_14_0_state; #[cfg(feature = "bench-contract-methods")] mod bench; @@ -2125,11 +2125,11 @@ impl MpcContract { pub fn migrate() -> Result { log!("migrating contract"); - match try_state_read::() { + match try_state_read::() { Ok(Some(state)) => return Ok(state.into()), Ok(None) => return Err(InvalidState::ContractStateIsMissing.into()), Err(err) => { - log!("failed to deserialize state into 3.13.0 state: {:?}", err); + log!("failed to deserialize state into 3.14.0 state: {:?}", err); } }; @@ -8513,4 +8513,77 @@ mod tests { {WORST_CASE_ENTRY_COST_CEILING} at today's storage price" ); } + #[test] + fn migrate__should_extend_dstack_expiries_and_raise_the_launcher_ttl() { + // Given persisted 3.14.0 state holding a dstack and a mock attestation. + const EXPIRY_SECONDS: u64 = 1_000_000; + let mock = MpcMockAttestation::WithConstraints { + mpc_docker_image_hash: None, + launcher_docker_compose_hash: None, + expiry_timestamp_seconds: Some(EXPIRY_SECONDS), + expected_measurements: None, + }; + let (mut contract, _, _, _) = setup_running_contract_with_domain(3, 2, 2); + contract.config.launcher_hash_unused_ttl_seconds = 1_209_600; // deployed 3.14.0 value: 14 days + let dstack_key = insert_attestation( + &mut contract, + "dstack.near", + VerifiedAttestation::Dstack(ValidatedDstackAttestation { + mpc_image_hash: MAX_HASH.into(), + launcher_compose_hash: MAX_HASH.into(), + expiry_timestamp_seconds: EXPIRY_SECONDS, + measurements: default_measurements()[0], + }), + ); + let mock_key = insert_attestation( + &mut contract, + "mock.near", + VerifiedAttestation::Mock(mock.clone()), + ); + env::state_write(&contract); + drop(contract); + + // When migrating, then writing state back as the `#[init]` wrapper does on chain. + let migrated = MpcContract::migrate().expect("3.14.0 state must migrate"); + env::state_write(&migrated); + drop(migrated); + + // Then the dstack expiry moved by one attestation window and the mock is untouched. + let reloaded = try_state_read::().unwrap().unwrap(); + let stored = &reloaded.tee_state.stored_attestations; + assert_matches!( + &stored.get(&dstack_key).unwrap().verified_attestation, + VerifiedAttestation::Dstack(dstack) + if dstack.expiry_timestamp_seconds + == EXPIRY_SECONDS + 14 * 24 * 60 * 60 + ); + assert_matches!( + &stored.get(&mock_key).unwrap().verified_attestation, + VerifiedAttestation::Mock(stored) if *stored == mock + ); + + // And the launcher TTL was raised, keeping `Config::validate`'s invariant true. + assert_ne!(1_209_600, config::DEFAULT_LAUNCHER_HASH_UNUSED_TTL_SECONDS); + assert_eq!( + reloaded.config.launcher_hash_unused_ttl_seconds, + config::DEFAULT_LAUNCHER_HASH_UNUSED_TTL_SECONDS + ); + } + + fn insert_attestation( + contract: &mut MpcContract, + account_id: &str, + verified_attestation: VerifiedAttestation, + ) -> Ed25519PublicKey { + let node_id = create_node_id(&account_id.parse().unwrap(), &bogus_ed25519_public_key()); + let tls_public_key = node_id.tls_public_key.clone(); + contract.tee_state.stored_attestations.insert( + tls_public_key.clone(), + NodeAttestation { + node_id, + verified_attestation, + }, + ); + tls_public_key + } } diff --git a/crates/contract/src/tee/proposal.rs b/crates/contract/src/tee/proposal.rs index 5892bd0388..cdffbc98cc 100644 --- a/crates/contract/src/tee/proposal.rs +++ b/crates/contract/src/tee/proposal.rs @@ -434,11 +434,6 @@ impl AllowedLauncherImages { } } - /// Migration constructor. - pub(crate) fn from_entries(entries: Vec) -> Self { - Self { entries } - } - /// Removes a launcher image hash and all its associated compose hashes. /// Returns `false` if the launcher hash was not found or if removal would leave the list empty. pub fn remove(&mut self, launcher_hash: &LauncherImageHash) -> bool { diff --git a/crates/contract/src/tee/tee_state.rs b/crates/contract/src/tee/tee_state.rs index a7587a937f..0a963679ab 100644 --- a/crates/contract/src/tee/tee_state.rs +++ b/crates/contract/src/tee/tee_state.rs @@ -497,6 +497,34 @@ impl TeeState { removed } + pub(crate) fn extend_dstack_attestation_expiries(&mut self, extension: Duration) { + let extension_seconds = extension.as_secs(); + + let dstack_tls_keys: Vec = self + .stored_attestations + .iter() + .filter(|(_, node_attestation)| { + matches!( + node_attestation.verified_attestation, + VerifiedAttestation::Dstack(_) + ) + }) + .map(|(tls_pk, _)| tls_pk.clone()) + .collect(); + + for tls_pk in dstack_tls_keys { + let Some(node_attestation) = self.stored_attestations.get_mut(&tls_pk) else { + continue; + }; + if let VerifiedAttestation::Dstack(dstack) = &mut node_attestation.verified_attestation + { + dstack.expiry_timestamp_seconds = dstack + .expiry_timestamp_seconds + .saturating_add(extension_seconds); + } + } + } + /// Returns the list of accounts that currently have TEE attestations stored. /// Note: This may include accounts that are no longer active protocol participants. pub fn get_tee_accounts(&self) -> Vec { diff --git a/crates/contract/src/v3_13_0_state.rs b/crates/contract/src/v3_13_0_state.rs deleted file mode 100644 index 11ad77250b..0000000000 --- a/crates/contract/src/v3_13_0_state.rs +++ /dev/null @@ -1,432 +0,0 @@ -//! ## Overview -//! This module stores the previous contract state—the one you want to migrate from. -//! The goal is to describe the data layout _exactly_ as it existed before. -//! -//! ## Guideline -//! In theory, you could copy-paste every struct from the specific commit you're migrating from. -//! However, this approach (a) requires manual effort from a developer and (b) increases the binary size. -//! A better approach: only copy the structures that have changed and import the rest from the existing codebase. - -use borsh::{BorshDeserialize, BorshSerialize}; -use mpc_attestation::attestation::{self, VerifiedAttestation}; -use near_mpc_contract_interface::types::{ - Ed25519PublicKey, Metrics, VerifyForeignTransactionRequest, -}; -use near_sdk::{ - AccountId, env, - store::{Lazy, LookupMap}, -}; - -use crate::{ - SupportedForeignChainsByNode, - config::Config, - foreign_chains_metadata::ForeignChainsMetadata, - node_migrations::NodeMigrations, - primitives::{ - ckd::CKDRequest, - signature::{SignatureRequest, YieldIndex}, - }, - state::ProtocolContractState, - tee::tee_state::TeeState, - tee::verifier_votes::TeeVerifierVotes, - update::ProposedUpdates, -}; - -/// Shadow of the `3.13.0` [`Config`]: the deployed layout predates this release's new -/// `Config` fields — the async attestation gas fields (`fail_attestation_submission_tera_gas`, -/// `verifier_tera_gas`, `resolve_verification_tera_gas`) and the launcher-eviction field -/// (`launcher_hash_unused_ttl_seconds`) — so -/// migrating `3.13.0` state deserializes the old field set and defaults the new ones. -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct OldConfig { - key_event_timeout_blocks: u64, - tee_upgrade_deadline_duration_seconds: u64, - contract_upgrade_deposit_tera_gas: u64, - sign_call_gas_attachment_requirement_tera_gas: u64, - ckd_call_gas_attachment_requirement_tera_gas: u64, - return_signature_and_clean_state_on_success_call_tera_gas: u64, - return_ck_and_clean_state_on_success_call_tera_gas: u64, - fail_on_timeout_tera_gas: u64, - clean_tee_status_tera_gas: u64, - clean_invalid_attestations_tera_gas: u64, - cleanup_orphaned_node_migrations_tera_gas: u64, - remove_non_participant_update_votes_tera_gas: u64, - clean_foreign_chain_data_tera_gas: u64, - remove_non_participant_tee_verifier_votes_tera_gas: u64, -} - -impl From for Config { - fn from(old: OldConfig) -> Self { - // Carry the deployed values; the new fields (async attestation gas + launcher - // eviction) are added in this release, so take their defaults. - Config { - key_event_timeout_blocks: old.key_event_timeout_blocks, - tee_upgrade_deadline_duration_seconds: old.tee_upgrade_deadline_duration_seconds, - contract_upgrade_deposit_tera_gas: old.contract_upgrade_deposit_tera_gas, - sign_call_gas_attachment_requirement_tera_gas: old - .sign_call_gas_attachment_requirement_tera_gas, - ckd_call_gas_attachment_requirement_tera_gas: old - .ckd_call_gas_attachment_requirement_tera_gas, - return_signature_and_clean_state_on_success_call_tera_gas: old - .return_signature_and_clean_state_on_success_call_tera_gas, - return_ck_and_clean_state_on_success_call_tera_gas: old - .return_ck_and_clean_state_on_success_call_tera_gas, - fail_on_timeout_tera_gas: old.fail_on_timeout_tera_gas, - clean_tee_status_tera_gas: old.clean_tee_status_tera_gas, - clean_invalid_attestations_tera_gas: old.clean_invalid_attestations_tera_gas, - cleanup_orphaned_node_migrations_tera_gas: old - .cleanup_orphaned_node_migrations_tera_gas, - remove_non_participant_update_votes_tera_gas: old - .remove_non_participant_update_votes_tera_gas, - clean_foreign_chain_data_tera_gas: old.clean_foreign_chain_data_tera_gas, - remove_non_participant_tee_verifier_votes_tera_gas: old - .remove_non_participant_tee_verifier_votes_tera_gas, - ..Config::default() - } - } -} - -/// `3.13.0` layout of `AllowedLauncherImage`: the current type appends an `expires_at` -/// timestamp, so the real type can no longer decode old bytes. -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct OldAllowedLauncherImage { - launcher_hash: mpc_primitives::hash::LauncherImageHash, - compose_hashes: Vec, -} - -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct OldAllowedLauncherImages { - entries: Vec, -} - -/// `3.13.0` layout of `TeeState`. Only `allowed_launcher_images` changed borsh -/// layout; every other field reuses the real (byte-identical) type. Field order -/// must match [`crate::tee::tee_state::TeeState`] exactly. -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct OldTeeState { - allowed_docker_image_hashes: crate::tee::proposal::StoredDockerImageHashes, - allowed_launcher_images: OldAllowedLauncherImages, - votes: crate::tee::proposal::CodeHashesVotes, - launcher_votes: crate::tee::proposal::LauncherHashVotes, - stored_attestations: near_sdk::store::IterableMap< - near_mpc_contract_interface::types::Ed25519PublicKey, - crate::tee::tee_state::NodeAttestation, - >, - allowed_measurements: crate::tee::measurements::AllowedMeasurements, - measurement_votes: crate::tee::measurements::MeasurementVotes, -} - -impl From for crate::tee::tee_state::TeeState { - fn from(old: OldTeeState) -> Self { - // `new` stamps `expires_at = migration_block_time + default_TTL` (constant within - // this call), so migrated entries stay live for the default unused-TTL window. - let ttl = - std::time::Duration::from_secs(crate::config::DEFAULT_LAUNCHER_HASH_UNUSED_TTL_SECONDS); - let entries = old - .allowed_launcher_images - .entries - .into_iter() - .map(|e| { - crate::tee::proposal::AllowedLauncherImage::new( - e.launcher_hash, - e.compose_hashes, - ttl, - ) - }) - .collect(); - crate::tee::tee_state::TeeState { - allowed_docker_image_hashes: old.allowed_docker_image_hashes, - allowed_launcher_images: crate::tee::proposal::AllowedLauncherImages::from_entries( - entries, - ), - votes: old.votes, - launcher_votes: old.launcher_votes, - stored_attestations: old.stored_attestations, - allowed_measurements: old.allowed_measurements, - measurement_votes: old.measurement_votes, - } - } -} - -#[derive(Debug, BorshSerialize, BorshDeserialize)] -pub struct MpcContract { - protocol_state: ProtocolContractState, - pending_signature_requests: LookupMap>, - pending_ckd_requests: LookupMap>, - pending_verify_foreign_tx_requests: LookupMap>, - proposed_updates: ProposedUpdates, - node_foreign_chain_support: SupportedForeignChainsByNode, - config: OldConfig, - tee_state: OldTeeState, - accept_requests: bool, - node_migrations: NodeMigrations, - metrics: Metrics, - foreign_chains: Lazy, - tee_verifier_account_id: Option, - tee_verifier_votes: TeeVerifierVotes, -} - -/// Stamps an expiry on every stored mock attestation that lacks or exceeds one — -/// both user-submitted mocks and the genesis sentinels written by -/// [`TeeState::with_mocked_participant_attestations`]. Legacy -/// [`mpc_attestation::attestation::MockAttestation::Valid`] entries pass -/// re-verification forever and can therefore never be evicted by -/// [`TeeState::clean_invalid_attestations`]; -/// [`mpc_attestation::attestation::MockAttestation::with_expiry_capped_at`] rewrites them as -/// expiring mocks so the normal cleanup flow can remove stale entries once the -/// window elapses. An entry whose expiry is longer than (or missing) the default -/// window is capped at it; a shorter existing expiry is left as-is. -/// -// TODO(#3978): transitional one-time upgrade step — removed together with this -// module when the pre-expiry migration is retired. -fn stamp_expiry_on_legacy_mocks(tee_state: &mut TeeState, current_timestamp_seconds: u64) { - let expiry_timestamp_seconds = - current_timestamp_seconds + attestation::DEFAULT_EXPIRATION_DURATION_SECONDS; - - // Collect keys before mutating to avoid iterator invalidation. - let mock_tls_keys: Vec = tee_state - .stored_attestations - .iter() - .filter(|(_, node_attestation)| { - matches!( - node_attestation.verified_attestation, - VerifiedAttestation::Mock(_) - ) - }) - .map(|(tls_pk, _)| tls_pk.clone()) - .collect(); - - for tls_pk in mock_tls_keys { - let Some(node_attestation) = tee_state.stored_attestations.get_mut(&tls_pk) else { - continue; - }; - if let VerifiedAttestation::Mock(mock) = &node_attestation.verified_attestation { - let stamped = mock.clone().with_expiry_capped_at(expiry_timestamp_seconds); - node_attestation.verified_attestation = VerifiedAttestation::Mock(stamped); - } - } -} - -impl From for crate::MpcContract { - fn from(old: MpcContract) -> Self { - if !matches!(old.protocol_state, ProtocolContractState::Running(_)) { - env::panic_str("Contract must be in running state when migrating."); - } - - // First convert the shadowed `3.13.0` `TeeState` (stamping `expires_at` on launcher - // entries), then stamp an expiry on legacy `MockAttestation::Valid` entries — which - // never expire and could otherwise never be cleaned up. - let mut tee_state: crate::tee::tee_state::TeeState = old.tee_state.into(); - stamp_expiry_on_legacy_mocks(&mut tee_state, TeeState::current_time_seconds()); - - crate::MpcContract { - protocol_state: old.protocol_state, - pending_signature_requests: old.pending_signature_requests, - pending_ckd_requests: old.pending_ckd_requests, - pending_verify_foreign_tx_requests: old.pending_verify_foreign_tx_requests, - proposed_updates: old.proposed_updates, - node_foreign_chain_support: old.node_foreign_chain_support, - config: old.config.into(), - tee_state, - accept_requests: old.accept_requests, - node_migrations: old.node_migrations, - metrics: old.metrics, - foreign_chains: old.foreign_chains, - tee_verifier_account_id: old.tee_verifier_account_id, - tee_verifier_votes: old.tee_verifier_votes, - } - } -} - -#[cfg(test)] -#[expect(non_snake_case)] -mod tests { - use super::*; - use crate::primitives::test_utils::bogus_ed25519_public_key; - use crate::storage_keys::StorageKey; - use crate::tee::proposal::{ - CodeHashesVotes, LauncherHashVotes, StoredDockerImageHashes, get_docker_compose_hash, - }; - use crate::tee::tee_state::{NodeAttestation, NodeId}; - use crate::tee::test_utils::set_block_timestamp; - use mpc_attestation::attestation::MockAttestation; - use mpc_primitives::hash::{LauncherImageHash, NodeImageHash}; - use near_sdk::store::IterableMap; - use near_sdk::{test_utils::VMContextBuilder, testing_env}; - use std::time::Duration; - - /// The `3.13.0` launcher layout (no timestamp) must deserialize under the shadow and - /// migrate: launcher hash + compose hashes preserved, and `expires_at` set to - /// `migration_time + default_TTL` (NOT the borsh/epoch default, which would immediately - /// expire every migrated hash). Both entries surviving at the migration block time - /// proves the expiry was stamped forward rather than to epoch 0. - #[test] - fn migration__should_preserve_launcher_hashes_and_stamp_timestamps() { - // Given two 3.13.0 launcher entries in the old, timestamp-less layout. - const MIGRATION_TIME_SECS: u64 = 1_000_000; - let launcher_1 = LauncherImageHash::from([1u8; 32]); - let launcher_2 = LauncherImageHash::from([2u8; 32]); - let mpc_hash = NodeImageHash::from([10u8; 32]); - let compose_1 = get_docker_compose_hash(&launcher_1, &mpc_hash); - let compose_2 = get_docker_compose_hash(&launcher_2, &mpc_hash); - - testing_env!( - VMContextBuilder::new() - .block_timestamp(MIGRATION_TIME_SECS * 1_000_000_000) - .build() - ); - - let old = OldTeeState { - allowed_docker_image_hashes: StoredDockerImageHashes::default(), - allowed_launcher_images: OldAllowedLauncherImages { - entries: vec![ - OldAllowedLauncherImage { - launcher_hash: launcher_1, - compose_hashes: vec![compose_1], - }, - OldAllowedLauncherImage { - launcher_hash: launcher_2, - compose_hashes: vec![compose_2], - }, - ], - }, - votes: CodeHashesVotes::default(), - launcher_votes: LauncherHashVotes::default(), - stored_attestations: IterableMap::new(StorageKey::StoredAttestations), - allowed_measurements: Default::default(), - measurement_votes: Default::default(), - }; - - // When migrated (borsh round-trip through the shadow, then into the real `TeeState`). - let bytes = borsh::to_vec(&old).unwrap(); - let decoded: OldTeeState = borsh::from_slice(&bytes).unwrap(); - let migrated: crate::tee::tee_state::TeeState = decoded.into(); - - // Then launcher hashes and compose hashes are carried over. - assert_eq!( - migrated.get_allowed_launcher_hashes(), - vec![launcher_1, launcher_2] - ); - assert_eq!( - migrated.get_allowed_launcher_compose_hashes(), - vec![compose_1, compose_2] - ); - - // `expires_at` was stamped to `migration_time + default_TTL`: at the migration block - // time both entries are still live (both surface, not just the newest-only fallback). - // Had they defaulted to epoch 0, both would be expired and the fallback would surface - // only one. - assert_eq!(migrated.get_allowed_launcher_hashes().len(), 2); - } - - #[test] - fn stamp_expiry_on_legacy_mocks__should_make_valid_mock_cleanable() { - // Given: a legacy `MockAttestation::Valid` entry stored with no expiry, as - // written by older contract versions. Such entries pass re-verification - // forever and cannot be cleaned up. - testing_env!(VMContextBuilder::new().block_timestamp(0).build()); - - let mut tee_state = TeeState::default(); - let node_id = NodeId { - account_id: "legacy.near".parse().unwrap(), - tls_public_key: bogus_ed25519_public_key(), - account_public_key: bogus_ed25519_public_key(), - }; - tee_state.stored_attestations.insert( - node_id.tls_public_key.clone(), - NodeAttestation { - node_id: node_id.clone(), - verified_attestation: VerifiedAttestation::Mock(MockAttestation::Valid), - }, - ); - - // Sanity: past the default window but without migration, the un-stamped - // entry survives cleanup indefinitely. - set_block_timestamp((attestation::DEFAULT_EXPIRATION_DURATION_SECONDS + 1) * 1_000_000_000); - assert_eq!( - tee_state.clean_invalid_attestations(Duration::from_secs(0), 100), - 0 - ); - - // When: the migration stamps an expiry as of block time 0 (window ends at - // DEFAULT), which the clock (already at DEFAULT + 1) is past. - stamp_expiry_on_legacy_mocks(&mut tee_state, 0); - let removed = tee_state.clean_invalid_attestations(Duration::from_secs(0), 100); - - // Then: the stale legacy mock entry is removed. - assert_eq!(removed, 1); - assert!( - !tee_state - .stored_attestations - .contains_key(&node_id.tls_public_key) - ); - } - - #[test] - fn migration__should_stamp_launcher_expiry_and_make_legacy_mocks_cleanable() { - // Given: a `3.13.0` TeeState carrying both a launcher image (old, timestamp-less - // layout) and a legacy `MockAttestation::Valid` stored attestation (no expiry) — - // the two things this release's migration must each handle. - const MIGRATION_TIME_SECS: u64 = 1_000_000; - testing_env!( - VMContextBuilder::new() - .block_timestamp(MIGRATION_TIME_SECS * 1_000_000_000) - .build() - ); - - let launcher = LauncherImageHash::from([1u8; 32]); - let mpc_hash = NodeImageHash::from([10u8; 32]); - let compose = get_docker_compose_hash(&launcher, &mpc_hash); - - let mut stored_attestations = IterableMap::new(StorageKey::StoredAttestations); - let node_id = NodeId { - account_id: "legacy.near".parse().unwrap(), - tls_public_key: bogus_ed25519_public_key(), - account_public_key: bogus_ed25519_public_key(), - }; - stored_attestations.insert( - node_id.tls_public_key.clone(), - NodeAttestation { - node_id: node_id.clone(), - verified_attestation: VerifiedAttestation::Mock(MockAttestation::Valid), - }, - ); - - let old = OldTeeState { - allowed_docker_image_hashes: StoredDockerImageHashes::default(), - allowed_launcher_images: OldAllowedLauncherImages { - entries: vec![OldAllowedLauncherImage { - launcher_hash: launcher, - compose_hashes: vec![compose], - }], - }, - votes: CodeHashesVotes::default(), - launcher_votes: LauncherHashVotes::default(), - stored_attestations, - allowed_measurements: Default::default(), - measurement_votes: Default::default(), - }; - - // When: the full `From` sequence runs on the TeeState — our shadow - // conversion (stamps launcher `expires_at`) followed by the legacy-mock stamping. - let mut tee_state: TeeState = old.into(); - stamp_expiry_on_legacy_mocks(&mut tee_state, TeeState::current_time_seconds()); - - // Then: the launcher was migrated with a stamped `expires_at` and is live. - assert_eq!(tee_state.get_allowed_launcher_hashes(), vec![launcher]); - - // And: the previously un-expiring legacy mock is now cleanable once the clock - // passes its stamped window — proving both migration steps applied. - set_block_timestamp( - (MIGRATION_TIME_SECS + attestation::DEFAULT_EXPIRATION_DURATION_SECONDS + 1) - * 1_000_000_000, - ); - let removed = tee_state.clean_invalid_attestations(Duration::from_secs(0), 100); - assert_eq!(removed, 1); - assert!( - !tee_state - .stored_attestations - .contains_key(&node_id.tls_public_key) - ); - } -} diff --git a/crates/contract/src/v3_14_0_state.rs b/crates/contract/src/v3_14_0_state.rs new file mode 100644 index 0000000000..cf197ccede --- /dev/null +++ b/crates/contract/src/v3_14_0_state.rs @@ -0,0 +1,89 @@ +//! ## Overview +//! This module stores the previous contract state—the one you want to migrate from. +//! The goal is to describe the data layout _exactly_ as it existed before. +//! +//! ## Guideline +//! In theory, you could copy-paste every struct from the specific commit you're migrating from. +//! However, this approach (a) requires manual effort from a developer and (b) increases the binary size. +//! A better approach: only copy the structures that have changed and import the rest from the existing codebase. + +use borsh::{BorshDeserialize, BorshSerialize}; + +const PRE_3_14_1_EXPIRATION_DURATION_SECONDS: u64 = 60 * 60 * 24 * 7; // 7 days +use near_mpc_contract_interface::types::{Metrics, VerifyForeignTransactionRequest}; +use near_sdk::{ + AccountId, env, + store::{Lazy, LookupMap}, +}; +use std::time::Duration; + +use crate::{ + SupportedForeignChainsByNode, + config::Config, + foreign_chains_metadata::ForeignChainsMetadata, + node_migrations::NodeMigrations, + primitives::{ + ckd::CKDRequest, + signature::{SignatureRequest, YieldIndex}, + }, + state::ProtocolContractState, + tee::{tee_state::TeeState, verifier_votes::TeeVerifierVotes}, + update::ProposedUpdates, +}; + +/// Keep this module in sync with [`crate::MpcContract`]: the moment a field's borsh +/// layout diverges, shadow the old type here (see this module's history for examples) so +/// state written by the `3.14.0` contract still deserializes during migration. +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct MpcContract { + protocol_state: ProtocolContractState, + pending_signature_requests: LookupMap>, + pending_ckd_requests: LookupMap>, + pending_verify_foreign_tx_requests: LookupMap>, + proposed_updates: ProposedUpdates, + node_foreign_chain_support: SupportedForeignChainsByNode, + config: Config, + tee_state: TeeState, + accept_requests: bool, + node_migrations: NodeMigrations, + metrics: Metrics, + foreign_chains: Lazy, + tee_verifier_account_id: Option, + tee_verifier_votes: TeeVerifierVotes, +} + +impl From for crate::MpcContract { + fn from(old: MpcContract) -> Self { + if !matches!(old.protocol_state, ProtocolContractState::Running(_)) { + env::panic_str("Contract must be in running state when migrating."); + } + + let mut tee_state = old.tee_state; + tee_state.extend_dstack_attestation_expiries(Duration::from_secs( + mpc_attestation::attestation::DEFAULT_EXPIRATION_DURATION_SECONDS + - PRE_3_14_1_EXPIRATION_DURATION_SECONDS, + )); + + let mut config = old.config; + config.launcher_hash_unused_ttl_seconds = config + .launcher_hash_unused_ttl_seconds + .max(crate::config::DEFAULT_LAUNCHER_HASH_UNUSED_TTL_SECONDS); + + crate::MpcContract { + protocol_state: old.protocol_state, + pending_signature_requests: old.pending_signature_requests, + pending_ckd_requests: old.pending_ckd_requests, + pending_verify_foreign_tx_requests: old.pending_verify_foreign_tx_requests, + proposed_updates: old.proposed_updates, + node_foreign_chain_support: old.node_foreign_chain_support, + config, + tee_state, + accept_requests: old.accept_requests, + node_migrations: old.node_migrations, + metrics: old.metrics, + foreign_chains: old.foreign_chains, + tee_verifier_account_id: old.tee_verifier_account_id, + tee_verifier_votes: old.tee_verifier_votes, + } + } +} diff --git a/crates/contract/tests/sandbox/contract_configuration.rs b/crates/contract/tests/sandbox/contract_configuration.rs index 2e6f24dc4a..471b566901 100644 --- a/crates/contract/tests/sandbox/contract_configuration.rs +++ b/crates/contract/tests/sandbox/contract_configuration.rs @@ -107,7 +107,7 @@ async fn contract_configuration_can_be_set_on_initialization() { verifier_tera_gas: Some(15), resolve_verification_tera_gas: Some(16), // Must satisfy `Config::validate` (>= DEFAULT_EXPIRATION_DURATION_SECONDS). - launcher_hash_unused_ttl_seconds: Some(14 * 24 * 60 * 60), + launcher_hash_unused_ttl_seconds: Some(21 * 24 * 60 * 60), }; let SandboxTestSetup { contract, .. } = SandboxTestSetup::builder() diff --git a/crates/contract/tests/sandbox/upgrade_from_current_contract.rs b/crates/contract/tests/sandbox/upgrade_from_current_contract.rs index b5e25ea7ef..151d8030bc 100644 --- a/crates/contract/tests/sandbox/upgrade_from_current_contract.rs +++ b/crates/contract/tests/sandbox/upgrade_from_current_contract.rs @@ -126,7 +126,7 @@ async fn test_propose_update_config() { verifier_tera_gas: 15, resolve_verification_tera_gas: 16, // Must satisfy `Config::validate` (>= DEFAULT_EXPIRATION_DURATION_SECONDS). - launcher_hash_unused_ttl_seconds: 14 * 24 * 60 * 60, + launcher_hash_unused_ttl_seconds: 21 * 24 * 60 * 60, }; let propose_args = ProposeUpdateArgs { diff --git a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs index 8fcf5f877d..876ba7ae8e 100644 --- a/crates/contract/tests/sandbox/upgrade_to_current_contract.rs +++ b/crates/contract/tests/sandbox/upgrade_to_current_contract.rs @@ -182,8 +182,8 @@ async fn propose_upgrade_from_production_to_current_binary( ) .await; - // Vote in a launcher image hash so the launcher-image migration decodes a non-empty - // `entries` vec off the real 3.13.0 layout, not just the empty-vec path. + // Vote in a launcher image hash so migration decodes a non-empty `entries` vec off the + // real production layout, not just the empty-vec path. let launcher_hash = mpc_primitives::hash::LauncherImageHash::from([0xAA; 32]); for account in &accounts { vote_add_launcher_hash(account, &contract, &launcher_hash) @@ -209,8 +209,6 @@ async fn propose_upgrade_from_production_to_current_binary( "State of the contract should remain the same post upgrade." ); - // The launcher hash survives migration: it is decoded from the old (timestamp-less) - // layout and re-stamped with a fresh expiry, so it is still live post-upgrade. assert!( get_allowed_launcher_image_hashes(&contract) .await diff --git a/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap b/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap index 4a23e5bdb5..9985ea658b 100644 --- a/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap +++ b/crates/contract/tests/snapshots/abi__abi_has_not_changed.snap @@ -6,7 +6,7 @@ expression: abi "schema_version": "0.4.0", "metadata": { "name": "mpc-contract", - "version": "3.14.0", + "version": "3.14.1", "build": { "compiler": "[RUSTC_VERSION]", "builder": "[CARGO_NEAR_BUILD_VERSION]" diff --git a/crates/mpc-attestation/src/attestation.rs b/crates/mpc-attestation/src/attestation.rs index ca14dee6ff..2b32f5d77b 100644 --- a/crates/mpc-attestation/src/attestation.rs +++ b/crates/mpc-attestation/src/attestation.rs @@ -25,7 +25,7 @@ use crate::alloc::string::{String, ToString}; /// re-verified via [`VerifiedAttestation::re_verify`]. Nodes resubmit hourly, /// well within this window, so valid attestations refresh in time. // TODO(#1639): extract timestamp from certificate itself -pub const DEFAULT_EXPIRATION_DURATION_SECONDS: u64 = 60 * 60 * 24 * 7; // 7 days +pub const DEFAULT_EXPIRATION_DURATION_SECONDS: u64 = 60 * 60 * 24 * 21; // 21 days // `large_enum_variant` fires only where `usize` is 64-bit; under the contract's // wasm32 build the variants are close enough in size that it doesn't, so gate the diff --git a/crates/test-utils/src/contract_types.rs b/crates/test-utils/src/contract_types.rs index b6def77411..b8adfcf28f 100644 --- a/crates/test-utils/src/contract_types.rs +++ b/crates/test-utils/src/contract_types.rs @@ -19,6 +19,6 @@ pub fn dummy_config(value: u64) -> near_mpc_contract_interface::types::Config { resolve_verification_tera_gas: value + 15, fail_attestation_submission_tera_gas: value + 16, // Must satisfy `Config::validate` (>= DEFAULT_EXPIRATION_DURATION_SECONDS). - launcher_hash_unused_ttl_seconds: value + (14 * 24 * 60 * 60), + launcher_hash_unused_ttl_seconds: value + (21 * 24 * 60 * 60), } } diff --git a/deny.toml b/deny.toml index 179831f9be..d5d9881f93 100644 --- a/deny.toml +++ b/deny.toml @@ -33,6 +33,10 @@ ignore = [ # has no patched release. Only reachable through near-vm-runner, which never # shares a Store across Engines. "RUSTSEC-2026-0222", # wasmtime stores can mix up type indices between engines + # TODO(#4117): remove once nearcore moves `near-network` off `im`, whose maintained fork is `imbl`. + "RUSTSEC-2026-0247", # bitmaps is unmaintained + "RUSTSEC-2026-0248", # im is unmaintained + "RUSTSEC-2026-0251", # sized-chunks is unmaintained ] [bans] diff --git a/deployment/build-images.sh b/deployment/build-images.sh index 7b2d923777..48c8315344 100755 --- a/deployment/build-images.sh +++ b/deployment/build-images.sh @@ -1,14 +1,14 @@ #! /usr/bin/env bash # Script to reproducibly build the docker images for the node and launcher # -# Requirements: docker, docker-buildx, git, find, touch, skopeo -# Extra requirements if using --node or --rust-launcher: repro-env, podman -# Extra requirements if using --push: docker must be logged in to registry +# Requirements: docker, docker-buildx, git, find, touch, podman +# Extra requirements if using --node or --rust-launcher: repro-env +# Extra requirements if using --push: skopeo, docker must be logged in to registry # # Usage: # ./deployment/build-images.sh [--node] [--node-gcp] [--rust-launcher] [--push] # If no image flags are used, all images are built -# Manifest digests are always computed and printed (skopeo required) +# Manifest digests are always computed and printed (via a pinned skopeo image) set -euo pipefail @@ -60,10 +60,14 @@ require_cmds() { [[ "${missing}" -eq 0 ]] || die "Please install the missing dependencies above." } -require_cmds docker git find touch skopeo +require_cmds docker git find touch podman -if $USE_NODE || $USE_RUST_LAUNCHER; then - require_cmds repro-env podman +if $USE_NODE || $USE_NODE_GCP || $USE_RUST_LAUNCHER; then + require_cmds repro-env +fi + +if $USE_PUSH; then + require_cmds skopeo fi if ! docker buildx &>/dev/null; then @@ -97,9 +101,12 @@ find . \( -type f -o -type d \) -exec touch -d @"$SOURCE_DATE_EPOCH" {} + buildkit_version="0.27.1" buildkit_image_name="buildkit_${buildkit_version}" +# Digest of moby/buildkit:v${buildkit_version}; a re-pushed tag would change the +# build output, so the builder is pinned by digest like the skopeo image below. +buildkit_digest="sha256:1e110c71d389d6d24f67b9438e2f7b8da749a6ff407b22a1631e025c95599368" if ! docker buildx inspect ${buildkit_image_name} &>/dev/null; then - docker buildx create --use --driver-opt image=moby/buildkit:v${buildkit_version} --name ${buildkit_image_name} + docker buildx create --use --driver-opt image=moby/buildkit@${buildkit_digest} --name ${buildkit_image_name} else # A reused builder may hold a stale local-context cache: buildkit keys # context changes on (size, mtime), but the touch above resets mtime, so a @@ -128,6 +135,12 @@ build_reproducible_image() { docker load -i "$tar_path" } +# skopeo re-gzips every layer here, so the manifest digest depends on the +# deflate library its binary was linked against rather than on its version +# string: Ubuntu 26.04's package (klauspost/compress 1.18.1) emits different +# bytes than 24.04's (1.17.7) for identical input. Pin the build by digest. +skopeo_image="quay.io/skopeo/stable:v1.22.2@sha256:c7d3c512612f52805023cd38351081dad7e2729fc13d14b701e47c7c8bdd6615" + # Compress a built image tar via skopeo to a temp directory. # Prints the temp dir path to stdout. The manifest digest can be # computed from $dir/manifest.json. @@ -137,7 +150,10 @@ skopeo_compress() { td=$(mktemp -d) # Compress the image to a local directory, which implicitly computes # the manifest digest in $td/manifest.json - skopeo copy --all --dest-compress "docker-archive:${tar_path}" "dir:$td" >&2 + podman run --rm \ + -v "${tar_path}:/image.tar:ro,z" -v "${td}:/out:z" \ + "${skopeo_image}" \ + copy --all --dest-compress docker-archive:/image.tar dir:/out >&2 echo "$td" } diff --git a/docs/reproducible-builds.md b/docs/reproducible-builds.md index 5078615025..86703d083a 100644 --- a/docs/reproducible-builds.md +++ b/docs/reproducible-builds.md @@ -12,11 +12,13 @@ security and verification purposes. - `docker` with buildx support - `jq` - `git` +- `podman` - runs the pinned `skopeo` image that compresses the layers; its + gzip output determines the manifest digest, so the pin (not a host `skopeo`) + is what makes that digest reproducible **Additional requirements for building the node image**: - `repro-env` - Tool for reproducible build environments ([install here](https://github.com/kpcyrd/repro-env)) -- `podman` **Requirements for building the MPC contract** (either path works): diff --git a/docs/running-an-mpc-node-in-tdx-external-guide.md b/docs/running-an-mpc-node-in-tdx-external-guide.md index 95ebfad268..6469be328f 100644 --- a/docs/running-an-mpc-node-in-tdx-external-guide.md +++ b/docs/running-an-mpc-node-in-tdx-external-guide.md @@ -1579,7 +1579,7 @@ git checkout 828f816be36aed6f0d2438e0131b3e9d7d0931ad ``` * Compile it using the reproduce build script. For this you need to install - `repro-env`, `docker-buildx`, and `skopeo`, and have the `docker` daemon + `repro-env`, `docker-buildx`, and `podman`, and have the `docker` daemon running. ```bash @@ -1685,7 +1685,7 @@ cd mpc/ git checkout ``` -* Compile it using the reproducible build script. For this you need to install `repro-env`, `docker-buildx`, and `skopeo`, and have the `docker` daemon running. +* Compile it using the reproducible build script. For this you need to install `repro-env`, `docker-buildx`, and `podman`, and have the `docker` daemon running. ```bash $ ./deployment/build-images.sh --rust-launcher diff --git a/scripts/build-and-verify-rust-launcher-docker-image.sh b/scripts/build-and-verify-rust-launcher-docker-image.sh deleted file mode 100755 index 6afb858822..0000000000 --- a/scripts/build-and-verify-rust-launcher-docker-image.sh +++ /dev/null @@ -1,17 +0,0 @@ -#! /usr/bin/env bash -# -# Builds the Rust launcher reproducibly and prints its manifest digest. -# No assertion — the previous strict check was removed in #3199; see -# https://github.com/near/mpc/issues/2662 for context and the -# preconditions for restoring it. - -set -euo pipefail - -./deployment/build-images.sh --rust-launcher - -temp_dir=$(mktemp -d) -trap 'rm -rf "$temp_dir"' EXIT -echo "using $temp_dir" -skopeo copy --all --dest-compress docker-daemon:mpc-rust-launcher:latest dir:"$temp_dir" -built_hex="$(sha256sum "$temp_dir/manifest.json" | cut -d' ' -f1)" -echo "Built launcher image hash: sha256:${built_hex}" diff --git a/third-party-licenses/licenses.html b/third-party-licenses/licenses.html index 38f18cdcef..6f7c6d9406 100644 --- a/third-party-licenses/licenses.html +++ b/third-party-licenses/licenses.html @@ -6098,8 +6098,8 @@

Used by:

  • anstyle-wincon 3.0.11
  • anstyle 1.0.14
  • bytesize 1.3.3
  • -
  • clap 4.6.4
  • -
  • clap_builder 4.6.2
  • +
  • clap 4.6.6
  • +
  • clap_builder 4.6.6
  • clap_derive 4.6.4
  • clap_lex 1.1.0
  • cobs 0.3.0
  • @@ -8029,7 +8029,7 @@

    Used by:

    Apache License 2.0

    Used by:

                                  Apache License
                             Version 2.0, January 2004
    @@ -10695,7 +10695,7 @@ 

    Used by:

  • rusticata-macros 4.1.0
  • rustix 1.1.4
  • rustls-native-certs 0.8.3
  • -
  • rustls 0.23.42
  • +
  • rustls 0.23.43
  • scopeguard 1.2.0
  • security-framework-sys 2.17.0
  • security-framework 3.7.0
  • @@ -16411,7 +16411,7 @@

    MIT License

    Used by:

    MIT License
    @@ -17421,45 +17421,45 @@ 

    Used by:

    MIT License

    Used by: