newton-core 0.7.5

newton protocol core sdk
//! Chain utilities and constants.
//!
//! This module contains:
//! - Pure chain constants and classification functions (always available)
//! - RPC-dependent functions for querying on-chain state (requires `rpc` feature)
//!
//! The constants and classification functions are re-exported in `io::chain`
//! for backward compatibility.

#[cfg(feature = "rpc")]
use crate::common::address::get_challenge_verifier_address;

#[cfg(feature = "rpc")]
use crate::{
    challenge_verifier::ChallengeVerifier, newton_policy_client::NewtonPolicyClient,
    newton_prover_task_manager::NewtonProverTaskManager,
};
#[cfg(feature = "rpc")]
use alloy::{
    primitives::{Address, ChainId, B256},
    providers::Provider,
};
#[cfg(feature = "rpc")]
use newton_rpc_provider::get_provider;
#[cfg(feature = "rpc")]
use tracing::info;

// Re-export commonly used address functions for convenience
#[cfg(feature = "rpc")]
pub use crate::common::address::{
    get_mock_newton_policy_client_address, get_newton_prover_service_manager, get_newton_prover_task_manager,
};

/// Get the chain ID from an RPC URL
#[cfg(feature = "rpc")]
pub async fn get_chain_id(rpc_url: &str) -> ChainId {
    let provider = get_provider(rpc_url);
    provider.get_chain_id().await.expect("Failed to get chain ID")
}

/// Fetches the task response window block from the task manager contract
/// This is the number of blocks (taskResponseWindowBlock) that defines the time window
/// within which operators must submit their task responses
#[cfg(feature = "rpc")]
pub async fn get_task_response_window_blocks(rpc_url: &str) -> eyre::Result<u32> {
    let chain_id = get_chain_id(rpc_url).await;
    let provider = get_provider(rpc_url);
    match chain_id {
        id if is_supported_chain(id) => {
            let task_manager_addr = get_newton_prover_task_manager(chain_id).await?;
            let task_manager = NewtonProverTaskManager::new(task_manager_addr, &provider);

            let task_response_window_block = task_manager
                .taskResponseWindowBlock()
                .call()
                .await
                .map_err(|e| eyre::eyre!("Failed to fetch task response window block from task manager: {}", e))?;

            Ok(task_response_window_block)
        }
        _ => Err(eyre::eyre!("Unsupported chain id: {}", chain_id)),
    }
}

/// Fetches the challenge window from the challenge verifier contract
#[cfg(feature = "rpc")]
pub async fn get_task_challenge_window_blocks(rpc_url: &str) -> eyre::Result<u32> {
    let chain_id = get_chain_id(rpc_url).await;
    let provider = get_provider(rpc_url);
    match chain_id {
        id if is_supported_chain(id) => {
            let challenge_verifier_addr = get_challenge_verifier_address(chain_id).await?;
            let challenge_verifier = ChallengeVerifier::new(challenge_verifier_addr, &provider);

            let challenge_window_blocks = challenge_verifier
                .taskChallengeWindowBlock()
                .call()
                .await
                .map_err(|e| eyre::eyre!("Failed to fetch task challenge window from challenge verifier: {}", e))?;

            Ok(challenge_window_blocks)
        }
        _ => Err(eyre::eyre!("Unsupported chain id: {}", chain_id)),
    }
}

/// Get block duration time in milliseconds per chain
///
/// # Supported Chains
///
/// - 1: Ethereum Mainnet (~12s)
/// - 11155111: Sepolia Testnet (~12s)
/// - 8453: Base (~2s)
/// - 84532: Base Sepolia (~2s)
/// - 4663: Robinhood Chain (~100ms)
/// - 46630: Robinhood Chain testnet (~100ms)
/// - 5042: Arc (~500ms)
/// - 5042002: Arc testnet (~500ms)
pub fn get_block_duration_ms(chain_id: ChainId) -> Result<u64, String> {
    match chain_id {
        ETHEREUM_MAINNET | SEPOLIA => Ok(12000),
        // Local anvil (31337 source, 31338 destination for multichain testing)
        1337 | LOCAL_ANVIL | LOCAL_ANVIL_DESTINATION => Ok(1000), // mock value
        BASE_MAINNET | BASE_SEPOLIA => Ok(2000),
        ROBINHOOD | ROBINHOOD_TESTNET => Ok(100),
        ARC | ARC_TESTNET => Ok(500),
        _ => Err(format!(
            "Unsupported chain ID: {}. Please add support for this chain.",
            chain_id
        )),
    }
}

