Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ jobs:
- name: Run Unit Tests
run: 'cargo test --lib && cargo test --doc'

- name: Run Rust-to-Solidity slashing assertions
run: pnpm rust:test:slashing

- name: Cancel workflow on failure
if: failure()
run: gh run cancel ${{ github.run_id }}
Expand Down Expand Up @@ -1152,7 +1155,7 @@ jobs:
zk_prover_e2e:
needs: [detect_changes, build_circuits]
if: needs.detect_changes.outputs.zk == 'true'
timeout-minutes: 30
timeout-minutes: 60
runs-on:
${{ github.repository == 'theinterfold/interfold' && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' ||
github.ref == 'refs/heads/dev') && format('runs-on={0}-{1}-{2}/runner=4cpu-linux-x64/ram=16', github.run_id, github.run_attempt,
Expand Down Expand Up @@ -1233,6 +1236,12 @@ jobs:
- name: Run ZK prover e2e tests
run: cargo test -p e3-zk-prover --test local_e2e_tests -- --nocapture --test-threads=1

- name: Verify fold accumulators and the correlated node proof
timeout-minutes: 30
run:
cargo test --locked -p e3-zk-prover --test fold_accumulators_e2e_tests --test node_fold_correlated_e2e_tests -- --include-ignored
--nocapture --test-threads=1

build_e3_support_dev:
needs: [detect_changes]
if: needs.detect_changes.outputs.build_e3_support_dev == 'true'
Expand Down Expand Up @@ -1330,6 +1339,10 @@ jobs:
- name: Run the tests
run: pnpm sdk:test

- name: Verify SDK encryption proofs and reject altered bindings
timeout-minutes: 15
run: pnpm sdk:test:proofs:prepared

- name: Upload SDK artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
Expand Down
20 changes: 19 additions & 1 deletion agent/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ Run from repo root via pnpm scripts — not raw cargo/nargo/hardhat.
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Install / build all | `pnpm i` · `pnpm build` |
| Build Rust | `pnpm rust:build` (cargo `--locked --release`; prebuilds EVM fixtures) |
| Test everything | `pnpm test` (evm → rust → sdk → noir) |
| Test everything | `pnpm test` (EVM, Rust, required proof/slashing suites, SDK, Noir) |
| Test one layer | `pnpm evm:test` · `pnpm rust:test` · `pnpm sdk:test` · `pnpm noir:test` |
| SDK proof verification | `pnpm sdk:test:proofs` (prepare circuits, generate one proof, verify bindings and reject tampering) |
| Prepared SDK proof tests | `pnpm sdk:test:proofs:prepared` (reuse the current SDK build or prepared circuit set) |
| Rust proof integration | `pnpm rust:test:proofs` (prepared insecure-512/minimum circuits and `bb`) |
| Rust slashing integration | `pnpm rust:test:slashing` (compiled contract artifacts and `anvil`) |
| Integration tests | `pnpm test:integration [name]` (`--no-prebuild` to skip binary build) |
| Lint / format | `pnpm lint` · `pnpm format` / `pnpm format:check` |
| Build circuits | `pnpm build:circuits [--preset …] [--committee …]` (needs `nargo` + `bb`; `interfold noir setup` installs them) |
Expand All @@ -70,6 +74,20 @@ Run from repo root via pnpm scripts — not raw cargo/nargo/hardhat.
| Prepare release branch | `pnpm bump:versions X.Y.Z` |
| Tag merged release | `pnpm release:tag X.Y.Z` from updated `main` |

## Test preparation

`pnpm sdk:test` runs the fast SDK suites without circuit preparation. The proof API tests mock the
prover boundary. They do not claim to verify cryptographic proofs. The separate proof suite verifies
a real proof against the compiled verification key and rejects altered public inputs and proof
bytes.

Before `pnpm rust:test:proofs` or `pnpm test`, run
`pnpm build:circuits --preset insecure-512 --committee minimum --skip-if-built`. This prepares one
consistent set of inner and recursive circuits. Before `pnpm rust:test:slashing`, run
`pnpm evm:build`. The named Rust integration suites fail if a required tool or artifact is missing.
Ordinary Rust test runs report these integration tests as ignored. CI explicitly selects them. The
full test command reuses the prepared circuits for SDK proof verification.

## Chain-Specific BFV Config

The protocol release can carry more than one circuit artifact set. Current deployments use this
Expand Down
11 changes: 10 additions & 1 deletion crates/net/src/event_buffer/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub const DEFAULT_MAX_BUFFERED_NET_BYTES: usize = 256 * 1024 * 1024;

pub struct NetEventBufferHandle {
readiness: oneshot::Receiver<std::result::Result<(), String>>,
#[cfg(test)]
actor: actix::Addr<NetEventBuffer>,
}

impl NetEventBufferHandle {
Expand Down Expand Up @@ -78,7 +80,14 @@ impl NetEventBuffer {
// Subscribe to InterfoldEvent on the bus
bus.subscribe(EventType::SyncEnded, addr.clone().recipient());

(output, NetEventBufferHandle { readiness })
(
output,
NetEventBufferHandle {
readiness,
#[cfg(test)]
actor: addr,
},
)
}

fn handle_interfold_event(&mut self, msg: InterfoldEvent) -> Result<()> {
Expand Down
55 changes: 39 additions & 16 deletions crates/net/src/event_buffer/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,26 @@ use libp2p::{
};
use tokio::{
sync::{broadcast, mpsc},
time::{sleep, timeout},
time::timeout,
};

const DELIVERY_TIMEOUT: Duration = Duration::from_secs(5);

#[derive(Message)]
#[rtype(result = "usize")]
struct BufferedEventCount;

impl Handler<BufferedEventCount> for NetEventBuffer {
type Result = usize;

fn handle(&mut self, _: BufferedEventCount, _: &mut actix::Context<Self>) -> usize {
match &self.state {
NetEventBufferState::Syncing { events, .. } => events.len(),
state => panic!("expected startup buffering, got {state:?}"),
}
}
}

fn sync_and_connection_control_events() -> Vec<NetEvent> {
let (command_tx, _command_rx) = mpsc::channel(1);
vec![
Expand Down Expand Up @@ -96,24 +113,30 @@ async fn test_buffers_until_sync_ended() -> Result<()> {
input_tx.send(event1.clone()).unwrap();
input_tx.send(event2.clone()).unwrap();

// Give actor time to process
sleep(Duration::from_millis(10)).await;

// Verify no events forwarded yet (should timeout)
// Wait for observable actor progress, then check that no event was forwarded.
timeout(DELIVERY_TIMEOUT, async {
while handle.actor.send(BufferedEventCount).await? != 2 {
tokio::task::yield_now().await;
}
Ok::<_, anyhow::Error>(())
})
.await
.context("network events did not reach the startup buffer")??;
assert!(
timeout(Duration::from_millis(50), output_rx.recv())
.await
.is_err(),
matches!(
output_rx.try_recv(),
Err(broadcast::error::TryRecvError::Empty)
),
"Events should be buffered, not forwarded during sync"
);

// Send SyncEnded event
bus.publish_without_context(SyncEnded::new()).unwrap();
handle.wait_until_running().await?;
timeout(DELIVERY_TIMEOUT, handle.wait_until_running()).await??;

// Now buffered events should be forwarded
let received1 = output_rx.recv().await.unwrap();
let received2 = output_rx.recv().await.unwrap();
let received1 = timeout(DELIVERY_TIMEOUT, output_rx.recv()).await??;
let received2 = timeout(DELIVERY_TIMEOUT, output_rx.recv()).await??;

assert!(
matches!(received1, NetEvent::GossipData(GossipData::GossipBytes(ref bytes)) if bytes == &vec![1, 2, 3])
Expand All @@ -126,7 +149,7 @@ async fn test_buffers_until_sync_ended() -> Result<()> {
let event3 = NetEvent::GossipData(GossipData::GossipBytes(vec![7, 8, 9]));
input_tx.send(event3.clone()).unwrap();

let received3 = tokio::time::timeout(tokio::time::Duration::from_millis(100), output_rx.recv())
let received3 = timeout(DELIVERY_TIMEOUT, output_rx.recv())
.await
.expect("Event should be forwarded immediately after sync")
.unwrap();
Expand All @@ -150,7 +173,7 @@ async fn startup_buffer_overflow_fails_readiness_without_dropping_oldest() -> Re
input_tx.send(NetEvent::GossipData(GossipData::GossipBytes(vec![1])))?;
input_tx.send(NetEvent::GossipData(GossipData::GossipBytes(vec![2])))?;

let error = timeout(Duration::from_secs(1), handle.wait_until_running())
let error = timeout(DELIVERY_TIMEOUT, handle.wait_until_running())
.await
.context("network buffer did not report overflow")?
.expect_err("overflow must fail startup readiness")
Expand All @@ -176,7 +199,7 @@ async fn startup_buffer_enforces_estimated_payload_bytes() -> Result<()> {

input_tx.send(event)?;

let error = timeout(Duration::from_secs(1), handle.wait_until_running())
let error = timeout(DELIVERY_TIMEOUT, handle.wait_until_running())
.await
.context("network buffer did not report byte overflow")?
.expect_err("byte overflow must fail startup readiness")
Expand Down Expand Up @@ -212,8 +235,8 @@ async fn sync_control_burst_does_not_lag_or_consume_the_application_buffer() ->
event_tx.send(NetEvent::GossipData(GossipData::GossipBytes(vec![7])))?;
bus.publish_without_context(SyncEnded::new())?;

handle.wait_until_running().await?;
let forwarded = timeout(Duration::from_secs(5), output_rx.recv()).await??;
timeout(DELIVERY_TIMEOUT, handle.wait_until_running()).await??;
let forwarded = timeout(DELIVERY_TIMEOUT, output_rx.recv()).await??;
assert!(matches!(
forwarded,
NetEvent::GossipData(GossipData::GossipBytes(bytes)) if bytes == vec![7]
Expand Down
67 changes: 34 additions & 33 deletions crates/zk-prover/tests/fold_accumulators_e2e_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,13 @@ fn c6_fold_total_slots_from_compiled_json() -> usize {
}

#[test]
#[ignore = "requires compiled circuits; run pnpm rust:test:proofs"]
fn c3_fold_compiled_abi_has_consistent_slot_count() {
if !c3_fold_json_path().exists() {
println!(
"skipping: {} not found (run `pnpm build:circuits --group recursive_aggregation`)",
panic!(
"missing required test prerequisite: {} not found (run `pnpm build:circuits --group recursive_aggregation`)",
c3_fold_json_path().display()
);
return;
}
let slots = c3_fold_total_slots_from_compiled_json();
assert!(slots > 0, "C3_SLOTS inferred from ABI should be positive");
Expand All @@ -143,13 +143,13 @@ fn c3_fold_compiled_abi_has_consistent_slot_count() {
}

#[test]
#[ignore = "requires compiled circuits; run pnpm rust:test:proofs"]
fn c6_fold_compiled_abi_has_consistent_slot_count() {
if !c6_fold_json_path().exists() {
println!(
"skipping: {} not found (run `pnpm build:circuits --group recursive_aggregation`)",
panic!(
"missing required test prerequisite: {} not found (run `pnpm build:circuits --group recursive_aggregation`)",
c6_fold_json_path().display()
);
return;
}
let slots = c6_fold_total_slots_from_compiled_json();
assert!(slots > 0, "C6 slots inferred from ABI should be positive");
Expand All @@ -158,6 +158,7 @@ fn c6_fold_compiled_abi_has_consistent_slot_count() {
}

#[test]
#[ignore = "requires compiled circuits; run pnpm rust:test:proofs"]
fn node_fold_pipeline_compiled_json_load() {
let mut missing = Vec::new();
for &c in NODE_FOLD_PIPELINE {
Expand All @@ -167,11 +168,10 @@ fn node_fold_pipeline_compiled_json_load() {
}
}
if !missing.is_empty() {
println!(
"skipping: missing compiled JSON(s) (run `pnpm build:circuits --group recursive_aggregation`): {:?}",
panic!(
"missing required test prerequisite: missing compiled JSON(s) (run `pnpm build:circuits --group recursive_aggregation`): {:?}",
missing
);
return;
}
for &c in NODE_FOLD_PIPELINE {
let path = recursive_aggregation_compiled_json_path(c);
Expand All @@ -181,14 +181,16 @@ fn node_fold_pipeline_compiled_json_load() {
}

#[tokio::test]
#[ignore = "requires prepared integration artifacts; run pnpm rust:test:proofs"]
async fn recursive_aggregation_default_artifacts_staged() {
let Some(bb) = find_bb().await else {
println!("skipping: bb not found");
return;
panic!("missing required test prerequisite: bb not found");
};
if !c3_fold_json_path().exists() {
println!("skipping: {} not found", c3_fold_json_path().display());
return;
panic!(
"missing required test prerequisite: {} not found",
c3_fold_json_path().display()
);
}

let (backend, temp) = setup_test_prover(&bb).await;
Expand Down Expand Up @@ -216,16 +218,18 @@ async fn recursive_aggregation_default_artifacts_staged() {
}

#[tokio::test]
#[ignore = "requires prepared integration artifacts; run pnpm rust:test:proofs"]
async fn recursive_aggregation_c6_fold_kernel_artifacts_staged() {
let Some(bb) = find_bb().await else {
println!("skipping: bb not found");
return;
panic!("missing required test prerequisite: bb not found");
};
let kernel_json = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../circuits/bin/recursive_aggregation/c6_fold_kernel/target/c6_fold_kernel.json");
if !kernel_json.exists() {
println!("skipping: {} not found", kernel_json.display());
return;
panic!(
"missing required test prerequisite: {} not found",
kernel_json.display()
);
}

let (backend, temp) = setup_test_prover(&bb).await;
Expand Down Expand Up @@ -253,18 +257,17 @@ async fn recursive_aggregation_c6_fold_kernel_artifacts_staged() {
}

#[tokio::test]
#[ignore = "requires prepared integration artifacts; run pnpm rust:test:proofs"]
async fn node_fold_pipeline_recursive_aggregation_artifacts_staged() {
let Some(bb) = find_bb().await else {
println!("skipping: bb not found");
return;
panic!("missing required test prerequisite: bb not found");
};
let gate = recursive_aggregation_compiled_json_path(CircuitName::NodeFold);
if !gate.exists() {
println!(
"skipping: {} not found (run `pnpm build:circuits --group recursive_aggregation`)",
panic!(
"missing required test prerequisite: {} not found (run `pnpm build:circuits --group recursive_aggregation`)",
gate.display()
);
return;
}

let (backend, temp) = setup_test_prover(&bb).await;
Expand Down Expand Up @@ -343,27 +346,26 @@ async fn setup_c3_fold_with_inner_share_encryption() -> Option<(
}

/// Expected C3 fold slot count when circuits are compiled for the minimum committee (N=3, T=1).
const MINIMUM_C3_FOLD_SLOTS: usize = 2;
const MINIMUM_C3_FOLD_SLOTS: usize = 6;
/// Expected C6 fold slot count when circuits are compiled for the minimum committee (N=3, T=1).
const MINIMUM_C6_FOLD_SLOTS: usize = 2;

#[tokio::test]
#[ignore = "requires prepared integration artifacts; run pnpm rust:test:proofs"]
async fn c3_fold_sequential_proves_and_verifies() {
let Some((_backend, _temp, prover, circuit, sample_a, sample_b, preset)) =
setup_c3_fold_with_inner_share_encryption().await
else {
println!("skipping: bb not found or prerequisites missing");
return;
panic!("missing required test prerequisite: bb not found or prerequisites missing");
};

let total_slots = c3_fold_total_slots_from_compiled_json();
if total_slots != MINIMUM_C3_FOLD_SLOTS {
println!(
"skipping c3_fold_sequential_proves_and_verifies: circuits compiled for \
panic!(
"c3_fold_sequential_proves_and_verifies: circuits compiled for \
non-minimum committee (total_slots={total_slots}, expected {MINIMUM_C3_FOLD_SLOTS}). \
Rebuild with `pnpm build:circuits --committee minimum` to run this test."
);
return;
}

let artifacts_dir = preset.artifacts_dir_for_committee("minimum");
Expand Down Expand Up @@ -460,22 +462,21 @@ async fn setup_c6_fold_with_inner_threshold_share_decryption() -> Option<(
}

#[tokio::test]
#[ignore = "requires prepared integration artifacts; run pnpm rust:test:proofs"]
async fn c6_fold_sequential_proves_and_verifies() {
let Some((_backend, _temp, prover, circuit, sample_a, sample_b, preset)) =
setup_c6_fold_with_inner_threshold_share_decryption().await
else {
println!("skipping: bb not found or prerequisites missing");
return;
panic!("missing required test prerequisite: bb not found or prerequisites missing");
};

let total_slots = c6_fold_total_slots_from_compiled_json();
if total_slots != MINIMUM_C6_FOLD_SLOTS {
println!(
"skipping c6_fold_sequential_proves_and_verifies: circuits compiled for \
panic!(
"c6_fold_sequential_proves_and_verifies: circuits compiled for \
non-minimum committee (total_slots={total_slots}, expected {MINIMUM_C6_FOLD_SLOTS}). \
Rebuild with `pnpm build:circuits --committee minimum` to run this test."
);
return;
}
let artifacts_dir = preset.artifacts_dir_for_committee("minimum");
let inner_e3_a = "e3-c6fold-inner-0";
Expand Down
Loading
Loading