Skip to content

chore(release): rust v0.4.9 #9

chore(release): rust v0.4.9

chore(release): rust v0.4.9 #9

name: Build, Test, and Deploy (Rust)
on:
workflow_dispatch:
push:
branches:
- staging
- trying
- rust/dev
- rust/main
tags: "**"
pull_request:
branches:
- rust/dev
- rust/main
- "feature/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
# Enable AES/SSE2 CPU features for gxhash dependency (required by PathMap crate)
# GitHub Actions runners support these features on x86_64
RUSTFLAGS: "-C target-feature=+aes,+sse2"
jobs:
# Build and save artifacts for next jobs.
# Skip release commits on branch pushes — the tag push handles the release build.
build_base:
name: Build Base
runs-on: ubuntu-latest
if: >-
!startsWith(github.event.head_commit.message, 'chore(release)')
|| startsWith(github.ref, 'refs/tags/')
outputs:
VERSION: ${{ env.VERSION }}
BRANCH: ${{ env.BRANCH }}
DEV_LATEST_TAG: ${{ env.DEV_LATEST_TAG }}
steps:
- name: Clone Repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Install APT Dependencies
shell: bash -ex {0}
run: |
sudo apt-get update
sudo apt-get install -y $(cat .github/apt-dependencies.txt)
- name: Tool versions
shell: bash -ex {0}
run: |
python --version
pip --version
rustc --version
cargo --version
- name: Initialize Environment
shell: bash -exu -o pipefail {0}
run: |
git fetch origin
git fetch origin --tags --force
# Version from Git repository (tag-commit)
VERSION="$(git describe --tags --always)"
# Strip implementation prefix for Docker-friendly version tags
VERSION="${VERSION#rust-}"
echo "VERSION=$VERSION" >> $GITHUB_ENV
# Find latest tag on rust/dev branch, with fallback
if git ls-remote --heads origin rust/dev 2>/dev/null | grep -q "refs/heads/rust/dev"; then
DEV_LATEST_TAG="$(git describe --tags --abbrev=0 origin/rust/dev 2>/dev/null || echo 'unknown')"
else
DEV_LATEST_TAG="unknown"
fi
echo "DEV_LATEST_TAG=$DEV_LATEST_TAG" >> $GITHUB_ENV
# Find related HEAD branch
BRANCH=""
if [[ $GITHUB_REF =~ ^refs/tags/ ]]; then
# For tag pushes, determine release type from tag name
# patch .0 = main release, patch > 0 = dev release
TAG_NAME="${GITHUB_REF#refs/tags/}"
TAG_VERSION="${TAG_NAME#rust-v}"
PATCH=$(echo "$TAG_VERSION" | cut -d. -f3)
if [ "$PATCH" = "0" ]; then
BRANCH="rust/main"
else
BRANCH="rust/dev"
fi
elif [[ $GITHUB_REF =~ ^refs/heads/ ]]; then
BRANCH=${GITHUB_REF#refs/*/}
else
BRANCH=$GITHUB_HEAD_REF
fi
echo "BRANCH=$BRANCH" >> $GITHUB_ENV
- name: Pack Working Tree
shell: bash -ex {0}
run: tar -H posix -czf /tmp/f1r3fly-worktree.tar.gz .
- name: Save Working Tree
uses: actions/upload-artifact@v4
with:
name: f1r3fly-worktree
path: /tmp/f1r3fly-worktree.tar.gz
required_rust_unit_tests:
name: Required Unit Tests (Rust)
needs: build_base
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
tests:
- crypto
- rholang
- rspace++
- shared
- models
- casper
- node
- block-storage
- comm
- graphz
steps:
- name: Clone Repository
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
- name: Install APT Dependencies
shell: bash -ex {0}
run: |
sudo apt-get update
sudo apt-get install -y $(cat .github/apt-dependencies.txt)
- name: Load Working Tree
uses: actions/download-artifact@v4
with:
name: f1r3fly-worktree
path: /tmp
- name: Restore Working Tree
shell: bash -ex {0}
run: tar -H posix -xzf /tmp/f1r3fly-worktree.tar.gz
- name: Run Unit Tests
shell: bash -ex {0}
env:
RUSTFLAGS: "-C target-feature=+aes,+sse2 -D warnings"
RUST_BACKTRACE: "1"
run: |
mkdir -p /tmp/test-logs
pushd ${{ matrix.tests }}
cargo test --release 2>&1 | tee /tmp/test-logs/${{ matrix.tests }}-test-output.txt
TEST_EXIT_CODE=${PIPESTATUS[0]}
popd
exit $TEST_EXIT_CODE
- name: Upload Test Logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-logs-${{ matrix.tests }}
path: /tmp/test-logs/
retention-days: 7
# Build Rust Docker image natively on each arch using GitHub-hosted runners.
build_rust_docker_image:
name: Build Rust Docker Image (${{ matrix.arch }})
needs: build_base
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
arch: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
arch: arm64
steps:
- name: Clone Repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker Image
shell: bash -ex {0}
run: |
docker buildx build \
--platform ${{ matrix.platform }} \
--file node/Dockerfile \
--tag f1r3flyindustries/f1r3fly-rust-node:${{ matrix.arch }} \
--load \
.
- name: Export Docker Image
shell: bash -ex {0}
run: |
mkdir -p /tmp/artifacts
git describe --tags --always >/tmp/artifacts/version.txt
docker image save f1r3flyindustries/f1r3fly-rust-node:${{ matrix.arch }} \
| gzip > /tmp/rust-node-docker.tar.gz
- name: Save Docker Image
uses: actions/upload-artifact@v4
with:
name: artifacts-docker-${{ matrix.arch }}
path: /tmp/rust-node-docker.tar.gz
# Integration tests on GitHub-hosted runners, parallelized by test module.
# Each test module gets its own ephemeral runner (2 arches × 16 modules = 32 jobs).
required_rust_integration_tests:
name: Integration Tests (${{ matrix.arch.name }}/${{ matrix.test }})
needs: build_rust_docker_image
runs-on: ${{ matrix.arch.runner }}
strategy:
fail-fast: false
matrix:
arch:
- { runner: ubuntu-latest, name: amd64 }
- { runner: ubuntu-24.04-arm, name: arm64 }
test:
- test_web_api
- test_wallets
- test_heartbeat
- test_deployment
- test_storage
- test_genesis_ceremony
- test_dag_correctness
- test_finalization
- test_propose
- test_replay_determinism
- test_consensus_health
- test_synchrony_constraint
- test_asymmetric_bonds
- test_bonding_validators
- test_trim_state
steps:
- name: Clone Repository
uses: actions/checkout@v4
- name: Clone system-integration
uses: actions/checkout@v4
with:
repository: F1R3FLY-io/system-integration
ref: main
path: system-integration
- uses: actions/setup-python@v5
with:
python-version: "3.10"
cache: 'pip'
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.8.5
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load Docker Image
uses: actions/download-artifact@v4
with:
name: artifacts-docker-${{ matrix.arch.name }}
path: /tmp
- name: Import Docker Image
shell: bash -ex {0}
run: |
zcat /tmp/rust-node-docker.tar.gz | docker image load
docker tag f1r3flyindustries/f1r3fly-rust-node:${{ matrix.arch.name }} f1r3flyindustries/f1r3fly-rust-node
- name: Install system-integration dependencies
shell: bash -ex {0}
run: |
cd system-integration
poetry lock --no-update
poetry install --with integration
- name: Ensure test ports are free
shell: bash {0}
run: |
for port in $(seq 40400 40545); do
fuser -k $port/tcp 2>/dev/null || true
done
- name: Run Integration Tests
shell: bash -ex {0}
env:
DEFAULT_IMAGE: f1r3flyindustries/f1r3fly-rust-node
run: |
cd system-integration
SCALE_FLAG=""
if [ "${{ matrix.arch.name }}" = "arm64" ]; then
SCALE_FLAG="--timeout-scale=1.5"
fi
DESELECT_FLAG=""
if [ "${{ matrix.test }}" = "test_replay_determinism" ]; then
# Skip test_network_recovers_from_slow_deploy — see f1r3node#463
DESELECT_FLAG="--deselect integration-tests/test/test_replay_determinism.py::test_network_recovers_from_slow_deploy"
fi
poetry run pytest integration-tests/test/${{ matrix.test }}.py -v --tb=short --log-cli-level=WARNING \
--startup-timeout=600 --command-timeout=300 --timeout=600 $SCALE_FLAG $DESELECT_FLAG
- name: Upload Integration Test Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-logs-${{ matrix.arch.name }}-${{ matrix.test }}
path: system-integration/integration-tests/
retention-days: 7
- name: Upload Docker Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: docker-logs-${{ matrix.arch.name }}-${{ matrix.test }}
path: /tmp/*.log
if-no-files-found: ignore
retention-days: 7
# release_* jobs make built artifacts available to public and run only on new
# tags or pushes to "staging" or "trying" branches used by Bors (bors r+ and bors try).
# These jobs require secrets! See "env" variables and "Secrets" page in GitHub repository
# settings. Release destinations differ slightly depending on the event that
# triggered the job (tag or branch push). See "Publish ..." steps for details.
# VERSION and BRANCH are used from "build_base" job outputs as a new way to share
# data between jobs and steps. Legacy "version" via artifact file is still used.
# Upload Rust Docker image to Docker Hub (multi-arch manifest).
release_rust_docker_image:
name: Release Rust Docker Image
needs:
- required_rust_unit_tests
- required_rust_integration_tests
- build_rust_docker_image
- build_base
if: >-
github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') ||
github.ref == 'refs/heads/trying' ||
github.ref == 'refs/heads/staging' ||
github.ref == 'refs/heads/rust/main' ||
github.ref == 'refs/heads/rust/dev')
runs-on: ubuntu-latest
steps:
- name: Load AMD64 Docker Image
uses: actions/download-artifact@v4
with:
name: artifacts-docker-amd64
path: /tmp/amd64
- name: Import AMD64 Docker Image
shell: bash -ex {0}
run: zcat /tmp/amd64/rust-node-docker.tar.gz | docker image load
- name: Load ARM64 Docker Image
uses: actions/download-artifact@v4
with:
name: artifacts-docker-arm64
path: /tmp/arm64
- name: Import ARM64 Docker Image
shell: bash -ex {0}
run: zcat /tmp/arm64/rust-node-docker.tar.gz | docker image load
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Publish Docker Image
env:
VERSION: ${{ needs.build_base.outputs.VERSION }}
BRANCH: ${{ needs.build_base.outputs.BRANCH }}
DEV_LATEST_TAG: ${{ needs.build_base.outputs.DEV_LATEST_TAG }}
shell: bash -exu -o pipefail {0}
run: |
DOCKER_IMAGE_NAME="f1r3flyindustries/f1r3fly-rust-node"
SOURCE_AMD64="${DOCKER_IMAGE_NAME}:amd64"
SOURCE_ARM64="${DOCKER_IMAGE_NAME}:arm64"
SUFFIX=""
CI_RUN=""
TAG_PREFIX=""
IS_TAG_PUSH="false"
if [[ $GITHUB_REF =~ ^refs/tags/ ]]; then
IS_TAG_PUSH="true"
fi
# Add suffix if trying or merging
if [[ "$BRANCH" =~ ^(trying|staging)$ ]]; then
SUFFIX="-$BRANCH"
# Add CI run number if trying
if [[ $BRANCH =~ ^trying$ ]]; then
CI_RUN="-ci-$GITHUB_RUN_NUMBER"
fi
elif [[ "$BRANCH" == "rust/dev" && "$IS_TAG_PUSH" == "false" ]]; then
# Only prefix dev- for branch pushes (commit hashes), not tag releases
TAG_PREFIX="dev-"
fi
set -x
IMAGE_NAME="$DOCKER_IMAGE_NAME$SUFFIX"
IMG_VERSION_RAW="$IMAGE_NAME:${TAG_PREFIX}$VERSION$CI_RUN"
# Replace unsupported character `+`
IMG_VERSION="${IMG_VERSION_RAW//[+]/__}"
if [[ "$BRANCH" == "rust/dev" ]]; then
IMG_LATEST="$IMAGE_NAME:dev"
else
IMG_LATEST="$IMAGE_NAME:latest"
fi
# Tag and push arch-specific images
docker tag $SOURCE_AMD64 $IMG_VERSION-amd64
docker tag $SOURCE_ARM64 $IMG_VERSION-arm64
docker push $IMG_VERSION-amd64
docker push $IMG_VERSION-arm64
docker tag $SOURCE_AMD64 $IMG_LATEST-amd64
docker tag $SOURCE_ARM64 $IMG_LATEST-arm64
docker push $IMG_LATEST-amd64
docker push $IMG_LATEST-arm64
# Create and push multi-arch manifests
docker manifest create $IMG_VERSION --amend $IMG_VERSION-amd64 $IMG_VERSION-arm64
docker manifest push $IMG_VERSION
docker manifest create $IMG_LATEST --amend $IMG_LATEST-amd64 $IMG_LATEST-arm64
docker manifest push $IMG_LATEST
# GitHub (create) release and packages
release_packages:
name: Release Packages
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/rust-v')
permissions:
contents: write # Required for creating/updating releases
needs:
- required_rust_unit_tests
- required_rust_integration_tests
- build_rust_docker_image
runs-on: ubuntu-latest
steps:
- name: Checkout (for CHANGELOG.md)
uses: actions/checkout@v4
- name: Load Docker artifact (amd64)
uses: actions/download-artifact@v4
with:
name: artifacts-docker-amd64
path: /tmp/artifacts
- name: Extract node binary from Docker image
run: |
zcat /tmp/artifacts/rust-node-docker.tar.gz | docker image load
CONTAINER_ID=$(docker create f1r3flyindustries/f1r3fly-rust-node:amd64)
docker cp "$CONTAINER_ID:/opt/docker/bin/node" /tmp/artifacts/f1r3node-linux-amd64
docker rm "$CONTAINER_ID"
test -f /tmp/artifacts/f1r3node-linux-amd64 || { echo 'Binary extraction failed'; exit 1; }
- name: Determine release type
id: release_type
run: |
TAG="${GITHUB_REF#refs/tags/rust-v}"
PATCH=$(echo "$TAG" | cut -d. -f3)
if [ "$PATCH" = "0" ]; then
echo "prerelease=false" >> "$GITHUB_OUTPUT"
else
echo "prerelease=true" >> "$GITHUB_OUTPUT"
fi
- name: Extract release notes for this version
run: |
# Extract only the latest version section from CHANGELOG.md
awk '/^## \[/{if(found) exit; found=1} found' CHANGELOG.md > RELEASE_NOTES.md
- name: Update release and release artifacts
uses: ncipollo/release-action@v1
with:
artifacts: /tmp/artifacts/*
prerelease: ${{ steps.release_type.outputs.prerelease }}
draft: false
allowUpdates: true
omitBodyDuringUpdate: true
bodyFile: RELEASE_NOTES.md
token: ${{ secrets.GITHUB_TOKEN }}