diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..c83e8972 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.github +target +**/.DS_Store +integration/jam-conformance +integration/jamtestvectors +integration/jamtestvectors-polkajam +integration/jamtestvectors-pvm +Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d832482..1f2daeca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,130 +1,71 @@ -name: Release Build +name: Release on: push: branches: - release + workflow_dispatch: env: CARGO_TERM_COLOR: always permissions: - contents: write + contents: read + packages: write jobs: - build-aarch64: - name: macOS build (aarch64) - runs-on: macos-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: false - - - name: Install stable toolchain - run: rustup toolchain install stable - - - name: Check rust version - run: rustc +stable --version - - - name: Install llvm/clang - run: | - brew install llvm - echo "/opt/homebrew/opt/llvm/bin" >> $GITHUB_PATH - - - name: Create artifacts directory - run: mkdir -p artifacts - - - name: Build release binary (tiny config) - run: cargo build --profile prod --target aarch64-apple-darwin --features "tiny,fuzz" - - - name: Copy binary to artifacts (tiny config) - run: cp target/aarch64-apple-darwin/prod/fastroll artifacts/fastroll-macos-aarch64-tiny - - - name: Build release binary (full config) - run: cargo build --profile prod --target aarch64-apple-darwin --features "full,fuzz" - - - name: Copy binary to artifacts (full config) - run: cp target/aarch64-apple-darwin/prod/fastroll artifacts/fastroll-macos-aarch64-full - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: aarch64-binaries - path: artifacts/* - retention-days: 21 - - build-linux-x86_64: - name: Linux build (x86_64) + docker-image: + name: Docker image runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: false - - name: Install stable toolchain - run: rustup toolchain install stable - - - name: Check rust version - run: rustc +stable --version - - - name: Install libclang-dev - run: sudo apt-get install libclang-dev - - - name: Create artifacts directory - run: mkdir -p artifacts - - - name: Build release binary (tiny config) - run: cargo build --profile prod --target x86_64-unknown-linux-gnu --features "tiny,fuzz" - - - name: Copy binary to artifacts (tiny config) - run: cp target/x86_64-unknown-linux-gnu/prod/fastroll artifacts/fastroll-linux-x86_64-tiny - - - name: Build release binary (full config) - run: cargo build --profile prod --target x86_64-unknown-linux-gnu --features "full,fuzz" - - - name: Copy binary to artifacts (full config) - run: cp target/x86_64-unknown-linux-gnu/prod/fastroll artifacts/fastroll-linux-x86_64-full - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: linux-binaries - path: artifacts/* - retention-days: 21 - - release: - name: Github Release - needs: [ build-aarch64, build-linux-x86_64 ] - runs-on: ubuntu-latest - if: success() + - name: Set image metadata + id: meta + shell: bash + run: | + image="ghcr.io/${GITHUB_REPOSITORY,,}" + version="$(python3 -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["version"])')" + echo "image=${image}" >> "${GITHUB_OUTPUT}" + echo "version=${version}" >> "${GITHUB_OUTPUT}" + echo "Image: ${image}:${version}" - steps: - - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Download artifacts (aarch64) - uses: actions/download-artifact@v4 + - name: Login to GHCR + uses: docker/login-action@v3 with: - name: aarch64-binaries - path: ./release-artifacts + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Download artifacts (linux-x86_64) - uses: actions/download-artifact@v4 + - name: Build and publish Docker image + id: build + uses: docker/build-push-action@v6 with: - name: linux-binaries - path: ./release-artifacts - - - name: Version tag - id: version + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ steps.meta.outputs.image }}:latest + ${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.version }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.version=${{ steps.meta.outputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Summary run: | - VERSION="v$(date +'%Y.%m.%d')-${GITHUB_SHA::7}" - echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - echo "Version: $VERSION" - - - name: Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.version.outputs.VERSION }} - name: FastRoll ${{ steps.version.outputs.VERSION }} - files: ./release-artifacts/* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + { + echo "Published Docker image:" + echo "- \`${{ steps.meta.outputs.image }}:latest\`" + echo "- \`${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.version }}\`" + echo "" + echo "Digest: \`${{ steps.build.outputs.digest }}\`" + } >> "${GITHUB_STEP_SUMMARY}" diff --git a/Cargo.lock b/Cargo.lock index 11faeb92..8d67bfb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -736,7 +736,7 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastroll" -version = "0.1.37" +version = "0.1.38" dependencies = [ "clap", "fr-common", @@ -766,7 +766,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fr-asn-types" -version = "0.1.37" +version = "0.1.38" dependencies = [ "bitvec", "fr-block", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "fr-block" -version = "0.1.37" +version = "0.1.38" dependencies = [ "bitvec", "fr-codec", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "fr-clock" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-common", "time", @@ -803,7 +803,7 @@ dependencies = [ [[package]] name = "fr-codec" -version = "0.1.37" +version = "0.1.38" dependencies = [ "bitvec", "fr-codec-derive", @@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "fr-codec-derive" -version = "0.1.37" +version = "0.1.38" dependencies = [ "quote", "syn", @@ -821,7 +821,7 @@ dependencies = [ [[package]] name = "fr-common" -version = "0.1.37" +version = "0.1.38" dependencies = [ "cfg-if", "fr-codec", @@ -838,14 +838,14 @@ dependencies = [ [[package]] name = "fr-config" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-db", ] [[package]] name = "fr-crypto" -version = "0.1.37" +version = "0.1.38" dependencies = [ "ark-vrf", "base32", @@ -865,7 +865,7 @@ dependencies = [ [[package]] name = "fr-db" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-codec", "fr-common", @@ -877,7 +877,7 @@ dependencies = [ [[package]] name = "fr-erasure-coding" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-common", "rayon", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "fr-extrinsics" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-block", "fr-codec", @@ -904,7 +904,7 @@ dependencies = [ [[package]] name = "fr-fuzz" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-block", "fr-codec", @@ -924,7 +924,7 @@ dependencies = [ [[package]] name = "fr-integration" -version = "0.1.37" +version = "0.1.38" dependencies = [ "async-trait", "fr-asn-types", @@ -953,14 +953,14 @@ dependencies = [ [[package]] name = "fr-limited-vec" -version = "0.1.37" +version = "0.1.38" dependencies = [ "thiserror 2.0.17", ] [[package]] name = "fr-merkle" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-codec", "fr-common", @@ -970,7 +970,7 @@ dependencies = [ [[package]] name = "fr-network" -version = "0.1.37" +version = "0.1.38" dependencies = [ "async-trait", "dashmap 6.1.0", @@ -989,7 +989,7 @@ dependencies = [ [[package]] name = "fr-node" -version = "0.1.37" +version = "0.1.38" dependencies = [ "clap", "fr-block", @@ -1016,7 +1016,7 @@ dependencies = [ [[package]] name = "fr-node-bench" -version = "0.1.37" +version = "0.1.38" dependencies = [ "criterion", "fr-clock", @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "fr-pvm-core" -version = "0.1.37" +version = "0.1.38" dependencies = [ "bitvec", "fr-codec", @@ -1040,7 +1040,7 @@ dependencies = [ [[package]] name = "fr-pvm-host" -version = "0.1.37" +version = "0.1.38" dependencies = [ "async-trait", "fr-codec", @@ -1057,7 +1057,7 @@ dependencies = [ [[package]] name = "fr-pvm-interface" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-codec", "fr-common", @@ -1073,7 +1073,7 @@ dependencies = [ [[package]] name = "fr-pvm-invocation" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-codec", "fr-common", @@ -1091,7 +1091,7 @@ dependencies = [ [[package]] name = "fr-pvm-types" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-codec", "fr-common", @@ -1100,7 +1100,7 @@ dependencies = [ [[package]] name = "fr-state" -version = "0.1.37" +version = "0.1.38" dependencies = [ "async-trait", "bitvec", @@ -1127,7 +1127,7 @@ dependencies = [ [[package]] name = "fr-state-merkle-v2" -version = "0.1.37" +version = "0.1.38" dependencies = [ "bitvec", "fr-codec", @@ -1143,7 +1143,7 @@ dependencies = [ [[package]] name = "fr-storage" -version = "0.1.37" +version = "0.1.38" dependencies = [ "async-trait", "fr-block", @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "fr-test-utils" -version = "0.1.37" +version = "0.1.38" dependencies = [ "async-trait", "fr-asn-types", @@ -1180,7 +1180,7 @@ dependencies = [ [[package]] name = "fr-transition" -version = "0.1.37" +version = "0.1.38" dependencies = [ "fr-block", "fr-codec", diff --git a/Cargo.toml b/Cargo.toml index 0edc217e..c3ccfe07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ ] [workspace.package] -version = "0.1.37" +version = "0.1.38" edition = "2021" license = "Apache-2.0" authors = ["Junha Park <0xjunha@gmail.com>"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ac10e73f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1.7 + +FROM rust:1-bookworm AS builder + +WORKDIR /workspace + +RUN apt-get update \ + && apt-get install -y --no-install-recommends libclang-dev clang \ + && rm -rf /var/lib/apt/lists/* + +COPY . . + +ARG CARGO_PROFILE=prod +ARG CARGO_BIN_DIR=prod + +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/workspace/target \ + cargo build --locked --package fastroll --profile "${CARGO_PROFILE}" --features "tiny,fuzz" \ + && cp "target/${CARGO_BIN_DIR}/fastroll" /usr/local/bin/fastroll-tiny \ + && cargo build --locked --package fastroll --profile "${CARGO_PROFILE}" --features "full,fuzz" \ + && cp "target/${CARGO_BIN_DIR}/fastroll" /usr/local/bin/fastroll-full + +FROM debian:bookworm-slim AS runtime + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates libgcc-s1 libstdc++6 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/local/bin/fastroll-tiny /usr/local/bin/fastroll-tiny +COPY --from=builder /usr/local/bin/fastroll-full /usr/local/bin/fastroll-full +COPY docker/entrypoint.sh /usr/local/bin/fastroll-entrypoint + +RUN chmod 0755 /usr/local/bin/fastroll-tiny \ + /usr/local/bin/fastroll-full \ + /usr/local/bin/fastroll-entrypoint + +ENTRYPOINT ["/usr/local/bin/fastroll-entrypoint"] +CMD ["--help"] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 83d1af18..b8058b31 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -16,5 +16,6 @@ clap = { workspace = true } tokio = { workspace = true } [features] -tiny = ["fr-common/tiny", "fr-node/tiny"] -full = ["fr-common/full", "fr-node/full"] +tiny = ["fr-common/tiny", "fr-node/tiny", "fr-fuzz/tiny"] +full = ["fr-common/full", "fr-node/full", "fr-fuzz/full"] +fuzz = ["fr-node/fuzz"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..9e72e6c5 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,49 @@ +#!/bin/sh +set -eu + +fail() { + echo "fastroll-entrypoint: $*" >&2 + exit 64 +} + +require_env() { + name="$1" + eval "value=\${$name:-}" + [ -n "$value" ] || fail "$name must be set when JAM_FUZZ is defined" +} + +if [ "${JAM_FUZZ+x}" ]; then + require_env JAM_FUZZ_SPEC + require_env JAM_FUZZ_DATA_PATH + require_env JAM_FUZZ_SOCK_PATH + + case "$JAM_FUZZ_SPEC" in + tiny) + fastroll_bin="/usr/local/bin/fastroll-tiny" + ;; + full) + fastroll_bin="/usr/local/bin/fastroll-full" + ;; + *) + fail "JAM_FUZZ_SPEC must be either 'tiny' or 'full'" + ;; + esac + + if [ -n "${JAM_FUZZ_LOG_LEVEL:-}" ]; then + case "$JAM_FUZZ_LOG_LEVEL" in + error|warn|info|debug|trace) + export RUST_LOG="${RUST_LOG:-$JAM_FUZZ_LOG_LEVEL}" + ;; + *) + fail "JAM_FUZZ_LOG_LEVEL must be one of error, warn, info, debug, trace" + ;; + esac + fi + + mkdir -p "$JAM_FUZZ_DATA_PATH" + mkdir -p "$(dirname "$JAM_FUZZ_SOCK_PATH")" + + exec "$fastroll_bin" fuzz --socket "$JAM_FUZZ_SOCK_PATH" +fi + +exec /usr/local/bin/fastroll-tiny "$@" diff --git a/extrinsics/src/validation/disputes.rs b/extrinsics/src/validation/disputes.rs index c5199122..656ec137 100644 --- a/extrinsics/src/validation/disputes.rs +++ b/extrinsics/src/validation/disputes.rs @@ -189,15 +189,15 @@ impl DisputesXtValidator { VerdictEvaluation::Invalid(positive_votes) => { return Err(XtError::InvalidVotesCount(positive_votes)) } - VerdictEvaluation::IsGood => { - if extrinsic.count_faults_with_report_hash(&entry.report_hash) < 1 { - return Err(XtError::NotEnoughFault(entry.report_hash.encode_hex())); - } + VerdictEvaluation::IsGood + if extrinsic.count_faults_with_report_hash(&entry.report_hash) < 1 => + { + return Err(XtError::NotEnoughFault(entry.report_hash.encode_hex())); } - VerdictEvaluation::IsBad => { - if extrinsic.count_culprits_with_report_hash(&entry.report_hash) < 2 { - return Err(XtError::NotEnoughCulprit(entry.report_hash.encode_hex())); - } + VerdictEvaluation::IsBad + if extrinsic.count_culprits_with_report_hash(&entry.report_hash) < 2 => + { + return Err(XtError::NotEnoughCulprit(entry.report_hash.encode_hex())); } _ => (), } diff --git a/fuzz/src/fuzz_target.rs b/fuzz/src/fuzz_target.rs index 3545d954..fa4d00c3 100644 --- a/fuzz/src/fuzz_target.rs +++ b/fuzz/src/fuzz_target.rs @@ -490,10 +490,14 @@ impl FuzzTargetRunner { } // Handle incoming messages + let mut session_initialized = false; loop { match StreamUtils::read_message(&mut stream).await { Ok(message_kind) => { - if let Err(e) = self.process_message(&mut stream, message_kind).await { + if let Err(e) = self + .process_message(&mut stream, message_kind, &mut session_initialized) + .await + { if Self::is_session_disconnect_error(&e) { tracing::info!("Fuzzer session disconnected (read_message)"); return Ok(()); @@ -561,10 +565,19 @@ impl FuzzTargetRunner { &mut self, stream: &mut UnixStream, message_kind: FuzzMessageKind, + session_initialized: &mut bool, ) -> Result<(), FuzzTargetError> { match message_kind { FuzzMessageKind::Initialize(init) => { tracing::info!("[RECV][Initialize] Received message"); + if *session_initialized { + tracing::warn!( + "Received a second Initialize in the same fuzzing session; closing session" + ); + return Err(FuzzTargetError::InvalidMessageKind( + "Initialize received more than once in a session".to_string(), + )); + } if self.state_initialized { self.reset_state_context()?; } @@ -610,6 +623,7 @@ impl FuzzTargetRunner { .await?; tracing::info!("[SEND][Initialize] root={state_root}"); self.state_initialized = true; + *session_initialized = true; Ok(()) } FuzzMessageKind::ImportBlock(import_block) => { diff --git a/fuzz/src/tests.rs b/fuzz/src/tests.rs index af23720b..029ab918 100644 --- a/fuzz/src/tests.rs +++ b/fuzz/src/tests.rs @@ -16,7 +16,7 @@ mod fuzz_target_tests { ByteEncodable, }; use fr_test_utils::importer_harness::TestCase as BlockImportCase; - use std::{path::PathBuf, str::FromStr, time::Duration}; + use std::{io::ErrorKind, path::PathBuf, str::FromStr, time::Duration}; use tempfile::tempdir; use tokio::{net::UnixStream, task::JoinHandle, time::timeout}; @@ -265,7 +265,7 @@ mod fuzz_target_tests { } #[tokio::test] - async fn test_fuzz_reinitialize_in_same_session() -> Result<(), FuzzTargetError> { + async fn test_second_initialize_closes_connection() -> Result<(), FuzzTargetError> { setup_tracing(); let _temp_dir_sock = tempdir().unwrap(); let socket_path = _temp_dir_sock @@ -303,8 +303,17 @@ mod fuzz_target_tests { ancestry: Ancestry::default(), }, ) - .await?; - assert_eq!(root_2.0, test_case_2.post_state.state_root); + .await; + assert!(matches!( + root_2, + Err(FuzzTargetError::IoError(e)) + if matches!( + e.kind(), + ErrorKind::UnexpectedEof + | ErrorKind::BrokenPipe + | ErrorKind::ConnectionReset + ) + )); cleanup_socket(&socket_path); Ok(()) diff --git a/pvm/pvm-core/src/program/instruction/mod.rs b/pvm/pvm-core/src/program/instruction/mod.rs index 29a1390c..eef61daa 100644 --- a/pvm/pvm-core/src/program/instruction/mod.rs +++ b/pvm/pvm-core/src/program/instruction/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unnecessary_min_or_max)] + pub mod opcode; pub mod set; pub(crate) mod utils; diff --git a/pvm/pvm-core/src/program/instruction/set.rs b/pvm/pvm-core/src/program/instruction/set.rs index daeeae9a..720e5076 100644 --- a/pvm/pvm-core/src/program/instruction/set.rs +++ b/pvm/pvm-core/src/program/instruction/set.rs @@ -1,3 +1,5 @@ +#![allow(clippy::manual_checked_ops)] + use crate::{ continue_with_mem_write, continue_with_reg_write, error::VMCoreError, diff --git a/transition/src/state/accumulate.rs b/transition/src/state/accumulate.rs index 0e645443..decc90dd 100644 --- a/transition/src/state/accumulate.rs +++ b/transition/src/state/accumulate.rs @@ -77,7 +77,7 @@ pub async fn transition_accumulate_history( StateMut::Update, |history| -> Result<(), StateManagerError> { // Add the latest history entry, shifting by one entry if the list is full. - history.add(BTreeSet::from_iter(accumulated.into_iter())); + history.add(BTreeSet::from_iter(accumulated)); Ok(()) }, )