/// Returns the block gas limit for the chain. Static table avoids an
/// `eth_getBlock` roundtrip at startup since limits change rarely.
pub fn get_block_gas_limit(chain_id: ChainId) -> u64 {
    match chain_id {
        ETHEREUM_MAINNET | SEPOLIA => 36_000_000,    // post-Pectra
        BASE_MAINNET | BASE_SEPOLIA => 240_000_000,  // OP Stack
        ROBINHOOD | ROBINHOOD_TESTNET => 32_000_000, // ArbGasInfo per-block limit; header reports a sentinel
        ARC | ARC_TESTNET => 30_000_000,             // measured on both networks, Oct 2026
        _ => 30_000_000,                             // anvil, conservative default
    }
}

/// Blocks to subtract from the observed head for `taskCreatedBlock`.
/// `BLSSignatureChecker.checkSignatures()` requires `referenceBlockNumber < block.number`;
/// 1 covers same-block races on L1, faster chains drift further between observed and executed head.
/// On Robinhood the contracts read `ArbSys.arbBlockNumber()`, the same L2 height `eth_blockNumber` reports.
pub const fn task_created_block_offset(chain_id: ChainId) -> u64 {
    match chain_id {
        BASE_MAINNET | BASE_SEPOLIA | ROBINHOOD | ROBINHOOD_TESTNET => 5,
        _ => 1,
    }
}

/// Fetches block time in milliseconds for the chain
#[cfg(feature = "rpc")]
pub async fn get_block_time_ms(rpc_url: &str) -> eyre::Result<u64> {
    let chain_id = get_chain_id(rpc_url).await;
    info!("Fetching block time for chain ID {}", chain_id);
    let block_time_ms =
        get_block_duration_ms(chain_id).map_err(|e| eyre::eyre!("Failed to get block duration: {}", e))?;

    info!("Block time for chain ID {} is {} ms", chain_id, block_time_ms);
    if block_time_ms == 0 {
        Err(eyre::eyre!("Block time is zero"))
    } else {
        Ok(block_time_ms)
    }
}

/// Fetches epoch time in number of blocks from the task manager contract
/// This is the number of blocks (epochBlocks) that defines the epoch duration
#[cfg(feature = "rpc")]
pub async fn get_epoch_blocks(rpc_url: &str) -> eyre::Result<u64> {
    let chain_id = get_chain_id(rpc_url).await;
    let provider = get_provider(rpc_url);
    match chain_id {
        31337 | 31338 | 11155111 | 1 | 84532 => {
            let task_manager_addr = get_newton_prover_task_manager(chain_id).await?;
            let task_manager = NewtonProverTaskManager::new(task_manager_addr, &provider);

            let epoch_blocks: u32 = task_manager
                .epochBlocks()
                .call()
                .await
                .map_err(|e| eyre::eyre!("Failed to fetch epoch blocks from task manager: {}", e))?;

            Ok(epoch_blocks as u64)
        }
        _ => Err(eyre::eyre!("Unsupported chain id: {}", chain_id)),
    }
}

/// Ethereum Mainnet
pub const ETHEREUM_MAINNET: u64 = 1;
/// Sepolia Testnet
pub const SEPOLIA: u64 = 11155111;
/// Local Anvil (primary)
pub const LOCAL_ANVIL: u64 = 31337;
/// Local Anvil (secondary, destination in dual-anvil mode)
pub const LOCAL_ANVIL_DESTINATION: u64 = 31338;

