diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 948280f..0b46b98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,9 @@ concurrency: group: rust-validation-${{ github.head_ref }} cancel-in-progress: true +env: + RUST_LOG: "gadget=trace" + jobs: fmt: name: Rustfmt @@ -91,19 +94,10 @@ jobs: with: cache-on-failure: "true" - - uses: taiki-e/install-action@v2 - with: - tool: nextest - - name: Forge build run: forge update && forge build - uses: taiki-e/github-actions/free-device-space@main - - name: Download Tangle Manual Sealing - run: | - wget https://github.com/tangle-network/tangle/releases/download/v1.2.3/tangle-testnet-manual-seal-linux-amd64 - chmod +x tangle-testnet-manual-seal-linux-amd64 - - name: tests - run: TANGLE_NODE=$(pwd)/tangle-testnet-manual-seal-linux-amd64 cargo nextest run \ No newline at end of file + run: cargo test -- --test-threads=1 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 20dbe79..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,295 +0,0 @@ -# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/ -# -# Copyright 2022-2024, axodotdev -# SPDX-License-Identifier: MIT or Apache-2.0 -# -# CI that: -# -# * checks for a Git Tag that looks like a release -# * builds artifacts with dist (archives, installers, hashes) -# * uploads those artifacts to temporary workflow zip -# * on success, uploads the artifacts to a GitHub Release -# -# Note that the GitHub Release will be created with a generated -# title/body based on your changelogs. - -name: Release -permissions: - "attestations": "write" - "contents": "write" - "id-token": "write" - -# This task will run whenever you push a git tag that looks like a version -# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. -# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where -# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION -# must be a Cargo-style SemVer Version (must have at least major.minor.patch). -# -# If PACKAGE_NAME is specified, then the announcement will be for that -# package (erroring out if it doesn't have the given version or isn't dist-able). -# -# If PACKAGE_NAME isn't specified, then the announcement will be for all -# (dist-able) packages in the workspace with that version (this mode is -# intended for workspaces with only one dist-able package, or with all dist-able -# packages versioned/released in lockstep). -# -# If you push multiple tags at once, separate instances of this workflow will -# spin up, creating an independent announcement for each one. However, GitHub -# will hard limit this to 3 tags per commit, as it will assume more tags is a -# mistake. -# -# If there's a prerelease-style suffix to the version, then the release(s) -# will be marked as a prerelease. -on: - pull_request: - push: - tags: - - '**[0-9]+.[0-9]+.[0-9]+*' - -jobs: - # Run 'dist plan' (or host) to determine what tasks we need to do - plan: - runs-on: "ubuntu-20.04" - outputs: - val: ${{ steps.plan.outputs.manifest }} - tag: ${{ !github.event.pull_request && github.ref_name || '' }} - tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} - publishing: ${{ !github.event.pull_request }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install dist - # we specify bash to get pipefail; it guards against the `curl` command - # failing. otherwise `sh` won't catch that `curl` returned non-0 - shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.25.1/cargo-dist-installer.sh | sh" - - name: Cache dist - uses: actions/upload-artifact@v4 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/dist - # sure would be cool if github gave us proper conditionals... - # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible - # functionality based on whether this is a pull_request, and whether it's from a fork. - # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* - # but also really annoying to build CI around when it needs secrets to work right.) - - id: plan - run: | - dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --target=x86_64-unknown-linux-gnu --output-format=json > plan-dist-manifest.json - echo "dist ran successfully" - cat plan-dist-manifest.json - echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v4 - with: - name: artifacts-plan-dist-manifest - path: plan-dist-manifest.json - - # Build and packages all the platform-specific things - build-local-artifacts: - name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) - # Let the initial task tell us to not run (currently very blunt) - needs: - - plan - if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} - strategy: - fail-fast: false - # Target platforms/runners are computed by dist in create-release. - # Each member of the matrix has the following arguments: - # - # - runner: the github runner - # - dist-args: cli flags to pass to dist - # - install-dist: expression to run to install dist on the runner - # - # Typically there will be: - # - 1 "global" task that builds universal installers - # - N "local" tasks that build each platform's binaries and platform-specific installers - matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} - runs-on: ${{ matrix.runner }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json - steps: - - name: enable windows longpaths - run: | - git config --global core.longpaths true - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: "Install Foundry" - uses: "foundry-rs/foundry-toolchain@v1" - - name: "Verify Forge installation" - run: "forge --version" - - name: "Install Solidity Dependencies" - run: "forge soldeer update -d" - - name: Install dist - run: ${{ matrix.install_dist }} - # Get the dist-manifest - - name: Fetch local artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - name: Install dependencies - run: | - ${{ matrix.packages_install }} - - name: Build artifacts - run: | - # Actually do builds and make zips and whatnot - dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json - echo "dist ran successfully" - - name: Attest - uses: actions/attest-build-provenance@v1 - with: - subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*" - - id: cargo-dist - name: Post-build - # We force bash here just because github makes it really hard to get values up - # to "real" actions without writing to env-vars, and writing to env-vars has - # inconsistent syntax between shell and powershell. - shell: bash - run: | - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v4 - with: - name: artifacts-build-local-${{ join(matrix.targets, '_') }} - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - - # Build and package all the platform-agnostic(ish) things - build-global-artifacts: - needs: - - plan - - build-local-artifacts - runs-on: "ubuntu-20.04" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v4 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Get all the local artifacts for the global tasks to use (for e.g. checksums) - - name: Fetch local artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: cargo-dist - shell: bash - run: | - dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json - echo "dist ran successfully" - - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v4 - with: - name: artifacts-build-global - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - # Determines if we should publish/announce - host: - needs: - - plan - - build-local-artifacts - - build-global-artifacts - # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) - if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - runs-on: "ubuntu-20.04" - outputs: - val: ${{ steps.host.outputs.manifest }} - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v4 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Fetch artifacts from scratch-storage - - name: Fetch artifacts - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: host - shell: bash - run: | - dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json - echo "artifacts uploaded and released successfully" - cat dist-manifest.json - echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v4 - with: - # Overwrite the previous copy - name: artifacts-dist-manifest - path: dist-manifest.json - # Create a GitHub Release while uploading all files to it - - name: "Download GitHub Artifacts" - uses: actions/download-artifact@v4 - with: - pattern: artifacts-* - path: artifacts - merge-multiple: true - - name: Cleanup - run: | - # Remove the granular manifests - rm -f artifacts/*-dist-manifest.json - - name: Create GitHub Release - env: - PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" - ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" - ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" - RELEASE_COMMIT: "${{ github.sha }}" - run: | - # Write and read notes from a file to avoid quoting breaking things - echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt - - gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - - announce: - needs: - - plan - - host - # use "always() && ..." to allow us to wait for all publish jobs while - # still allowing individual publish jobs to skip themselves (for prereleases). - # "host" however must run to completion, no skipping allowed! - if: ${{ always() && needs.host.result == 'success' }} - runs-on: "ubuntu-20.04" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive diff --git a/Cargo.toml b/Cargo.toml index 8de1b48..41e374f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,8 @@ edition = "2021" manager = { Evm = "BlsBlueprint" } [dependencies] -gadget-sdk = { git = "https://github.com/tangle-network/gadget/", features = ["std"] } +gadget-sdk = { git = "https://github.com/tangle-network/gadget", branch = "drew/download-binary", features = ["std"] } color-eyre = { version = "0.6", features = ["tracing-error", "color-spantrace"] } -tokio = { version = "1.40", default-features = false, features = ["full"] } hex = { version = "0.4.3", default-features = false } k256 = { version = "0.13.3", default-features = false } bincode = "1.3.3" @@ -28,11 +27,12 @@ snowbridge-milagro-bls = "1.5.4" gennaro-dkg = { version = "0.8.0", features = ["curve25519"]} [build-dependencies] -blueprint-metadata = { git = "https://github.com/tangle-network/gadget/" } +blueprint-metadata = { git = "https://github.com/tangle-network/gadget", branch = "drew/download-binary" } [dev-dependencies] -blueprint-test-utils = { git = "https://github.com/tangle-network/gadget/" } -cargo-tangle = { git = "https://github.com/tangle-network/gadget/" } +blueprint-test-utils = { git = "https://github.com/tangle-network/gadget", branch = "drew/download-binary" } +cargo-tangle = { git = "https://github.com/tangle-network/gadget", branch = "drew/download-binary" } +tokio = { version = "1.42.0", features = ["test-util"] } #alloy-primitives = "0.7.2" [features] diff --git a/build.rs b/build.rs index dcbb32b..acb638b 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,5 @@ fn main() { - println!("cargo:rerun-if-changed=src/cli"); println!("cargo:rerun-if-changed=src/lib.rs"); println!("cargo:rerun-if-changed=src/main.rs"); - println!("cargo:rerun-if-changed=src/*"); blueprint_metadata::generate_json(); } diff --git a/remappings.txt b/remappings.txt index eaa0683..cb0de4b 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,2 +1,2 @@ -forge-std/=dependencies/forge-std-1.9.4/src/ -tnt-core/=dependencies/tnt-core-0.1.0/src/ +forge-std/=dependencies/forge-std-1.9.4/src +tnt-core/=dependencies/tnt-core-0.1.0/src diff --git a/soldeer.lock b/soldeer.lock index 39b5d16..b36ba3b 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -9,4 +9,4 @@ integrity = "3874463846ab995a6a9a88412913cacec6144f7605daa1af57c2d8bf3f210b13" name = "tnt-core" version = "0.1.0" git = "https://github.com/tangle-network/tnt-core.git" -rev = "dc71ca12f9b5d01b898adb2057c9b8846c90bda2" +rev = "df6d5d6da3c82217405c408d3c0b11369bb3e5c2" diff --git a/src/context.rs b/src/context.rs index 27fe6ea..e2a4308 100644 --- a/src/context.rs +++ b/src/context.rs @@ -24,6 +24,8 @@ const NETWORK_PROTOCOL: &str = "/bls/gennaro/1.0.0"; pub struct BlsContext { #[config] pub config: sdk::config::StdGadgetConfiguration, + #[call_id] + pub call_id: Option, pub network_backend: Arc, pub store: Arc>, pub identity: ecdsa::Pair, @@ -52,6 +54,7 @@ impl BlsContext { Ok(Self { store, identity, + call_id: None, config, network_backend: Arc::new(NetworkMultiplexer::new(gossip_handle)), }) diff --git a/tests/bls.rs b/tests/bls.rs index b2ff1b2..a279d60 100644 --- a/tests/bls.rs +++ b/tests/bls.rs @@ -1,35 +1,144 @@ -#[cfg(test)] -mod e2e { - use std::sync::atomic::AtomicU64; - - use bls_blueprint::keygen::KEYGEN_JOB_ID; - use bls_blueprint::signing::SIGN_JOB_ID; - use blueprint_test_utils::*; - - const N: usize = 3; - const T: usize = 2; - - // The macro takes this variable as an argument, and will update it so that - // when we pass the signing arguments, we can pass the associated keygen call id - static KEYGEN_CALL_ID: AtomicU64 = AtomicU64::new(0); - - mpc_generate_keygen_and_signing_tests!( - "./", - N, - T, - KEYGEN_JOB_ID, - [InputValue::Uint16(T as u16)], - [], - SIGN_JOB_ID, - [ - InputValue::Uint64(KEYGEN_CALL_ID.load(std::sync::atomic::Ordering::SeqCst)), +use bls_blueprint::keygen::KEYGEN_JOB_ID; +use bls_blueprint::signing::SIGN_JOB_ID; + +const N: usize = 3; +const T: usize = 2; + +use blueprint_test_utils::tangle::NodeConfig; +use blueprint_test_utils::test_ext::new_test_ext_blueprint_manager; +use blueprint_test_utils::{ + get_next_call_id, run_test_blueprint_manager, setup_log, submit_job, + wait_for_completion_of_tangle_job, BoundedVec, InputValue, Job, +}; + +#[tokio::test(flavor = "multi_thread")] +async fn test_blueprint() { + setup_log(); + gadget_sdk::info!("Running BLS blueprint test"); + let tmp_dir = blueprint_test_utils::tempfile::TempDir::new().unwrap(); + let tmp_dir_path = tmp_dir.path().to_string_lossy().into_owned(); + let node_config = NodeConfig::new(false); + + new_test_ext_blueprint_manager::( + tmp_dir_path, + run_test_blueprint_manager, + node_config, + ) + .await + .execute_with_async(|client, handles, blueprint, _| async move { + let keypair = handles[0].sr25519_id().clone(); + let service = &blueprint.services[KEYGEN_JOB_ID as usize]; + + let service_id = service.id; + gadget_sdk::info!( + "Submitting KEYGEN job {KEYGEN_JOB_ID} with service ID {service_id}", + ); + + let job_args = vec![(InputValue::Uint16(T as u16))]; + let call_id = get_next_call_id(client) + .await + .expect("Failed to get next job id") + .saturating_sub(1); + let job = submit_job( + client, + &keypair, + service_id, + Job::from(KEYGEN_JOB_ID), + job_args, + call_id, + ) + .await + .expect("Failed to submit job"); + + let keygen_call_id = job.call_id; + + gadget_sdk::info!( + "Submitted KEYGEN job {} with service ID {service_id} has call id {keygen_call_id}", + KEYGEN_JOB_ID + ); + + let job_results = wait_for_completion_of_tangle_job(client, service_id, keygen_call_id, T) + .await + .expect("Failed to wait for job completion"); + + assert_eq!(job_results.service_id, service_id); + assert_eq!(job_results.call_id, keygen_call_id); + + let expected_outputs = vec![]; + if !expected_outputs.is_empty() { + assert_eq!( + job_results.result.len(), + expected_outputs.len(), + "Number of keygen outputs doesn't match expected" + ); + + for (result, expected) in job_results + .result + .into_iter() + .zip(expected_outputs.into_iter()) + { + assert_eq!(result, expected); + } + } else { + gadget_sdk::info!("No expected outputs specified, skipping keygen verification"); + } + + gadget_sdk::info!("Keygen job completed successfully! Moving on to signing ..."); + + let service = &blueprint.services[0]; + let service_id = service.id; + gadget_sdk::info!( + "Submitting SIGNING job {} with service ID {service_id}", + SIGN_JOB_ID + ); + + let job_args = vec![ + InputValue::Uint64(keygen_call_id), InputValue::List(BoundedVec(vec![ InputValue::Uint8(1), InputValue::Uint8(2), InputValue::Uint8(3), - ])) - ], - [], - KEYGEN_CALL_ID, - ); + ])), + ]; + + let job = submit_job( + client, + &keypair, + service_id, + Job::from(SIGN_JOB_ID), + job_args, + call_id + 1, + ) + .await + .expect("Failed to submit job"); + + let signing_call_id = job.call_id; + gadget_sdk::info!( + "Submitted SIGNING job {SIGN_JOB_ID} with service ID {service_id} has call id {signing_call_id}", + ); + + let job_results = wait_for_completion_of_tangle_job(client, service_id, signing_call_id, T) + .await + .expect("Failed to wait for job completion"); + + let expected_outputs = vec![]; + if !expected_outputs.is_empty() { + assert_eq!( + job_results.result.len(), + expected_outputs.len(), + "Number of signing outputs doesn't match expected" + ); + + for (result, expected) in job_results + .result + .into_iter() + .zip(expected_outputs.into_iter()) + { + assert_eq!(result, expected); + } + } else { + gadget_sdk::info!("No expected outputs specified, skipping signing verification"); + } + }) + .await }