newton-aggregator 0.4.13

newton prover aggregator utils
Documentation
//! Operator-set + reference-timestamp snapshot reader.
//!
//! The `BN254Certificate` references operators *as of* `referenceTimestamp`,
//! so `(operator_set, reference_timestamp)` is a single atomic snapshot —
//! splitting the read across two unsynchronized sources risks committing
//! against an operator set whose APK does not match the cert verifier at
//! that timestamp.
//!
//! [`OperatorSetSnapshotReader`] is the trait the orchestrator depends on.
//! For NEWT-1116 a production impl will compose
//! `ViewBN254CertificateVerifier::latestReferenceTimestamp` with
//! `AvsRegistryServiceCaller` keyed on the matching reference block.
//! Until then, [`StubOperatorSetSnapshotReader`] (dev-stub) returns a
//! fixed snapshot the gateway wires from config.
//!
//! ## Eventual-consistency caveat
//!
//! Same shape as [`crate::state_commit::registry_view`]: a transporter
//! sync landing across the operator-set query and the cert-verifier query
//! produces a snapshot whose APK is one round behind. The certificate's
//! BLS verification at submit time catches the mismatch and the
//! orchestrator's poison handling rebuilds on the next tick.

use async_trait::async_trait;

use crate::state_commit::{error::StateCommitError, orchestrator::OperatorEntry};

/// Atomic snapshot of the operator set valid at `reference_timestamp`.
///
/// Returned by [`OperatorSetSnapshotReader::snapshot`] at the start of every
/// state-commit tick. The orchestrator no longer pins these values at
/// construction — every tick re-queries so transporter syncs and operator
/// (de)registrations take effect within one 120s cycle.
#[derive(Clone)]
pub struct OperatorSetSnapshot {
    /// Operator set used for prepare/commit fanout and BLS aggregation.
    ///
    /// **An empty `Vec` is a misconfiguration sentinel**, not a normal state.
    /// It indicates the underlying registry / cert-verifier has not been
    /// seeded yet (typical during cold-start before NEWT-1116 lands the
    /// production reader, or in dev-stub builds wired with `Vec::new()` as a
    /// placeholder). The orchestrator surfaces this as
    /// [`StateCommitError::OperatorDisagreement`] on the next tick — which is
    /// non-poison, so the next tick re-reads the snapshot fresh and clears
    /// once seeding lands.
    pub operators: Vec<OperatorEntry>,
    /// `referenceTimestamp` to embed in the BN254 certificate. Production
    /// callers source this from `latestReferenceTimestamp` on the cert
    /// verifier.
    pub reference_timestamp: u32,
}

impl std::fmt::Debug for OperatorSetSnapshot {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("OperatorSetSnapshot")
            .field("operators", &self.operators.len())
            .field("reference_timestamp", &self.reference_timestamp)
            .finish()
    }
}

/// Source of [`OperatorSetSnapshot`] reads for the state-commit orchestrator.
///
/// `Send + Sync + 'static` so the orchestrator can hold the reader behind
/// [`std::sync::Arc`] across `tokio::spawn` boundaries.
#[async_trait]
pub trait OperatorSetSnapshotReader: Send + Sync + 'static {
    /// Read the snapshot fresh — no caching at this layer.
    ///
    /// # Errors
    ///
    /// Returns [`StateCommitError::OnchainCallFailed`] for transport-side
    /// failures. The eight typed `IStateRootCommittable` reverts are NOT
    /// produced here — view calls don't surface PDS reverts.
    async fn snapshot(&self) -> Result<OperatorSetSnapshot, StateCommitError>;
}

/// Dev-stub reader that returns a fixed snapshot on every call.
///
/// Replaces the construction-time injection used in tests and in the
/// `dev-stub` aggregator wiring before NEWT-1116 lands the production
/// `AvsRegistryServiceCaller` + `latestReferenceTimestamp` reader.
#[cfg(any(test, feature = "dev-stub"))]
#[derive(Debug, Clone)]
pub struct StubOperatorSetSnapshotReader {
    snapshot: OperatorSetSnapshot,
}

#[cfg(any(test, feature = "dev-stub"))]
impl StubOperatorSetSnapshotReader {
    /// Construct a stub reader returning a fixed
    /// `(operators, reference_timestamp)` snapshot.
    pub fn new(operators: Vec<OperatorEntry>, reference_timestamp: u32) -> Self {
        Self {
            snapshot: OperatorSetSnapshot {
                operators,
                reference_timestamp,
            },
        }
    }
}

#[cfg(any(test, feature = "dev-stub"))]
#[async_trait]
impl OperatorSetSnapshotReader for StubOperatorSetSnapshotReader {
    async fn snapshot(&self) -> Result<OperatorSetSnapshot, StateCommitError> {
        Ok(self.snapshot.clone())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloy::primitives::U256;
    use ark_bn254::{G1Affine, G2Affine};
    use eigensdk::{
        crypto_bls::{BlsG1Point, BlsG2Point},
        types::operator::OperatorId,
    };
    use std::sync::Arc;

    fn make_entry(id_byte: u8) -> OperatorEntry {
        let mut id = OperatorId::default();
        id[0] = id_byte;
        OperatorEntry {
            operator_id: id,
            stake: U256::from(1_000u64),
            g1_pubkey: BlsG1Point::new(G1Affine::identity()),
            g2_pubkey: BlsG2Point::new(G2Affine::identity()),
        }
    }

    #[tokio::test]
    async fn stub_returns_configured_snapshot() {
        let reader = StubOperatorSetSnapshotReader::new(vec![make_entry(0x01), make_entry(0x02)], 42);
        let snap = reader.snapshot().await.expect("stub never errors");
        assert_eq!(snap.operators.len(), 2);
        assert_eq!(snap.reference_timestamp, 42);
    }

    #[tokio::test]
    async fn stub_is_object_safe_behind_arc() {
        let reader: Arc<dyn OperatorSetSnapshotReader> = Arc::new(StubOperatorSetSnapshotReader::new(vec![], 0));
        let snap = reader.snapshot().await.expect("stub never errors");
        assert!(snap.operators.is_empty());
        assert_eq!(snap.reference_timestamp, 0);
    }

    #[tokio::test]
    async fn stub_returns_independent_clones_per_call() {
        // Mutating one returned snapshot must not leak back into the reader's
        // internal state — guards against accidentally moving the inner Vec.
        let reader = StubOperatorSetSnapshotReader::new(vec![make_entry(0x01)], 7);
        let mut s1 = reader.snapshot().await.expect("call 1");
        s1.operators.clear();
        let s2 = reader.snapshot().await.expect("call 2");
        assert_eq!(s2.operators.len(), 1);
        assert_eq!(s2.reference_timestamp, 7);
    }
}