/// Base Mainnet (currently EigenLayer-supported; migrating to Newton registry)
pub const BASE_MAINNET: u64 = 8453;
/// Base Sepolia (currently EigenLayer-supported; migrating to Newton registry)
pub const BASE_SEPOLIA: u64 = 84532;

/// Robinhood Chain (Arbitrum Orbit)
pub const ROBINHOOD: u64 = 4663;
/// Robinhood Chain testnet
pub const ROBINHOOD_TESTNET: u64 = 46630;

/// Arc
pub const ARC: u64 = 5042;
/// Arc testnet
pub const ARC_TESTNET: u64 = 5042002;

/// Returns true if the chain is a source chain (where EigenLayer is deployed).
#[inline]
pub const fn is_source_chain(chain_id: u64) -> bool {
    matches!(chain_id, ETHEREUM_MAINNET | SEPOLIA | LOCAL_ANVIL)
}

/// Returns true if the chain is a destination chain.
/// A destination chain is any supported chain that is NOT a source chain.
#[inline]
pub const fn is_destination_chain(chain_id: u64) -> bool {
    is_supported_chain(chain_id) && !is_source_chain(chain_id)
}

/// Returns true if EigenLayer's Generator service currently operates on this destination.
///
/// Base chains temporarily use EigenLayer's CrossChainRegistry. Newton plans to
/// migrate all destinations (including Base) to NewtonCrossChainRegistry for
/// full self-sufficiency. Once migrated, this function will return false for all
/// chains and `requires_newton_registry` will return true for all destinations.
#[inline]
pub const fn is_eigenlayer_supported_destination(chain_id: u64) -> bool {
    matches!(chain_id, BASE_MAINNET | BASE_SEPOLIA)
}

/// Returns true if Newton's CrossChainRegistry is needed for this chain.
/// Currently required for destinations where EigenLayer doesn't operate.
/// Base temporarily uses EigenLayer's CrossChainRegistry but will migrate to
/// Newton's registry for self-sufficiency.
#[inline]
pub const fn requires_newton_registry(chain_id: u64) -> bool {
    is_destination_chain(chain_id) && !is_eigenlayer_supported_destination(chain_id)
}

/// Returns the source chain ID for any given chain.
#[inline]
pub const fn get_source_chain_id(chain_id: u64) -> Option<u64> {
    if is_source_chain(chain_id) {
        return Some(chain_id);
    }

    // Local destination maps to local source
    if chain_id == LOCAL_ANVIL_DESTINATION {
        return Some(LOCAL_ANVIL);
    }

    // Testnet destinations map to Sepolia
    if is_testnet(chain_id) {
        return Some(SEPOLIA);
    }

    // Mainnet destinations map to Ethereum
    if is_mainnet(chain_id) {
        return Some(ETHEREUM_MAINNET);
    }

    None // Unsupported chain
}

/// Returns true if this is a mainnet chain.
#[inline]
pub const fn is_mainnet(chain_id: u64) -> bool {
    matches!(chain_id, ETHEREUM_MAINNET | BASE_MAINNET | ROBINHOOD | ARC)
}

/// Returns true if this is a testnet chain.
#[inline]
pub const fn is_testnet(chain_id: u64) -> bool {
    matches!(chain_id, SEPOLIA | BASE_SEPOLIA | ROBINHOOD_TESTNET | ARC_TESTNET)
}

/// Returns true if this is a local development chain.
#[inline]
pub const fn is_local(chain_id: u64) -> bool {
    matches!(chain_id, LOCAL_ANVIL | LOCAL_ANVIL_DESTINATION)
}

/// Returns true if the chain is supported by Newton Prover AVS.
#[inline]
pub const fn is_supported_chain(chain_id: u64) -> bool {
    is_mainnet(chain_id) || is_testnet(chain_id) || is_local(chain_id)
}