diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 89c4009..8fda881 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -140,6 +140,31 @@ jobs: echo "CU usage has changed. Please run `cargo bench` and commit the new results."; exit 1; fi + + fd_conformance: + name: Builtin-BPF Conformance Test + runs-on: ubuntu-latest + needs: build_programs + steps: + - name: Git Checkout + uses: actions/checkout@v4 + + - name: Setup Environment + uses: ./.github/actions/setup + with: + cargo-cache-key: cargo-program-conformance + cargo-cache-fallback-key: cargo-programs + solana: true + + - name: Restore Program Builds + uses: actions/cache/restore@v4 + with: + path: ./**/*.so + key: ${{ runner.os }}-builds-${{ github.sha }} + + - name: Builtin-BPF Conformance Test + shell: bash + run: pnpm zx ./scripts/ci/fd-conformance.mjs ## SKIP: IDL is hand-cranked here for now. ## diff --git a/.gitignore b/.gitignore index 70487d2..74673ed 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ node_modules test-ledger dist +solana-conformance diff --git a/Cargo.toml b/Cargo.toml index 8d32cda..020aeaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] resolver = "2" members = ["clients/rust", "program"] +# Required for CI +exclude = ["solana-conformance/impl/solfuzz-agave"] [workspace.metadata.cli] solana = "2.0.2" diff --git a/scripts/ci/fd-conformance.mjs b/scripts/ci/fd-conformance.mjs new file mode 100644 index 0000000..3ca7672 --- /dev/null +++ b/scripts/ci/fd-conformance.mjs @@ -0,0 +1,85 @@ +#!/usr/bin/env zx + +// Firedancer conformance testing of the Core BPF Config program against its +// original builtin implementation. +// +// Note: This script can only be run on Ubuntu. + +import 'zx/globals'; +import { getProgramId, getProgramSharedObjectPath, workingDirectory } from '../utils.mjs'; + +// Clone the conformance harness. +const harnessPath = path.join(workingDirectory, 'solana-conformance'); +await $`git clone https://github.com/firedancer-io/solana-conformance.git`; + +// Clone the test vectors. +const testVectorsPath = path.join(harnessPath, 'impl', 'test-vectors'); +await $`git clone https://github.com/firedancer-io/test-vectors.git ${testVectorsPath}`; + +// Remove the fixtures we want to skip. +const fixturesPath = path.join(testVectorsPath, 'instr', 'fixtures', 'config'); +const skipFixtures = [ + // These fixtures provide executable config accounts, which we know is a + // case that can't manifest under the Solana protocol. + '04a0b782cb1f4b1be044313331edda9dfb4696d6.fix', + 'c7ec10c03d5faadcebd32dc5b9a4086abef892ca_3157979.fix', + '68e8dbf0f31de69a2bd1d2c0fe9af3ba676301d6_3157979.fix', + 'f84b5ad44f7a253ebc8056d06396694370a7fa4c_3157979.fix', + '8bbe900444c675cfc3fbf0f80ae2eb061e536a09.fix', +]; +for (const fixture of skipFixtures) { + await $`rm -f ${path.join(fixturesPath, fixture)}`; +} + +// Clone the SolFuzz-Agave harness. +const solFuzzAgavePath = path.join(harnessPath, 'impl', 'solfuzz-agave'); +await $`git clone -b agave-v2.1.0 http://github.com/firedancer-io/solfuzz-agave.git ${solFuzzAgavePath}`; + +// Fetch protobuf files. +await $`make -j -C ${solFuzzAgavePath} fetch_proto` + +// Move into the conformance harness. +cd(harnessPath); + +// Build the environment. +await $`bash install_ubuntu_lite.sh`; + +const solFuzzAgaveManifestPath = path.join(solFuzzAgavePath, 'Cargo.toml'); +const solFuzzAgaveTargetPath = path.join( + solFuzzAgavePath, + 'target', + 'x86_64-unknown-linux-gnu', + 'release', + 'libsolfuzz_agave.so', +); + +const testTargetsDir = path.join(harnessPath, 'impl', 'lib'); +await $`mkdir -p ${testTargetsDir}`; + +// Build the Agave target with the builtin version. +const testTargetPathBuiltin = path.join(testTargetsDir, 'builtin.so'); +await $`cargo build --manifest-path ${solFuzzAgaveManifestPath} \ + --lib --release --target x86_64-unknown-linux-gnu`; +await $`mv ${solFuzzAgaveTargetPath} ${testTargetPathBuiltin}`; + +// Build the Agave target with the BPF version. +const testTargetPathCoreBpf = path.join(testTargetsDir, 'core_bpf.so'); +await $`CORE_BPF_PROGRAM_ID=${getProgramId('program')} \ + CORE_BPF_TARGET=${getProgramSharedObjectPath('program')} \ + FORCE_RECOMPILE=true \ + cargo build --manifest-path ${solFuzzAgaveManifestPath} \ + --lib --release --target x86_64-unknown-linux-gnu --features core-bpf`; +await $`mv ${solFuzzAgaveTargetPath} ${testTargetPathCoreBpf}`; + +// Run the tests. +await $`source test_suite_env/bin/activate && \ + solana-test-suite run-tests \ + -i ${fixturesPath} -s ${testTargetPathBuiltin} -t ${testTargetPathCoreBpf}`; + +// Assert conformance. +// There should be no fixtures in the `failed_protobufs` directory. +if (fs.readdirSync('test_results/failed_protobufs').length > 0) { + throw new Error(`Error: mismatches detected.`); +} else { + console.log('All tests passed.'); +} diff --git a/scripts/utils.mjs b/scripts/utils.mjs index 44ee564..77bba9a 100644 --- a/scripts/utils.mjs +++ b/scripts/utils.mjs @@ -70,6 +70,27 @@ export function getAllProgramFolders() { ); } +export function getProgramId(folder) { + return getCargoMetadata(folder)?.solana?.['program-id']; +} + +export function getProgramName(folder) { + return getCargo(folder).package?.name; +} + +export function getProgramSharedObjectName(folder) { + return `${getProgramName(folder).replace(/-/g, '_')}.so`; +} + +export function getProgramSharedObjectPath(folder) { + return path.join( + workingDirectory, + 'target', + 'deploy', + getProgramSharedObjectName(folder), + ); +} + export function getCargo(folder) { return parseToml( fs.readFileSync(