use std::sync::Arc;
use alloy::primitives::{Address, B256};
use async_trait::async_trait;
use eigensdk::common::SdkProvider;
use newton_prover_core::{
bn254_certificate_verifier::{
IOperatorTableCalculatorTypes::BN254OperatorInfo,
ViewBN254CertificateVerifier::{OperatorSet as VerifierOperatorSet, ViewBN254CertificateVerifierInstance},
BN254::G1Point,
},
bn254_table_calculator::BN254TableCalculator::{
BN254TableCalculatorInstance, OperatorSet as CalculatorOperatorSet,
},
};
fn calc_op_info_to_verifier(
calc_info: newton_prover_core::bn254_table_calculator::IOperatorTableCalculatorTypes::BN254OperatorInfo,
) -> BN254OperatorInfo {
BN254OperatorInfo {
pubkey: G1Point {
X: calc_info.pubkey.X,
Y: calc_info.pubkey.Y,
},
weights: calc_info.weights,
}
}
fn calc_g1_to_verifier(calc: newton_prover_core::bn254_table_calculator::BN254::G1Point) -> G1Point {
G1Point { X: calc.X, Y: calc.Y }
}
#[derive(Debug, Clone)]
pub struct OperatorTableSnapshot {
pub reference_timestamp: u32,
pub operators: Vec<BN254OperatorInfo>,
pub aggregate_pubkey_g1: G1Point,
pub operator_info_tree_root: B256,
}
impl OperatorTableSnapshot {
pub fn index_by_g1_pubkey(&self, pubkey: &G1Point) -> Option<u32> {
self.operators
.iter()
.position(|op| op.pubkey == *pubkey)
.map(|i| i as u32)
}
}
#[derive(Debug, thiserror::Error)]
pub enum OperatorTableError {
#[error("on-chain query failed: {0}")]
Rpc(String),
#[error("no operator-table root has been confirmed on-chain yet")]
NoConfirmedRoot,
#[error("operator set is empty: {0}")]
EmptySet(String),
}
#[async_trait]
pub trait OperatorTableProvider: Send + Sync + 'static {
async fn fetch_snapshot(
&self,
operator_set: VerifierOperatorSet,
) -> Result<OperatorTableSnapshot, OperatorTableError>;
}
#[derive(Debug)]
pub struct HttpOperatorTableProvider {
table_calculator: Address,
cert_verifier: Address,
provider: SdkProvider,
}
impl HttpOperatorTableProvider {
pub fn new(table_calculator: Address, cert_verifier: Address, dest_rpc_url: &str) -> Self {
Self {
table_calculator,
cert_verifier,
provider: eigensdk::common::get_provider(dest_rpc_url),
}
}
}
#[async_trait]
impl OperatorTableProvider for HttpOperatorTableProvider {
async fn fetch_snapshot(
&self,
operator_set: VerifierOperatorSet,
) -> Result<OperatorTableSnapshot, OperatorTableError> {
let calculator = BN254TableCalculatorInstance::new(self.table_calculator, &self.provider);
let verifier = ViewBN254CertificateVerifierInstance::new(self.cert_verifier, &self.provider);
let calculator_op_set = CalculatorOperatorSet {
avs: operator_set.avs,
id: operator_set.id,
};
let reference_timestamp = verifier
.latestReferenceTimestamp(operator_set.clone())
.call()
.await
.map_err(|e| OperatorTableError::Rpc(format!("latestReferenceTimestamp: {e}")))?;
if reference_timestamp == 0 {
return Err(OperatorTableError::NoConfirmedRoot);
}
let infos_call = calculator.getOperatorInfos(calculator_op_set.clone());
let set_info_call = calculator.getOperatorSetInfo(calculator_op_set);
let (calc_operators_res, set_info_res) = tokio::try_join!(infos_call.call(), set_info_call.call(),)
.map_err(|e| OperatorTableError::Rpc(format!("calculator parallel read: {e}")))?;
if calc_operators_res.is_empty() {
return Err(OperatorTableError::EmptySet(format!(
"BN254TableCalculator.getOperatorInfos returned 0 operators for set {:?}",
operator_set
)));
}
let operators: Vec<BN254OperatorInfo> = calc_operators_res.into_iter().map(calc_op_info_to_verifier).collect();
let set_info = set_info_res;
if set_info.operatorInfoTreeRoot.is_zero() {
return Err(OperatorTableError::EmptySet(format!(
"BN254TableCalculator.getOperatorSetInfo returned zero tree root for set {:?}",
operator_set
)));
}
Ok(OperatorTableSnapshot {
reference_timestamp,
operators,
aggregate_pubkey_g1: calc_g1_to_verifier(set_info.aggregatePubkey),
operator_info_tree_root: set_info.operatorInfoTreeRoot,
})
}
}
#[cfg(any(test, feature = "dev-stub"))]
pub mod fake {
use super::*;
use alloy::primitives::U256;
#[derive(Debug)]
pub struct FakeOperatorTableProvider {
snapshot: OperatorTableSnapshot,
}
impl FakeOperatorTableProvider {
pub fn new(snapshot: OperatorTableSnapshot) -> Arc<Self> {
Arc::new(Self { snapshot })
}
pub fn from_operators(reference_timestamp: u32, operators: Vec<BN254OperatorInfo>) -> Arc<Self> {
Arc::new(Self {
snapshot: OperatorTableSnapshot {
reference_timestamp,
operators,
aggregate_pubkey_g1: G1Point {
X: U256::ZERO,
Y: U256::ZERO,
},
operator_info_tree_root: B256::ZERO,
},
})
}
}
#[async_trait]
impl OperatorTableProvider for FakeOperatorTableProvider {
async fn fetch_snapshot(
&self,
_operator_set: VerifierOperatorSet,
) -> Result<OperatorTableSnapshot, OperatorTableError> {
Ok(self.snapshot.clone())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::primitives::U256;
fn op_info(x: u8, y: u8) -> BN254OperatorInfo {
BN254OperatorInfo {
pubkey: G1Point {
X: U256::from(x),
Y: U256::from(y),
},
weights: vec![U256::from(100)],
}
}
#[test]
fn index_by_g1_pubkey_finds_canonical_position() {
let snap = OperatorTableSnapshot {
reference_timestamp: 42,
operators: vec![op_info(1, 2), op_info(3, 4), op_info(5, 6)],
aggregate_pubkey_g1: G1Point {
X: U256::ZERO,
Y: U256::ZERO,
},
operator_info_tree_root: B256::ZERO,
};
assert_eq!(
snap.index_by_g1_pubkey(&G1Point {
X: U256::from(3),
Y: U256::from(4)
}),
Some(1)
);
assert_eq!(
snap.index_by_g1_pubkey(&G1Point {
X: U256::from(99),
Y: U256::from(99)
}),
None
);
}
#[tokio::test]
async fn fake_provider_returns_constructed_snapshot() {
let snapshot = OperatorTableSnapshot {
reference_timestamp: 12345,
operators: vec![op_info(1, 2)],
aggregate_pubkey_g1: G1Point {
X: U256::from(1),
Y: U256::from(2),
},
operator_info_tree_root: B256::repeat_byte(0xAB),
};
let provider = fake::FakeOperatorTableProvider::new(snapshot.clone());
let op_set = VerifierOperatorSet {
avs: Address::repeat_byte(0x11),
id: 0,
};
let fetched = provider.fetch_snapshot(op_set).await.expect("fetch");
assert_eq!(fetched.reference_timestamp, 12345);
assert_eq!(fetched.operators.len(), 1);
assert_eq!(fetched.operator_info_tree_root, B256::repeat_byte(0xAB));
}
}