diff --git a/Cargo.lock b/Cargo.lock index 0c215996..6ce15c8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ version = "0.1.0" dependencies = [ "aggchain-proof-contracts", "aggchain-proof-core", - "aggkit-prover-types", + "aggchain-proof-types", "alloy", "futures", "prover-alloy", @@ -51,7 +51,7 @@ dependencies = [ name = "aggchain-proof-contracts" version = "0.1.0" dependencies = [ - "aggchain-proof-core", + "aggchain-proof-types", "alloy", "anyhow", "async-trait", @@ -97,8 +97,8 @@ name = "aggchain-proof-service" version = "0.1.0" dependencies = [ "aggchain-proof-builder", - "aggchain-proof-core", - "aggkit-prover-types", + "aggchain-proof-types", + "alloy-primitives", "anyhow", "futures", "proposer-service", @@ -110,11 +110,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "aggchain-proof-types" +version = "0.1.0" +dependencies = [ + "aggchain-proof-core", + "alloy-primitives", + "anyhow", + "hex", + "serde", + "thiserror 2.0.11", + "tiny-keccak 2.0.2 (git+https://github.com/sp1-patches/tiny-keccak?tag=patch-2.0.2-sp1-4.0.0)", +] + [[package]] name = "aggkit-prover" version = "0.1.0" dependencies = [ "aggchain-proof-service", + "aggchain-proof-types", "aggkit-prover-config", "aggkit-prover-types", "anyhow", @@ -151,11 +165,11 @@ dependencies = [ name = "aggkit-prover-types" version = "0.1.0" dependencies = [ - "aggchain-proof-core", - "anyhow", + "aggchain-proof-types", + "alloy-primitives", "bincode", + "hex", "prost", - "serde", "thiserror 2.0.11", "tonic", "tonic-build", @@ -5103,6 +5117,7 @@ dependencies = [ "alloy", "anyhow", "async-trait", + "ff 0.13.0", "mockall", "url", ] diff --git a/Cargo.toml b/Cargo.toml index 91f3584e..c0a3bd31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ aggchain-proof-builder = { path = "crates/aggchain-proof-builder" } aggchain-proof-contracts = { path = "crates/aggchain-proof-contracts" } aggchain-proof-core = { path = "crates/aggchain-proof-core" } aggchain-proof-service = { path = "crates/aggchain-proof-service" } +aggchain-proof-types = { path = "crates/aggchain-proof-types" } aggkit-prover = { path = "crates/aggkit-prover" } aggkit-prover-config = { path = "crates/aggkit-prover-config" } aggkit-prover-types = { path = "crates/aggkit-prover-types" } diff --git a/crates/aggchain-proof-builder/Cargo.toml b/crates/aggchain-proof-builder/Cargo.toml index ee17e18d..8b67f24f 100644 --- a/crates/aggchain-proof-builder/Cargo.toml +++ b/crates/aggchain-proof-builder/Cargo.toml @@ -15,7 +15,7 @@ url.workspace = true aggchain-proof-contracts.workspace = true aggchain-proof-core.workspace = true -aggkit-prover-types.workspace = true +aggchain-proof-types.workspace = true prover-alloy.workspace = true prover-config.workspace = true prover-executor.workspace = true diff --git a/crates/aggchain-proof-builder/src/lib.rs b/crates/aggchain-proof-builder/src/lib.rs index 3fc762d6..acdcf982 100644 --- a/crates/aggchain-proof-builder/src/lib.rs +++ b/crates/aggchain-proof-builder/src/lib.rs @@ -1,7 +1,6 @@ pub mod config; mod error; -use std::collections::HashMap; use std::sync::Arc; use std::task::{Context, Poll}; @@ -10,8 +9,7 @@ use aggchain_proof_contracts::contracts::{ }; use aggchain_proof_contracts::{AggchainContractsClient, AggchainContractsRpcClient}; use aggchain_proof_core::proof::AggchainProofWitness; -use aggkit_prover_types::v1::{InclusionProof, L1InfoTreeLeaf}; -use aggkit_prover_types::Hash; +use aggchain_proof_types::AggchainProofRequest; pub use error::Error; use futures::{future::BoxFuture, FutureExt}; use prover_alloy::AlloyFillProvider; @@ -44,22 +42,11 @@ pub struct AggchainProofBuilderRequest { /// Aggregated full execution proof for the number of aggregated block /// spans. pub agg_span_proof: SP1ProofWithPublicValues, - /// First block in the aggregated span. - pub start_block: u64, - /// Last block in the aggregated span (inclusive). + /// Last block in the agg_span_proof provided by the proposer. + /// Could be different from the max_end_block requested by the agg-sender. pub end_block: u64, - /// Root hash of the l1 info tree, containing all relevant GER. - /// Provided by agg-sender. - pub l1_info_tree_root_hash: Hash, - /// Particular leaf of the l1 info tree corresponding - /// to the max_block. - pub l1_info_tree_leaf: L1InfoTreeLeaf, - /// Inclusion proof of the l1 info tree leaf to the - /// l1 info tree root - pub l1_info_tree_merkle_proof: [Hash; 32], - /// Map of the Global Exit Roots with their inclusion proof. - /// Note: the GER (string) is a base64 encoded string of the GER digest. - pub ger_inclusion_proofs: HashMap, + /// Aggchain proof request information, public inputs, bridge data,... + pub aggchain_proof_request: AggchainProofRequest, } #[derive(Clone, Debug)] @@ -128,7 +115,7 @@ impl AggchainProofBuilder { L2LocalExitRootFetcher + L2OutputAtBlockFetcher + L1RollupConfigHashFetcher, { let _prev_local_exit_root = contracts_client - .get_l2_local_exit_root(request.start_block - 1) + .get_l2_local_exit_root(request.aggchain_proof_request.start_block - 1) .await .map_err(Error::L2ChainDataRetrievalError)?; @@ -138,7 +125,7 @@ impl AggchainProofBuilder { .map_err(Error::L2ChainDataRetrievalError)?; let _l2_pre_root_output_at_block = contracts_client - .get_l2_output_at_block(request.start_block - 1) + .get_l2_output_at_block(request.aggchain_proof_request.start_block - 1) .await .map_err(Error::L2ChainDataRetrievalError)?; diff --git a/crates/aggchain-proof-contracts/Cargo.toml b/crates/aggchain-proof-contracts/Cargo.toml index b51f94a9..707ee2d6 100644 --- a/crates/aggchain-proof-contracts/Cargo.toml +++ b/crates/aggchain-proof-contracts/Cargo.toml @@ -16,7 +16,7 @@ tokio.workspace = true tracing.workspace = true url.workspace = true -aggchain-proof-core.workspace = true +aggchain-proof-types.workspace = true prover-alloy.workspace = true prover-utils.workspace = true diff --git a/crates/aggchain-proof-contracts/src/contracts.rs b/crates/aggchain-proof-contracts/src/contracts.rs index 072fc522..95d2ff04 100644 --- a/crates/aggchain-proof-contracts/src/contracts.rs +++ b/crates/aggchain-proof-contracts/src/contracts.rs @@ -1,4 +1,4 @@ -use aggchain_proof_core::Digest; +use aggchain_proof_types::Digest; use alloy::network::Ethereum; use alloy::sol; diff --git a/crates/aggchain-proof-contracts/src/lib.rs b/crates/aggchain-proof-contracts/src/lib.rs index ecef7af0..ff54e17a 100644 --- a/crates/aggchain-proof-contracts/src/lib.rs +++ b/crates/aggchain-proof-contracts/src/lib.rs @@ -5,7 +5,7 @@ mod error; use std::str::FromStr; use std::sync::Arc; -use aggchain_proof_core::Digest; +use aggchain_proof_types::Digest; use alloy::primitives::B256; use jsonrpsee::core::client::ClientT; use jsonrpsee::http_client::HttpClient; diff --git a/crates/aggchain-proof-core/src/lib.rs b/crates/aggchain-proof-core/src/lib.rs index de1c8108..8c5ab14e 100644 --- a/crates/aggchain-proof-core/src/lib.rs +++ b/crates/aggchain-proof-core/src/lib.rs @@ -1,7 +1,7 @@ mod bridge; pub mod error; mod full_execution_proof; -mod keccak; +pub mod keccak; mod local_exit_tree; pub mod proof; diff --git a/crates/aggchain-proof-service/Cargo.toml b/crates/aggchain-proof-service/Cargo.toml index 27b39c97..3f64345f 100644 --- a/crates/aggchain-proof-service/Cargo.toml +++ b/crates/aggchain-proof-service/Cargo.toml @@ -9,6 +9,7 @@ workspace = true [dependencies] anyhow = { workspace = true } +alloy-primitives.workspace = true futures.workspace = true serde.workspace = true sp1-sdk = { workspace = true } @@ -17,7 +18,6 @@ tower = { workspace = true, features = ["timeout"] } tracing.workspace = true aggchain-proof-builder.workspace = true -aggchain-proof-core.workspace = true -aggkit-prover-types.workspace = true +aggchain-proof-types.workspace = true proposer-service.workspace = true prover-alloy.workspace = true diff --git a/crates/aggchain-proof-service/src/service.rs b/crates/aggchain-proof-service/src/service.rs index 687a4363..ef8e6298 100644 --- a/crates/aggchain-proof-service/src/service.rs +++ b/crates/aggchain-proof-service/src/service.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::{ future::Future, pin::Pin, @@ -7,8 +6,8 @@ use std::{ }; use aggchain_proof_builder::{AggchainProofBuilder, AggchainProofBuilderResponse}; -use aggkit_prover_types::v1::{InclusionProof, L1InfoTreeLeaf}; -use aggkit_prover_types::Hash; +use aggchain_proof_types::AggchainProofRequest; +use alloy_primitives::B256; use futures::{FutureExt as _, TryFutureExt}; use proposer_service::{ProposerRequest, ProposerService}; use sp1_sdk::SP1Proof; @@ -20,23 +19,9 @@ use crate::error::Error; /// A request for the AggchainProofService to generate the /// aggchain proof for the range of blocks. #[derive(Default, Clone, Debug)] -#[allow(unused)] pub struct AggchainProofServiceRequest { - /// Aggchain proof starting block - pub start_block: u64, - /// Max number of blocks that the aggchain proof is allowed to contain - pub max_block: u64, - /// Root hash of the L1 info tree. - pub l1_info_tree_root_hash: Hash, - /// Particular leaf of the l1 info tree corresponding - /// to the max_block. - pub l1_info_tree_leaf: L1InfoTreeLeaf, - /// Inclusion proof of the l1 info tree leaf to the - /// l1 info tree root. - pub l1_info_tree_merkle_proof: [Hash; 32], - /// Map of the Global Exit Roots with their inclusion proof. - /// Note: the GER (string) is a base64 encoded string of the GER digest. - pub ger_inclusion_proofs: HashMap, + /// Aggchain proof request information + pub aggchain_proof_request: AggchainProofRequest, } /// Resulting generated Aggchain proof @@ -129,12 +114,16 @@ impl tower::Service for AggchainProofService { } fn call(&mut self, req: AggchainProofServiceRequest) -> Self::Future { - let l1_block_number = req.max_block; + let l1_block_hash = req + .aggchain_proof_request + .l1_info_tree_leaf + .inner_leaf + .block_hash; let proposer_request = ProposerRequest { - start_block: req.start_block, - max_block: req.max_block, - l1_block_number, + start_block: req.aggchain_proof_request.start_block, + max_block: req.aggchain_proof_request.max_end_block, + l1_block_hash: B256::from(l1_block_hash.0), }; let mut proof_builder = self.aggchain_proof_builder.clone(); @@ -146,12 +135,8 @@ impl tower::Service for AggchainProofService { let aggchain_proof_builder_request = aggchain_proof_builder::AggchainProofBuilderRequest { agg_span_proof: agg_span_proof_response.agg_span_proof, - start_block: agg_span_proof_response.start_block, end_block: agg_span_proof_response.end_block, - l1_info_tree_merkle_proof: req.l1_info_tree_merkle_proof, - l1_info_tree_leaf: req.l1_info_tree_leaf, - l1_info_tree_root_hash: req.l1_info_tree_root_hash, - ger_inclusion_proofs: req.ger_inclusion_proofs, + aggchain_proof_request: req.aggchain_proof_request, }; proof_builder diff --git a/crates/aggchain-proof-types/Cargo.toml b/crates/aggchain-proof-types/Cargo.toml new file mode 100644 index 00000000..c094b9b4 --- /dev/null +++ b/crates/aggchain-proof-types/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "aggchain-proof-types" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +alloy-primitives.workspace = true +anyhow.workspace = true +hex.workspace = true +serde.workspace = true +thiserror.workspace = true +tiny-keccak = { git = "https://github.com/sp1-patches/tiny-keccak", tag = "patch-2.0.2-sp1-4.0.0", features = [ + "keccak", +] } + +aggchain-proof-core.workspace = true + +[lints] +workspace = true diff --git a/crates/aggchain-proof-types/src/lib.rs b/crates/aggchain-proof-types/src/lib.rs new file mode 100644 index 00000000..cb471a1b --- /dev/null +++ b/crates/aggchain-proof-types/src/lib.rs @@ -0,0 +1,12 @@ +mod proof; + +pub use aggchain_proof_core::keccak::{digest::Digest, keccak256_combine}; +pub use proof::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub enum NetworkIndex { + #[default] + Mainnet, + Rollup(u32), +} diff --git a/crates/aggchain-proof-types/src/proof.rs b/crates/aggchain-proof-types/src/proof.rs new file mode 100644 index 00000000..df2f8d56 --- /dev/null +++ b/crates/aggchain-proof-types/src/proof.rs @@ -0,0 +1,110 @@ +use std::collections::HashMap; + +use alloy_primitives::Address; +use serde::{Deserialize, Serialize}; + +use crate::Digest; +use crate::NetworkIndex; + +/// Inclusion proof for the L1 info tree. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct InclusionProof { + pub siblings: Vec, +} + +/// Claim from L1, used to prove the inclusion of the L1 info tree leaf. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct ClaimFromMainnet { + /// Proof from GER to Root + pub inclusion_proof: InclusionProof, + /// Related L1InfoTree leaf + pub l1_leaf: L1InfoTreeLeaf, +} + +/// Structure that represents a L1 info tree leaf, part of the +/// L1 info tree. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct L1InfoTreeLeaf { + pub l1_info_tree_index: u32, + pub rollup_exit_root: Digest, + pub mainnet_exit_root: Digest, + pub inner_leaf: L1InfoTreeLeafInner, +} + +/// Represents the inner part of the leaf in the L1InfoTree. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct L1InfoTreeLeafInner { + pub global_exit_root: Digest, + pub block_hash: Digest, + pub timestamp: u64, +} + +/// Represents a token bridge exit originating on another network but claimed on +/// the current network. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct ImportedBridgeExit { + /// The bridge exit initiated on another network, called the "sending" + /// network. Need to verify that the destination network matches the + /// current network, and that the bridge exit is included in an imported + /// LER. + pub bridge_exit: BridgeExit, + /// The global index of the imported bridge exit. + pub global_index: GlobalIndex, +} + +/// Represents a token bridge exit from the network. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct BridgeExit { + /// The type of the leaf. + pub leaf_type: i32, + /// Unique ID for the token being transferred. + pub token_info: TokenInfo, + /// Network which the token is transferred to. + pub destination_network: u32, + /// Address which will own the received token. + pub destination_address: Address, + /// Token amount sent. + pub amount: String, + /// Is metadata hashed. + pub is_metadata_hashed: bool, + /// Metadata for the bridge exit. + pub metadata: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct GlobalIndex { + pub network_index: NetworkIndex, + pub leaf_index: u32, +} + +/// Encapsulates the information to uniquely identify a token on the origin +/// network. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct TokenInfo { + /// Network which the token originates from. + pub origin_network: u32, + /// The address of the token on the origin network. + pub origin_token_address: Address, +} + +/// Data needed as the input for the aggchain proof generation. +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +pub struct AggchainProofRequest { + /// The start block for which the aggchain proof is requested. + pub start_block: u64, + /// The max end block for which the aggchain proof is requested. + pub max_end_block: u64, + /// Root hash of the L1 info tree. + pub l1_info_tree_root_hash: Digest, + /// Particular leaf of the l1 info tree corresponding + /// to the max_block. + pub l1_info_tree_leaf: L1InfoTreeLeaf, + /// Inclusion proof of the l1 info tree leaf to the + /// l1 info tree root. + pub l1_info_tree_merkle_proof: [Digest; 32], + /// Map of the Global Exit Roots with their inclusion proof. + /// Note: the GER (string) is a base64 encoded string of the GER digest. + pub ger_leaves: HashMap, + /// Imporeted bridge exits. + pub imported_bridge_exits: Vec, +} diff --git a/crates/aggkit-prover-types/Cargo.toml b/crates/aggkit-prover-types/Cargo.toml index a8850da7..d55cc636 100644 --- a/crates/aggkit-prover-types/Cargo.toml +++ b/crates/aggkit-prover-types/Cargo.toml @@ -5,11 +5,10 @@ edition.workspace = true license.workspace = true [dependencies] -anyhow.workspace = true -aggchain-proof-core.workspace = true +alloy-primitives.workspace = true bincode.workspace = true +hex.workspace = true prost = "0.13.4" -serde.workspace = true thiserror.workspace = true tonic = { workspace = true, default-features = false, features = [ "prost", @@ -17,6 +16,8 @@ tonic = { workspace = true, default-features = false, features = [ "transport", ] } +aggchain-proof-types.workspace = true + [build-dependencies] tonic-build = { version = "0.12", default-features = false, features = [ "prost", diff --git a/crates/aggkit-prover-types/src/conversion.rs b/crates/aggkit-prover-types/src/conversion.rs new file mode 100644 index 00000000..cb8d7eba --- /dev/null +++ b/crates/aggkit-prover-types/src/conversion.rs @@ -0,0 +1,221 @@ +use std::collections::HashMap; + +use alloy_primitives::Address; + +use crate::error::AggchainProofRequestError as Error; +use crate::v1; + +impl TryFrom for aggchain_proof_types::TokenInfo { + type Error = Error; + + fn try_from(value: v1::TokenInfo) -> Result { + Ok(aggchain_proof_types::TokenInfo { + origin_network: value.origin_network, + origin_token_address: Address::from_slice(&value.origin_token_address), + }) + } +} + +impl From for aggchain_proof_types::GlobalIndex { + fn from(value: v1::GlobalIndex) -> Self { + aggchain_proof_types::GlobalIndex { + network_index: if value.mainnet_flag { + aggchain_proof_types::NetworkIndex::Mainnet + } else { + aggchain_proof_types::NetworkIndex::Rollup(value.rollup_index) + }, + leaf_index: value.leaf_index, + } + } +} + +impl TryFrom for aggchain_proof_types::BridgeExit { + type Error = Error; + + fn try_from(value: v1::BridgeExit) -> Result { + Ok(aggchain_proof_types::BridgeExit { + leaf_type: value.leaf_type, + token_info: value + .token_info + .ok_or(Error::MissingTokenInfo { + field_path: "imported_bridge_exits.bridge_exit.token_info".to_string(), + })? + .try_into()?, + destination_network: value.destination_network, + destination_address: Address::from_slice(&value.destination_address), + amount: value.amount, + is_metadata_hashed: value.is_metadata_hashed, + metadata: value.metadata, + }) + } +} + +impl TryFrom for aggchain_proof_types::ImportedBridgeExit { + type Error = Error; + + fn try_from(value: v1::ImportedBridgeExit) -> Result { + Ok(aggchain_proof_types::ImportedBridgeExit { + bridge_exit: value + .bridge_exit + .ok_or(Error::MissingBridgeExit { + field_path: "imported_bridge_exits.bridge_exit".to_string(), + })? + .try_into()?, + global_index: value + .global_index + .ok_or(Error::MissingGlobalIndex { + field_path: "imported_bridge_exits.global_index".to_string(), + })? + .into(), + }) + } +} + +impl TryFrom for aggchain_proof_types::L1InfoTreeLeafInner { + type Error = Error; + + fn try_from(value: v1::L1InfoTreeLeafInner) -> Result { + Ok(aggchain_proof_types::L1InfoTreeLeafInner { + global_exit_root: value.global_exit_root.try_into().map_err(|error| { + Error::InvalidHexConversion { + field_path: "l1_info_tree_leaf.inner.global_exit_root".to_string(), + error, + } + })?, + block_hash: value.block_hash.try_into().map_err(|error| { + Error::InvalidHexConversion { + field_path: "l1_info_tree_leaf.inner.block_hash".to_string(), + error, + } + })?, + timestamp: value.timestamp, + }) + } +} + +impl TryFrom for aggchain_proof_types::L1InfoTreeLeaf { + type Error = Error; + + fn try_from(value: v1::L1InfoTreeLeaf) -> Result { + Ok(aggchain_proof_types::L1InfoTreeLeaf { + l1_info_tree_index: value.l1_info_tree_index, + rollup_exit_root: value.rer.try_into().map_err(|error| { + Error::InvalidHexConversion { + field_path: "l1_info_tree_leaf.rer".to_string(), + error, + } + })?, + mainnet_exit_root: value.mer.try_into().map_err(|error| { + Error::InvalidHexConversion { + field_path: "l1_info_tree_leaf.mer".to_string(), + error, + } + })?, + inner_leaf: value + .inner + .ok_or(Error::MissingL1InfoTreeLeafInner { + field_path: "l1_info_tree_leaf.inner".to_string(), + })? + .try_into()?, + }) + } +} + +impl TryFrom for aggchain_proof_types::InclusionProof { + type Error = Error; + + fn try_from(value: v1::InclusionProof) -> Result { + Ok(aggchain_proof_types::InclusionProof { + siblings: value + .siblings + .into_iter() + .map(|x| { + x.try_into().map_err(|error| Error::InvalidHexConversion { + field_path: "ger_leaves.claim_from_mainnet.inclusion_proof".to_string(), + error, + }) + }) + .collect::, Error>>()?, + }) + } +} + +impl TryFrom for aggchain_proof_types::ClaimFromMainnet { + type Error = Error; + + fn try_from(value: v1::ClaimFromMainnet) -> Result { + Ok(aggchain_proof_types::ClaimFromMainnet { + inclusion_proof: value + .inclusion_proof + .ok_or(Error::MissingInclusionProof { + field_path: "ger_leaves.claim_from_mainnet.inclusion_proof".to_string(), + })? + .try_into()?, + l1_leaf: value + .l1_leaf + .ok_or(Error::MissingL1InfoTreeLeaf { + field_path: "ger_leaves.claim_from_mainnet.l1_leaf".to_string(), + })? + .try_into()?, + }) + } +} + +impl TryFrom for aggchain_proof_types::AggchainProofRequest { + type Error = Error; + + fn try_from(value: v1::GenerateAggchainProofRequest) -> Result { + // Parse l1 info tree proof, of type [Digest; 32]. + let l1_info_tree_merkle_proof: [aggchain_proof_types::Digest; 32] = value + .l1_info_tree_merkle_proof + .into_iter() + .map(|x| { + x.try_into().map_err(|error| Error::InvalidHexConversion { + field_path: "l1_info_tree_merkle_proof".to_string(), + error, + }) + }) + .collect::, Error>>()? + .try_into() + .map_err(|_| Error::MissingL1InfoTreeMerkleProof { + field_path: "l1_info_tree_merkle_proof".to_string(), + })?; + + Ok(aggchain_proof_types::AggchainProofRequest { + start_block: value.start_block, + max_end_block: value.max_end_block, + l1_info_tree_root_hash: value.l1_info_tree_root_hash.try_into().map_err(|error| { + Error::InvalidHexConversion { + field_path: "l1_info_tree_root_hash".to_string(), + error, + } + })?, + l1_info_tree_leaf: value + .l1_info_tree_leaf + .ok_or(Error::MissingL1InfoTreeLeaf { + field_path: "l1_info_tree_leaf".to_string(), + })? + .try_into()?, + l1_info_tree_merkle_proof, + ger_leaves: value + .ger_leaves + .into_iter() + .map(|(k, v)| { + Ok(( + k, + v.try_into() + .map_err(|_| Error::InvalidClaimFromMainnetConversion { + field_path: "ger_leaves".to_string(), + })?, + )) + }) + .collect::, Error>>( + )?, + imported_bridge_exits: value + .imported_bridge_exits + .into_iter() + .map(|x| x.try_into()) + .collect::, Error>>()?, + }) + } +} diff --git a/crates/aggkit-prover-types/src/error.rs b/crates/aggkit-prover-types/src/error.rs new file mode 100644 index 00000000..f5c0124f --- /dev/null +++ b/crates/aggkit-prover-types/src/error.rs @@ -0,0 +1,53 @@ +use hex::FromHexError; + +/// Represents the errors that could happen with the grpc request +/// to generate the aggchain proof +#[derive(thiserror::Error, Debug)] +pub enum AggchainProofRequestError { + #[error("Missing bridge exit token info")] + MissingTokenInfo { field_path: String }, + + #[error("Missing request bridge exit")] + MissingBridgeExit { field_path: String }, + + #[error("Missing request global index")] + MissingGlobalIndex { field_path: String }, + + #[error("Missing inner l1 info tree leaf")] + MissingL1InfoTreeLeafInner { field_path: String }, + + #[error("Missing l1 info tree leaf")] + MissingL1InfoTreeLeaf { field_path: String }, + + #[error("Missing or invalid l1 info merkle tree proof")] + MissingL1InfoTreeMerkleProof { field_path: String }, + + #[error("Invalid claim from mainet value")] + InvalidClaimFromMainnetConversion { field_path: String }, + + #[error("Missing inclusion proof")] + MissingInclusionProof { field_path: String }, + + #[error("Invalid hex conversion")] + InvalidHexConversion { + field_path: String, + #[source] + error: FromHexError, + }, +} + +impl AggchainProofRequestError { + pub fn field_path(&self) -> &str { + match self { + AggchainProofRequestError::MissingTokenInfo { field_path } + | AggchainProofRequestError::MissingBridgeExit { field_path } + | AggchainProofRequestError::MissingGlobalIndex { field_path } + | AggchainProofRequestError::MissingL1InfoTreeLeafInner { field_path } + | AggchainProofRequestError::MissingL1InfoTreeLeaf { field_path } + | AggchainProofRequestError::MissingL1InfoTreeMerkleProof { field_path } + | AggchainProofRequestError::InvalidClaimFromMainnetConversion { field_path } + | AggchainProofRequestError::MissingInclusionProof { field_path } + | AggchainProofRequestError::InvalidHexConversion { field_path, .. } => field_path, + } + } +} diff --git a/crates/aggkit-prover-types/src/lib.rs b/crates/aggkit-prover-types/src/lib.rs index 61bd428e..7b525da7 100644 --- a/crates/aggkit-prover-types/src/lib.rs +++ b/crates/aggkit-prover-types/src/lib.rs @@ -9,6 +9,8 @@ pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("generated/aggkit.prover.b #[rustfmt::skip] #[allow(warnings)] pub mod v1; +pub mod conversion; +pub mod error; pub fn default_bincode_options( ) -> WithOtherIntEncoding, FixintEncoding> { diff --git a/crates/aggkit-prover/Cargo.toml b/crates/aggkit-prover/Cargo.toml index ddb7af32..98910151 100644 --- a/crates/aggkit-prover/Cargo.toml +++ b/crates/aggkit-prover/Cargo.toml @@ -19,6 +19,7 @@ tower = { workspace = true, features = ["timeout"] } tracing.workspace = true aggchain-proof-service.workspace = true +aggchain-proof-types.workspace = true aggkit-prover-config.workspace = true aggkit-prover-types.workspace = true prover-engine.workspace = true diff --git a/crates/aggkit-prover/src/rpc.rs b/crates/aggkit-prover/src/rpc.rs index f0475454..0061e3e7 100644 --- a/crates/aggkit-prover/src/rpc.rs +++ b/crates/aggkit-prover/src/rpc.rs @@ -1,10 +1,12 @@ use aggchain_proof_service::config::AggchainProofServiceConfig; use aggchain_proof_service::service::{AggchainProofService, AggchainProofServiceRequest}; +use aggchain_proof_types::AggchainProofRequest; +use aggkit_prover_types::default_bincode_options; +use aggkit_prover_types::error::AggchainProofRequestError; use aggkit_prover_types::v1::{ aggchain_proof_service_server::AggchainProofService as AggchainProofGrpcService, GenerateAggchainProofRequest, GenerateAggchainProofResponse, }; -use aggkit_prover_types::{default_bincode_options, Hash}; use bincode::Options; use tonic::{Request, Response, Status}; use tonic_types::{ErrorDetails, StatusExt}; @@ -53,25 +55,22 @@ impl AggchainProofGrpcService for GrpcService { )); } - let l1_info_tree_root_hash: Hash = - request.l1_info_tree_root_hash.try_into().map_err(|_| { - let mut error_details = ErrorDetails::new(); - error_details.add_bad_request_violation( - "l1_info_tree_root_hash", - "l1 info tree root hash must be non empty and 32 bytes long", - ); - Status::with_error_details( - tonic::Code::InvalidArgument, - "Invalid l1 info tree root hash", - error_details, - ) - })?; + let aggchain_proof_request: AggchainProofRequest = + request + .try_into() + .map_err(|error: AggchainProofRequestError| { + let field = error.field_path(); + let mut error_details = ErrorDetails::new(); + error_details.add_bad_request_violation(field, error.to_string()); + Status::with_error_details( + tonic::Code::InvalidArgument, + "Invalid aggchain proof request data", + error_details, + ) + })?; let proof_request = AggchainProofServiceRequest { - start_block: request.start_block, - max_block: request.max_end_block, - l1_info_tree_root_hash, - ..Default::default() + aggchain_proof_request, }; let mut service = self.service.clone(); diff --git a/crates/aggkit-prover/src/tests/mod.rs b/crates/aggkit-prover/src/tests/mod.rs index f1dd7d2c..ef336f19 100644 --- a/crates/aggkit-prover/src/tests/mod.rs +++ b/crates/aggkit-prover/src/tests/mod.rs @@ -24,11 +24,7 @@ async fn service_can_be_called() { let mut service = AggchainProofService::new(&AggchainProofServiceConfig::default()) .await .expect("create aggchain proof service"); - let request = AggchainProofServiceRequest { - start_block: 0, - max_block: 100, - ..Default::default() - }; + let request = AggchainProofServiceRequest::default(); let response = service.call(request).await; assert!(response.is_ok()); } diff --git a/crates/proposer-client/src/lib.rs b/crates/proposer-client/src/lib.rs index 93c0db3d..db87a8b0 100644 --- a/crates/proposer-client/src/lib.rs +++ b/crates/proposer-client/src/lib.rs @@ -88,7 +88,7 @@ where pub struct ProposerRequest { pub start_block: u64, pub max_block: u64, - pub l1_block_number: u64, + pub l1_block_hash: B256, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/proposer-client/src/rpc.rs b/crates/proposer-client/src/rpc.rs index 5b2fece0..65265291 100644 --- a/crates/proposer-client/src/rpc.rs +++ b/crates/proposer-client/src/rpc.rs @@ -9,7 +9,7 @@ use serde_with::serde_as; use serde_with::DisplayFromStr; use tracing::info; -use crate::{error::Error, ProposerRequest}; +use crate::error::Error; /// Proposer client that requests the generation /// of the AggSpanProof from the proposer and gets @@ -43,8 +43,8 @@ impl AggSpanProofProposer for ProposerRpcClient { request: AggSpanProofProposerRequest, ) -> Result { let params = rpc_params![ - request.start, - request.end, + request.start_block, + request.max_block, request.l1_block_number, request.l1_block_hash ]; @@ -70,25 +70,15 @@ impl AggSpanProofProposer for ProposerRpcClient { pub struct AggSpanProofProposerRequest { // Starting block number to request proof from #[serde(rename = "startBlock")] - pub start: u64, + pub start_block: u64, // Maximum block number on which the proof needs to be aggregated #[serde(rename = "maxBlock")] - pub end: u64, + pub max_block: u64, pub l1_block_number: u64, #[serde_as(as = "DisplayFromStr")] pub l1_block_hash: B256, } -impl From for ProposerRequest { - fn from(request: AggSpanProofProposerRequest) -> Self { - ProposerRequest { - start_block: request.start, - max_block: request.end, - l1_block_number: request.l1_block_number, - } - } -} - /// Response for the external proposer `request_span_proof` call #[derive(Serialize, Deserialize, Debug)] pub struct AggSpanProofProposerResponse { diff --git a/crates/proposer-client/src/tests/mod.rs b/crates/proposer-client/src/tests/mod.rs index 1946be9f..0392aac8 100644 --- a/crates/proposer-client/src/tests/mod.rs +++ b/crates/proposer-client/src/tests/mod.rs @@ -39,8 +39,8 @@ mod proposer_rpc { let service = ProposerRpcClient::new(&server.url()).unwrap(); let request = AggSpanProofProposerRequest { - start: 110, - end: 200, + start_block: 110, + max_block: 200, l1_block_number: 230203, l1_block_hash: [23; 32].into(), }; diff --git a/crates/proposer-service/Cargo.toml b/crates/proposer-service/Cargo.toml index d3437afe..28f27ea9 100644 --- a/crates/proposer-service/Cargo.toml +++ b/crates/proposer-service/Cargo.toml @@ -5,7 +5,8 @@ edition.workspace = true license.workspace = true [dependencies] -anyhow = { workspace = true } +alloy-primitives.workspace = true +anyhow.workspace = true futures.workspace = true serde.workspace = true sp1-sdk.workspace = true @@ -17,11 +18,10 @@ proposer-client.workspace = true prover-alloy.workspace = true [dev-dependencies] -alloy-primitives = { workspace = true } -mockall = { workspace = true } +mockall.workspace = true proposer-client = { workspace = true, features = ["testutils"] } prover-alloy = { workspace = true, features = ["testutils"] } -tokio = { workspace = true } +tokio.workspace = true [lints] workspace = true diff --git a/crates/proposer-service/src/lib.rs b/crates/proposer-service/src/lib.rs index a6b16b42..5a16778c 100644 --- a/crates/proposer-service/src/lib.rs +++ b/crates/proposer-service/src/lib.rs @@ -67,23 +67,23 @@ where ProposerRequest { start_block, max_block, - l1_block_number, + l1_block_hash, }: ProposerRequest, ) -> Self::Future { let client = self.client.clone(); let l1_rpc = self.l1_rpc.clone(); async move { - let l1_block_hash = l1_rpc - .get_block_hash(l1_block_number) + let l1_block_number = l1_rpc + .get_block_number(l1_block_hash) .await .map_err(Error::AlloyProviderError)?; // Request the AggSpanProof generation from the proposer. let response = client .request_agg_proof(AggSpanProofProposerRequest { - start: start_block, - end: max_block, + start_block, + max_block, l1_block_number, l1_block_hash, }) diff --git a/crates/proposer-service/src/tests/mod.rs b/crates/proposer-service/src/tests/mod.rs index 70eeda3f..a83657b3 100644 --- a/crates/proposer-service/src/tests/mod.rs +++ b/crates/proposer-service/src/tests/mod.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use alloy_primitives::{FixedBytes, B256}; +use alloy_primitives::FixedBytes; use proposer_client::{rpc::AggSpanProofProposerRequest, MockProposerClient, ProposerRequest}; use prover_alloy::MockProvider; use sp1_sdk::{Prover as _, SP1_CIRCUIT_VERSION}; @@ -15,9 +15,9 @@ const ELF: &[u8] = include_bytes!("../../../prover-dummy-program/elf/riscv32im-s async fn test_proposer_service() { let mut l1_rpc = MockProvider::new(); l1_rpc - .expect_get_block_hash() + .expect_get_block_number() .once() - .returning(|_| Box::pin(async { Ok(B256::ZERO) })); + .returning(|_| Box::pin(async { Ok(0) })); let mut client = MockProposerClient::new(); client @@ -27,8 +27,8 @@ async fn test_proposer_service() { Box::pin(async move { Ok(proposer_client::rpc::AggSpanProofProposerResponse { proof_id: FixedBytes::new([0; 32]), - start_block: request.start, - end_block: request.end, + start_block: request.start_block, + end_block: request.max_block, }) }) }); @@ -62,7 +62,7 @@ async fn test_proposer_service() { let request = ProposerRequest { start_block: 0, max_block: 10, - l1_block_number: 0, + l1_block_hash: Default::default(), }; let response = proposer_service.call(request).await.unwrap(); @@ -70,12 +70,12 @@ async fn test_proposer_service() { } #[tokio::test] -async fn unable_to_fetch_block_hash() { +async fn unable_to_fetch_block_number() { let mut l1_rpc = MockProvider::new(); l1_rpc - .expect_get_block_hash() + .expect_get_block_number() .once() - .returning(|_| Box::pin(async { anyhow::bail!("Failed to fetch block hash") })); + .returning(|_| Box::pin(async { anyhow::bail!("Failed to fetch block number") })); let client = MockProposerClient::new(); @@ -86,7 +86,7 @@ async fn unable_to_fetch_block_hash() { let request = ProposerRequest { start_block: 0, max_block: 10, - l1_block_number: 0, + l1_block_hash: Default::default(), }; let response = proposer_service.call(request).await; diff --git a/crates/prover-alloy/Cargo.toml b/crates/prover-alloy/Cargo.toml index bcbfcd46..5b149f8b 100644 --- a/crates/prover-alloy/Cargo.toml +++ b/crates/prover-alloy/Cargo.toml @@ -8,6 +8,7 @@ license.workspace = true alloy.workspace = true async-trait = { workspace = true } anyhow.workspace = true +ff = { version = "0.13", features = ["derive"] } url.workspace = true mockall = { workspace = true, optional = true } diff --git a/crates/prover-alloy/src/lib.rs b/crates/prover-alloy/src/lib.rs index 8def466b..39a301cd 100644 --- a/crates/prover-alloy/src/lib.rs +++ b/crates/prover-alloy/src/lib.rs @@ -8,6 +8,7 @@ use alloy::providers::fillers::{ }; use alloy::providers::Identity; use alloy::providers::{Provider as _, ProviderBuilder}; +// use alloy::signers::k256::elliptic_curve::ff::derive::bitvec::macros::internal::funty::Fundamental; use alloy::transports::http::reqwest; use alloy::transports::layers::RetryBackoffLayer; use alloy::{providers::RootProvider, rpc::client::ClientBuilder}; @@ -57,6 +58,11 @@ pub trait Provider { &self, block_number: u64, ) -> Result; + + async fn get_block_number( + &self, + block_hash: alloy::primitives::B256, + ) -> Result; } /// Wrapper around alloy `Provider` client. @@ -114,6 +120,21 @@ impl Provider for AlloyProvider { .hash; Ok(hash) } + + async fn get_block_number( + &self, + block_hash: alloy::primitives::B256, + ) -> Result { + let number = self + .client + .get_block_by_hash(block_hash, BlockTransactionsKind::Hashes) + .await + .map_err(|error| anyhow::anyhow!("Failed to get L1 block number: {:?}", error))? + .ok_or(anyhow::anyhow!("target block {block_hash} does not exist"))? + .header + .number; + Ok(number) + } } pub fn default_l1_node_url() -> Url {