From 58344fd820fa8fd8b941ca8aa8ab918a35f667b1 Mon Sep 17 00:00:00 2001 From: Arthur Meyre Date: Fri, 19 Jul 2024 10:48:46 +0200 Subject: [PATCH 1/4] chore(ci): auto data branch --- .github/workflows/aws_tfhe_fast_tests.yml | 12 ++++++++++++ Makefile | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/aws_tfhe_fast_tests.yml b/.github/workflows/aws_tfhe_fast_tests.yml index 48b20e400b..800f936a95 100644 --- a/.github/workflows/aws_tfhe_fast_tests.yml +++ b/.github/workflows/aws_tfhe_fast_tests.yml @@ -216,12 +216,24 @@ jobs: run: | make test_safe_deserialization + - name: Use specific data branch + if: ${{ contains(github.event.label.name, 'data_PR') }} + run: | + echo "BACKWARD_COMPAT_DATA_BRANCH=${{ github.ref }}" >> "${GITHUB_ENV}" + + - name: Get backward compat branch + id: backward_compat_branch + run: | + BRANCH="$(make backward_compat_branch)" + echo "branch=${BRANCH}" >> "${GITHUB_OUTPUT}" + - name: Clone test data uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 with: repository: zama-ai/tfhe-backward-compat-data path: tfhe/tfhe-backward-compat-data lfs: 'true' + ref: ${{ steps.backward_compat_branch.outputs.branch }} - name: Run backward compatibility tests run: | diff --git a/Makefile b/Makefile index 7efcbcc619..2a0be7b39c 100644 --- a/Makefile +++ b/Makefile @@ -756,6 +756,10 @@ test_backward_compatibility_ci: install_rs_build_toolchain .PHONY: test_backward_compatibility # Same as test_backward_compatibility_ci but tries to clone the data repo first if needed test_backward_compatibility: tfhe/$(BACKWARD_COMPAT_DATA_DIR) test_backward_compatibility_ci +.PHONY: backward_compat_branch # Prints the required backward compatibility branch +backward_compat_branch: + @echo "$(BACKWARD_COMPAT_DATA_BRANCH)" + .PHONY: doc # Build rust doc doc: install_rs_check_toolchain @# Even though we are not in docs.rs, this allows to "just" build the doc From d77a66c7aee4f6ac0744907cbc40b9e63d7d4f3a Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Thu, 18 Jul 2024 11:28:20 +0200 Subject: [PATCH 2/4] chore(hl): add data tests for heterogeneous lists --- .../backward_compatibility/high_level_api.rs | 144 +++++++++++++++++- 1 file changed, 136 insertions(+), 8 deletions(-) diff --git a/tfhe/tests/backward_compatibility/high_level_api.rs b/tfhe/tests/backward_compatibility/high_level_api.rs index 3c42ca9a94..86385d32ee 100644 --- a/tfhe/tests/backward_compatibility/high_level_api.rs +++ b/tfhe/tests/backward_compatibility/high_level_api.rs @@ -5,20 +5,22 @@ use tfhe::backward_compatibility::booleans::{CompactFheBool, CompactFheBoolList} use tfhe::backward_compatibility::integers::{ CompactFheInt8, CompactFheInt8List, CompactFheUint8, CompactFheUint8List, }; + use tfhe::prelude::{FheDecrypt, FheEncrypt}; use tfhe::shortint::PBSParameters; use tfhe::{ - set_server_key, ClientKey, CompactCiphertextList, CompressedCompactPublicKey, - CompressedFheBool, CompressedFheInt8, CompressedFheUint8, CompressedPublicKey, - CompressedServerKey, FheUint8, + set_server_key, ClientKey, CompactCiphertextList, CompressedCiphertextList, + CompressedCompactPublicKey, CompressedFheBool, CompressedFheInt8, CompressedFheUint8, + CompressedPublicKey, CompressedServerKey, FheBool, FheInt8, FheUint8, }; use tfhe_backward_compat_data::load::{ load_versioned_auxiliary, DataFormat, TestFailure, TestResult, TestSuccess, }; use tfhe_backward_compat_data::{ - HlBoolCiphertextListTest, HlBoolCiphertextTest, HlCiphertextListTest, HlCiphertextTest, - HlClientKeyTest, HlPublicKeyTest, HlServerKeyTest, HlSignedCiphertextListTest, - HlSignedCiphertextTest, TestMetadata, TestParameterSet, TestType, Testcase, + DataKind, HlBoolCiphertextListTest, HlBoolCiphertextTest, HlCiphertextListTest, + HlCiphertextTest, HlClientKeyTest, HlHeterogeneousCiphertextListTest, HlPublicKeyTest, + HlServerKeyTest, HlSignedCiphertextListTest, HlSignedCiphertextTest, TestMetadata, + TestParameterSet, TestType, Testcase, }; use tfhe_versionable::Unversionize; @@ -257,6 +259,129 @@ pub fn test_hl_bool_ciphertext_list( } } +/// Test HL ciphertext list: loads the ciphertext list and compare the decrypted values to the ones +/// in the metadata. +pub fn test_hl_heterogeneous_ciphertext_list( + dir: &Path, + test: &HlHeterogeneousCiphertextListTest, + format: DataFormat, +) -> Result { + let key_file = dir.join(&*test.key_filename); + let key = ClientKey::unversionize( + load_versioned_auxiliary(key_file).map_err(|e| test.failure(e, format))?, + ) + .map_err(|e| test.failure(e, format))?; + + let server_key = key.generate_server_key(); + set_server_key(server_key); + + if test.compressed { + test_hl_heterogeneous_ciphertext_list_compressed( + load_and_unversionize(dir, test, format)?, + &key, + test, + ) + } else { + test_hl_heterogeneous_ciphertext_list_compact( + load_and_unversionize(dir, test, format)?, + &key, + test, + ) + } + .map(|_| test.success(format)) + .map_err(|msg| test.failure(msg, format)) +} + +pub fn test_hl_heterogeneous_ciphertext_list_compact( + list: CompactCiphertextList, + key: &ClientKey, + test: &HlHeterogeneousCiphertextListTest, +) -> Result<(), String> { + let ct_list = list.expand().unwrap(); + + for idx in 0..(ct_list.len()) { + match test.data_kinds[idx] { + DataKind::Bool => { + let ct: FheBool = ct_list.get(idx).unwrap().unwrap(); + let clear = ct.decrypt(key); + if clear != (test.clear_values[idx] != 0) { + return Err(format!( + "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", + clear, test.clear_values[idx] + )); + } + } + DataKind::Signed => { + let ct: FheInt8 = ct_list.get(idx).unwrap().unwrap(); + let clear: i8 = ct.decrypt(key); + if clear != test.clear_values[idx] as i8 { + return Err(format!( + "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", + clear, + (test.clear_values[idx] as i8) + )); + } + } + DataKind::Unsigned => { + let ct: FheUint8 = ct_list.get(idx).unwrap().unwrap(); + let clear: u8 = ct.decrypt(key); + if clear != test.clear_values[idx] as u8 { + return Err(format!( + "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", + clear, test.clear_values[idx] + )); + } + } + }; + } + Ok(()) +} + +pub fn test_hl_heterogeneous_ciphertext_list_compressed( + list: CompressedCiphertextList, + key: &ClientKey, + test: &HlHeterogeneousCiphertextListTest, +) -> Result<(), String> { + let ct_list = list; + + for idx in 0..(ct_list.len()) { + match test.data_kinds[idx] { + DataKind::Bool => { + let ct: FheBool = ct_list.get(idx).unwrap().unwrap(); + let clear = ct.decrypt(key); + if clear != (test.clear_values[idx] != 0) { + return Err(format!( + "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", + clear, test.clear_values[idx] + )); + } + } + DataKind::Signed => { + let ct: FheInt8 = ct_list.get(idx).unwrap().unwrap(); + let clear: i8 = ct.decrypt(key); + if clear != test.clear_values[idx] as i8 { + return Err(format!( + "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", + clear, + (test.clear_values[idx] as i8) + )); + } + } + DataKind::Unsigned => { + let ct: FheUint8 = ct_list.get(idx).unwrap().unwrap(); + let clear: u8 = ct.decrypt(key); + if clear != test.clear_values[idx] as u8 { + return Err(format!( + "Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}", + clear, test.clear_values[idx] + )); + } + } + }; + } + Ok(()) +} + /// Test HL client key: loads the key and checks the parameters using the values stored in /// the test metadata. pub fn test_hl_clientkey( @@ -333,8 +458,8 @@ pub fn test_hl_pubkey( } } -/// Test HL server key: encrypt to values with a client key, add them using the server key and check -/// that the decrypted sum is valid. +/// Test HL server key: encrypt two values with a client key, add them using the server key and +/// check that the decrypted sum is valid. pub fn test_hl_serverkey( dir: &Path, test: &HlServerKeyTest, @@ -406,6 +531,9 @@ impl TestedModule for Hl { TestMetadata::HlSignedCiphertextList(test) => { test_hl_signed_ciphertext_list(test_dir.as_ref(), test, format).into() } + TestMetadata::HlHeterogeneousCiphertextList(test) => { + test_hl_heterogeneous_ciphertext_list(test_dir.as_ref(), test, format).into() + } TestMetadata::HlClientKey(test) => { test_hl_clientkey(test_dir.as_ref(), test, format).into() } From a687d2c2bbc8717b529c8e528db34dcb02f15602 Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Fri, 19 Jul 2024 14:12:42 +0200 Subject: [PATCH 3/4] chore(ci): use specific workflow for data compatibility tests --- .../aws_tfhe_backward_compat_tests.yml | 120 ++++++++++++++++++ .github/workflows/aws_tfhe_fast_tests.yml | 27 ---- 2 files changed, 120 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/aws_tfhe_backward_compat_tests.yml diff --git a/.github/workflows/aws_tfhe_backward_compat_tests.yml b/.github/workflows/aws_tfhe_backward_compat_tests.yml new file mode 100644 index 0000000000..056a944971 --- /dev/null +++ b/.github/workflows/aws_tfhe_backward_compat_tests.yml @@ -0,0 +1,120 @@ +# Run backward compatibility tests +name: Backward compatibility Tests on CPU + +env: + CARGO_TERM_COLOR: always + ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + RUSTFLAGS: "-C target-cpu=native" + RUST_BACKTRACE: "full" + RUST_MIN_STACK: "8388608" + SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }} + SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png + SLACK_USERNAME: ${{ secrets.BOT_USERNAME }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + +on: + # Allows you to run this workflow manually from the Actions tab as an alternative. + workflow_dispatch: + pull_request: + +jobs: + setup-instance: + name: Setup instance (backward-compat-tests) + runs-on: ubuntu-latest + outputs: + runner-name: ${{ steps.start-instance.outputs.label }} + steps: + - name: Start instance + id: start-instance + uses: zama-ai/slab-github-runner@447a2d0fd2d1a9d647aa0d0723a6e9255372f261 + with: + mode: start + github-token: ${{ secrets.SLAB_ACTION_TOKEN }} + slab-url: ${{ secrets.SLAB_BASE_URL }} + job-secret: ${{ secrets.JOB_SECRET }} + backend: aws + profile: cpu-small + + backward-compat-tests: + name: Backward compatibility tests + needs: [ setup-instance ] + concurrency: + group: ${{ github.workflow }}_${{ github.ref }} + cancel-in-progress: true + runs-on: ${{ needs.setup-instance.outputs.runner-name }} + steps: + - name: Checkout tfhe-rs + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + with: + persist-credentials: 'false' + + - name: Set up home + run: | + echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}" + + - name: Install latest stable + uses: dtolnay/rust-toolchain@21dc36fb71dd22e3317045c0c31a3f4249868b17 + with: + toolchain: stable + + - name: Install git-lfs + run: | + sudo apt update && sudo apt -y install git-lfs + + - name: Use specific data branch + if: ${{ contains(github.event.pull_request.labels.*.name, 'data_PR') }} + env: + PR_BRANCH: ${{ github.head_ref || github.ref_name }} + run: | + echo "BACKWARD_COMPAT_DATA_BRANCH=${PR_BRANCH}" >> "${GITHUB_ENV}" + + - name: Get backward compat branch + id: backward_compat_branch + run: | + BRANCH="$(make backward_compat_branch)" + echo "branch=${BRANCH}" >> "${GITHUB_OUTPUT}" + + - name: Clone test data + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 + with: + persist-credentials: 'false' + repository: zama-ai/tfhe-backward-compat-data + path: tfhe/tfhe-backward-compat-data + lfs: 'true' + ref: ${{ steps.backward_compat_branch.outputs.branch }} + + - name: Run backward compatibility tests + run: | + make test_backward_compatibility_ci + + - name: Slack Notification + if: ${{ failure() }} + continue-on-error: true + uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 + env: + SLACK_COLOR: ${{ job.status }} + SLACK_MESSAGE: "Backward compatibility tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})" + + teardown-instance: + name: Teardown instance (backward-compat-tests) + if: ${{ always() && needs.setup-instance.result != 'skipped' }} + needs: [ setup-instance, backward-compat-tests ] + runs-on: ubuntu-latest + steps: + - name: Stop instance + id: stop-instance + uses: zama-ai/slab-github-runner@447a2d0fd2d1a9d647aa0d0723a6e9255372f261 + with: + mode: stop + github-token: ${{ secrets.SLAB_ACTION_TOKEN }} + slab-url: ${{ secrets.SLAB_BASE_URL }} + job-secret: ${{ secrets.JOB_SECRET }} + label: ${{ needs.setup-instance.outputs.runner-name }} + + - name: Slack Notification + if: ${{ failure() }} + continue-on-error: true + uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 + env: + SLACK_COLOR: ${{ job.status }} + SLACK_MESSAGE: "Instance teardown (backward-compat-tests) finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})" diff --git a/.github/workflows/aws_tfhe_fast_tests.yml b/.github/workflows/aws_tfhe_fast_tests.yml index 800f936a95..c0b7f4ebfc 100644 --- a/.github/workflows/aws_tfhe_fast_tests.yml +++ b/.github/workflows/aws_tfhe_fast_tests.yml @@ -157,10 +157,6 @@ jobs: with: toolchain: stable - - name: Install git-lfs - run: | - sudo apt update && sudo apt -y install git-lfs - - name: Run concrete-csprng tests if: needs.should-run.outputs.csprng_test == 'true' run: | @@ -216,29 +212,6 @@ jobs: run: | make test_safe_deserialization - - name: Use specific data branch - if: ${{ contains(github.event.label.name, 'data_PR') }} - run: | - echo "BACKWARD_COMPAT_DATA_BRANCH=${{ github.ref }}" >> "${GITHUB_ENV}" - - - name: Get backward compat branch - id: backward_compat_branch - run: | - BRANCH="$(make backward_compat_branch)" - echo "branch=${BRANCH}" >> "${GITHUB_OUTPUT}" - - - name: Clone test data - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 - with: - repository: zama-ai/tfhe-backward-compat-data - path: tfhe/tfhe-backward-compat-data - lfs: 'true' - ref: ${{ steps.backward_compat_branch.outputs.branch }} - - - name: Run backward compatibility tests - run: | - make test_backward_compatibility_ci - - name: Slack Notification if: ${{ always() }} continue-on-error: true From e46a373fb69d2d33ba6b4e02da76a595a7776221 Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Mon, 22 Jul 2024 17:52:00 +0200 Subject: [PATCH 4/4] chore(ci): automatically merge pr in the data repo --- .github/workflows/data_pr_close.yml | 123 ++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 .github/workflows/data_pr_close.yml diff --git a/.github/workflows/data_pr_close.yml b/.github/workflows/data_pr_close.yml new file mode 100644 index 0000000000..dee7dc872e --- /dev/null +++ b/.github/workflows/data_pr_close.yml @@ -0,0 +1,123 @@ +name: Close or Merge corresponding PR on the data repo + +# When a PR with the data_PR tag is closed or merged, this will close the corresponding PR in the data repo. + +env: + TARGET_REPO_API_URL: ${{ github.api_url }}/repos/zama-ai/tfhe-backward-compat-data + SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }} + SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png + SLACK_USERNAME: ${{ secrets.BOT_USERNAME }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + PR_BRANCH: ${{ github.head_ref || github.ref_name }} + CLOSE_TYPE: ${{ github.event.pull_request.merged && 'merge' || 'close' }} + +# only trigger on pull request closed events +on: + pull_request: + types: [ closed ] + +# The same pattern is used for jobs that use the github api: +# - save the result of the API call in the env var "GH_API_RES". Since the var is multiline +# we use this trick: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#example-of-a-multiline-string +# - "set +e" will make sure we reach the last "echo EOF" even in case of error +# - "set -o" pipefail makes one line piped command return the error of the first failure +# - 'RES="$?"' and 'exit $RES' are used to return the error code if a command failed. Without it, with "set +e" +# the script will always return 0 because of the "echo EOF". + + +jobs: + auto_close_job: + if: ${{ contains(github.event.pull_request.labels.*.name, 'data_PR') }} + runs-on: ubuntu-latest + steps: + - name: Find corresponding Pull Request in the data repo + run: | + { + set +e + set -o pipefail + echo 'GH_API_RES<> "${GITHUB_ENV}" + exit $RES + + - name: Comment on the PR to indicate the reason of the close + run: | + { + set +e + set -o pipefail + echo 'GH_API_RES<> "${GITHUB_ENV}" + exit $RES + + - name: Merge the Pull Request in the data repo + if: ${{ github.event.pull_request.merged }} + run: | + { + set +e + set -o pipefail + echo 'GH_API_RES<> "${GITHUB_ENV}" + exit $RES + + - name: Close the Pull Request in the data repo + if: ${{ !github.event.pull_request.merged }} + run: | + { + set +e + set -o pipefail + echo 'GH_API_RES<> "${GITHUB_ENV}" + exit $RES + + - name: Delete the associated branch in the data repo + run: | + { + set +e + set -o pipefail + echo 'GH_API_RES<> "${GITHUB_ENV}" + exit $RES + + - name: Slack Notification + if: ${{ always() && job.status == 'failure' }} + continue-on-error: true + uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 + env: + SLACK_COLOR: ${{ job.status }} + SLACK_MESSAGE: "Failed to auto-${{ env.CLOSE_TYPE }} PR on data repo: ${{ fromJson(env.GH_API_RES).message }}"