From 788f5a3dbd513ad36b1afd3ecd44732b18f1b960 Mon Sep 17 00:00:00 2001 From: vibhurajeev Date: Wed, 9 Oct 2024 10:08:05 +0400 Subject: [PATCH 01/26] feat: implement gas relay mode --- Cargo.toml | 4 +- contracts | 2 +- .../lib/config/src/configs/da_client/avail.rs | 7 +- core/lib/config/src/testonly.rs | 3 + core/lib/env_config/src/da_client.rs | 18 +- core/lib/protobuf_config/src/da_client.rs | 58 ++++- .../src/proto/config/da_client.proto | 3 + core/node/da_clients/Cargo.toml | 3 + core/node/da_clients/src/avail/client.rs | 224 +++++++++++++++++- 9 files changed, 296 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60b5628f419..c983899372c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,6 +102,7 @@ categories = ["cryptography"] [workspace.dependencies] # "External" dependencies +alloy = { version = "0.1", features = ["serde", "sol-types"] } anyhow = "1" assert_matches = "1.5" async-trait = "0.1" @@ -111,6 +112,7 @@ backon = "0.4.4" bigdecimal = "0.4.5" bincode = "1" blake2 = "0.10" +bytes = "1" chrono = "0.4" clap = "4.2.2" codegen = "0.2.0" @@ -156,7 +158,7 @@ rayon = "1.3.1" regex = "1" reqwest = "0.12" rlp = "0.5" -rocksdb = "0.21.0" +rocksdb = "0.21" rustc_version = "0.4.0" rustls = "0.23" secp256k1 = { version = "0.27.0", features = ["recovery", "global-context"] } diff --git a/contracts b/contracts index aafee035db8..6b1f483f93b 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit aafee035db892689df3f7afe4b89fd6467a39313 +Subproject commit 6b1f483f93baab3b1d44e8f4efaece71377c2d45 diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index 590dc5fef18..5bbdf238fc2 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -3,11 +3,14 @@ use zksync_basic_types::seed_phrase::SeedPhrase; #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct AvailConfig { - pub api_node_url: String, + pub api_node_url: Option, pub bridge_api_url: String, - pub app_id: u32, + pub app_id: Option, pub timeout: usize, pub max_retries: usize, + pub gas_relay_mode: bool, + pub gas_relay_api_url: Option, + pub gas_relay_api_key: Option, } #[derive(Clone, Debug, PartialEq)] diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 960808aa6a6..c5e34b1ec80 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -950,6 +950,9 @@ impl Distribution for EncodeDist { app_id: self.sample(rng), timeout: self.sample(rng), max_retries: self.sample(rng), + gas_relay_mode: self.sample(rng), + gas_relay_api_url: self.sample(rng), + gas_relay_api_key: self.sample(rng), }) } } diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index 0fc3ad216f8..b1c85bdefe3 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -89,16 +89,24 @@ mod tests { fn expected_avail_da_layer_config( api_node_url: &str, bridge_api_url: &str, + seed: &str, app_id: u32, timeout: usize, max_retries: usize, + gas_relay_mode: bool, + gas_relay_api_url: &str, + gas_relay_api_key: &str, ) -> DAClientConfig { DAClientConfig::Avail(AvailConfig { - api_node_url: api_node_url.to_string(), + api_node_url: Some(api_node_url.to_string()), bridge_api_url: bridge_api_url.to_string(), - app_id, + seed: Some(seed.to_string()), + app_id: Some(app_id), timeout, max_retries, + gas_relay_mode, + gas_relay_api_url: Some(gas_relay_api_url.to_string()), + gas_relay_api_key: Some(gas_relay_api_key.to_string()), }) } @@ -112,6 +120,9 @@ mod tests { DA_APP_ID="1" DA_TIMEOUT="2" DA_MAX_RETRIES="3" + DA_GAS_RELAY_MODE="true" + DA_GAS_RELAY_API_URL="localhost:23456" + DA_GAS_RELAY_API_KEY="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" "#; lock.set_env(config); @@ -125,6 +136,9 @@ mod tests { "1".parse::().unwrap(), "2".parse::().unwrap(), "3".parse::().unwrap(), + true, + "localhost:23456", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ) ); } diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index 1499e88efb4..cf0413f5ccb 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -1,10 +1,8 @@ use anyhow::Context; -use zksync_config::{ - configs::{ - da_client::DAClientConfig::{Avail, ObjectStore}, - {self}, - }, - AvailConfig, +use zksync_config::configs::{ + da_client::avail::AvailConfig, + da_client::DAClientConfig::{Avail, ObjectStore}, + {self}, }; use zksync_protobuf::{required, ProtoRepr}; @@ -18,15 +16,48 @@ impl ProtoRepr for proto::DataAvailabilityClient { let client = match config { proto::data_availability_client::Config::Avail(conf) => Avail(AvailConfig { - api_node_url: required(&conf.api_node_url) - .context("api_node_url")? - .clone(), + api_node_url: if conf.gas_relay_mode.unwrap_or(false) { + None + } else { + Some( + required(&conf.api_node_url) + .context("api_node_url")? + .clone(), + ) + }, bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? .clone(), - app_id: *required(&conf.app_id).context("app_id")?, + app_id: if conf.gas_relay_mode.unwrap_or(false) { + None + } else { + Some(*required(&conf.app_id).context("app_id")?) + }, timeout: *required(&conf.timeout).context("timeout")? as usize, max_retries: *required(&conf.max_retries).context("max_retries")? as usize, + gas_relay_mode: conf + .gas_relay_mode + .context("gas_relay_mode") + .unwrap_or(false), + // if gas_relay_mode is true, then we need to set the gas_relay_api_url and gas_relay_api_key + gas_relay_api_url: if conf.gas_relay_mode.unwrap_or(false) { + Some( + required(&conf.gas_relay_api_url) + .context("gas_relay_api_url")? + .clone(), + ) + } else { + None + }, + gas_relay_api_key: if conf.gas_relay_mode.unwrap_or(false) { + Some( + required(&conf.gas_relay_api_key) + .context("gas_relay_api_key")? + .clone(), + ) + } else { + None + }, }), proto::data_availability_client::Config::ObjectStore(conf) => { ObjectStore(object_store_proto::ObjectStore::read(conf)?) @@ -41,11 +72,14 @@ impl ProtoRepr for proto::DataAvailabilityClient { Avail(config) => Self { config: Some(proto::data_availability_client::Config::Avail( proto::AvailConfig { - api_node_url: Some(config.api_node_url.clone()), + api_node_url: config.api_node_url.clone(), bridge_api_url: Some(config.bridge_api_url.clone()), - app_id: Some(config.app_id), + app_id: config.app_id, timeout: Some(config.timeout as u64), max_retries: Some(config.max_retries as u64), + gas_relay_mode: Some(config.gas_relay_mode), + gas_relay_api_url: config.gas_relay_api_url.clone(), + gas_relay_api_key: config.gas_relay_api_key.clone(), }, )), }, diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index d01bda2c847..d996df5e9af 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -10,6 +10,9 @@ message AvailConfig { optional uint32 app_id = 4; optional uint64 timeout = 5; optional uint64 max_retries = 6; + optional bool gas_relay_mode = 7; + optional string gas_relay_api_url = 8; + optional string gas_relay_api_key = 9; reserved 3; reserved "seed"; } diff --git a/core/node/da_clients/Cargo.toml b/core/node/da_clients/Cargo.toml index 60b65067f48..db469481102 100644 --- a/core/node/da_clients/Cargo.toml +++ b/core/node/da_clients/Cargo.toml @@ -37,3 +37,6 @@ blake2b_simd.workspace = true jsonrpsee = { workspace = true, features = ["ws-client"] } parity-scale-codec = { workspace = true, features = ["derive"] } subxt-signer = { workspace = true, features = ["sr25519", "native"] } +reqwest = { workspace = true } +bytes = { workspace = true } +alloy = { workspace = true } diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 7718691bf18..50bd1d62a65 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -1,7 +1,15 @@ use std::{fmt::Debug, sync::Arc}; +use alloy::{ + primitives::{B256, U256}, + sol, + sol_types::SolValue, +}; +use anyhow::anyhow; use async_trait::async_trait; +use bytes::Bytes; use jsonrpsee::ws_client::WsClientBuilder; +use serde::{Deserialize, Serialize}; use subxt_signer::ExposeSecret; use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets}; use zksync_da_client::{ @@ -15,19 +23,84 @@ use crate::avail::sdk::RawAvailClient; #[derive(Debug, Clone)] pub struct AvailClient { config: AvailConfig, - sdk_client: Arc, + sdk_client: Option>, + api_client: Arc, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct BridgeAPIResponse { + blob_root: Option, + bridge_root: Option, + data_root_index: Option, + data_root_proof: Option>, + leaf: Option, + leaf_index: Option, + leaf_proof: Option>, + range_hash: Option, + error: Option, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GasRelayAPISubmissionResponse { + submission_id: String, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GasRelayAPIStatusResponse { + submission: GasRelayAPISubmission, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GasRelayAPISubmission { + block_hash: Option, + extrinsic_index: Option, +} + +sol! { + #[derive(Deserialize, Serialize, Debug)] + struct MerkleProofInput { + // proof of inclusion for the data root + bytes32[] dataRootProof; + // proof of inclusion of leaf within blob/bridge root + bytes32[] leafProof; + // abi.encodePacked(startBlock, endBlock) of header range commitment on vectorx + bytes32 rangeHash; + // index of the data root in the commitment tree + uint256 dataRootIndex; + // blob root to check proof against, or reconstruct the data root + bytes32 blobRoot; + // bridge root to check proof against, or reconstruct the data root + bytes32 bridgeRoot; + // leaf being proven + bytes32 leaf; + // index of the leaf in the blob/bridge root tree + uint256 leafIndex; + } } impl AvailClient { pub async fn new(config: AvailConfig, secrets: AvailSecrets) -> anyhow::Result { + let api_client = reqwest::Client::new(); + if config.gas_relay_mode { + return Ok(Self { + config, + sdk_client: None, + api_client: Arc::new(api_client), + }); + } + let seed_phrase = secrets .seed_phrase .ok_or_else(|| anyhow::anyhow!("seed phrase"))?; - let sdk_client = RawAvailClient::new(config.app_id, seed_phrase.0.expose_secret()).await?; + // these unwraps are safe because we validate in protobuf config + let sdk_client = + RawAvailClient::new(config.app_id.unwrap(), seed_phrase.0.expose_secret()).await?; Ok(Self { config, - sdk_client: Arc::new(sdk_client), + sdk_client: Some(Arc::new(sdk_client)), + api_client: Arc::new(api_client), }) } } @@ -39,37 +112,165 @@ impl DataAvailabilityClient for AvailClient { _: u32, // batch_number data: Vec, ) -> anyhow::Result { + if self.config.gas_relay_mode { + let submit_url = format!( + "{}/user/submit_raw_data?token=ethereum", + self.config.gas_relay_api_url.clone().unwrap() + ); + // send the data to the gas relay + let submit_response = self + .api_client + .post(&submit_url) + .body(Bytes::from(data)) + .header("Content-Type", "text/plain") + .header( + "Authorization", + self.config.gas_relay_api_key.clone().unwrap(), + ) + .send() + .await + .map_err(to_retriable_da_error)?; + let submit_response_text = submit_response + .text() + .await + .map_err(to_retriable_da_error)?; + let submit_response_struct: GasRelayAPISubmissionResponse = + serde_json::from_str(&submit_response_text.clone()) + .map_err(to_retriable_da_error)?; + let status_url = format!( + "{}/user/get_submission_info?submission_id={}", + self.config.gas_relay_api_url.clone().unwrap(), + submit_response_struct.submission_id + ); + let mut retries = 0; + let mut status_response: reqwest::Response; + let mut status_response_text: String; + let mut status_response_struct: GasRelayAPIStatusResponse; + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(u64::try_from(40).unwrap())) + .await; // usually takes 20s to finalize + status_response = self + .api_client + .get(&status_url) + .header( + "Authorization", + self.config.gas_relay_api_key.clone().unwrap(), + ) + .send() + .await + .map_err(to_retriable_da_error)?; + status_response_text = status_response + .text() + .await + .map_err(to_retriable_da_error)?; + status_response_struct = + serde_json::from_str(&status_response_text).map_err(to_retriable_da_error)?; + if status_response_struct.submission.block_hash.is_some() { + break; + } + retries += 1; + if retries > self.config.max_retries { + return Err(to_retriable_da_error(anyhow!( + "Failed to get gas relay status" + ))); + } + } + return Ok(DispatchResponse { + blob_id: format!( + "{:x}:{}", + status_response_struct.submission.block_hash.unwrap(), + status_response_struct.submission.extrinsic_index.unwrap() + ), + }); + } let client = WsClientBuilder::default() - .build(self.config.api_node_url.as_str()) + .build(self.config.api_node_url.clone().unwrap().as_str()) .await .map_err(to_non_retriable_da_error)?; let extrinsic = self .sdk_client + .as_ref() + .unwrap() .build_extrinsic(&client, data) .await .map_err(to_non_retriable_da_error)?; let block_hash = self .sdk_client + .as_ref() + .unwrap() .submit_extrinsic(&client, extrinsic.as_str()) .await .map_err(to_non_retriable_da_error)?; let tx_id = self .sdk_client + .as_ref() + .unwrap() .get_tx_id(&client, block_hash.as_str(), extrinsic.as_str()) .await .map_err(to_non_retriable_da_error)?; - Ok(DispatchResponse::from(format!("{}:{}", block_hash, tx_id))) } async fn get_inclusion_data( &self, - _blob_id: &str, + blob_id: &str, ) -> anyhow::Result, DAError> { - // TODO: implement inclusion data retrieval - Ok(Some(InclusionData { data: vec![] })) + let (block_hash, tx_idx) = blob_id.split_once(':').ok_or_else(|| DAError { + error: anyhow!("Invalid blob ID format"), + is_retriable: false, + })?; + + let url = format!( + "{}/eth/proof/{}?index={}", + self.config.bridge_api_url, block_hash, tx_idx + ); + let mut response: reqwest::Response; + let mut retries = self.config.max_retries; + let mut response_text: String; + let mut bridge_api_data: BridgeAPIResponse; + loop { + response = self + .api_client + .get(&url) + .send() + .await + .map_err(to_retriable_da_error)?; + response_text = response.text().await.unwrap(); + + if let Ok(data) = serde_json::from_str::(&response_text) { + bridge_api_data = data; + if bridge_api_data.error.is_none() { + break; + } + } + + tokio::time::sleep(tokio::time::Duration::from_secs( + u64::try_from(480).unwrap(), + )) + .await; // usually takes 15 mins on Hex + retries += 1; + if retries > self.config.max_retries { + return Err(DAError { + error: anyhow!("Failed to get inclusion data"), + is_retriable: true, + }); + } + } + let attestation_data: MerkleProofInput = MerkleProofInput { + dataRootProof: bridge_api_data.data_root_proof.unwrap(), + leafProof: bridge_api_data.leaf_proof.unwrap(), + rangeHash: bridge_api_data.range_hash.unwrap(), + dataRootIndex: bridge_api_data.data_root_index.unwrap(), + blobRoot: bridge_api_data.blob_root.unwrap(), + bridgeRoot: bridge_api_data.bridge_root.unwrap(), + leaf: bridge_api_data.leaf.unwrap(), + leafIndex: bridge_api_data.leaf_index.unwrap(), + }; + Ok(Some(InclusionData { + data: attestation_data.abi_encode(), + })) } fn clone_boxed(&self) -> Box { @@ -87,3 +288,10 @@ pub fn to_non_retriable_da_error(error: impl Into) -> DAError { is_retriable: false, } } + +pub fn to_retriable_da_error(error: impl Into) -> DAError { + DAError { + error: error.into(), + is_retriable: true, + } +} From feec5724d5f62e8a79101d1afc32ef4d0fa54df4 Mon Sep 17 00:00:00 2001 From: vibhurajeev Date: Wed, 9 Oct 2024 17:48:43 +0400 Subject: [PATCH 02/26] fix(env_config): merge conflicts with da client resolved --- core/lib/env_config/src/da_client.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index b1c85bdefe3..94da120ec08 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -89,7 +89,6 @@ mod tests { fn expected_avail_da_layer_config( api_node_url: &str, bridge_api_url: &str, - seed: &str, app_id: u32, timeout: usize, max_retries: usize, @@ -100,7 +99,6 @@ mod tests { DAClientConfig::Avail(AvailConfig { api_node_url: Some(api_node_url.to_string()), bridge_api_url: bridge_api_url.to_string(), - seed: Some(seed.to_string()), app_id: Some(app_id), timeout, max_retries, From fc9f7d4439d6b40237d9944c287bd738fa472360 Mon Sep 17 00:00:00 2001 From: vibhurajeev Date: Wed, 9 Oct 2024 18:37:38 +0400 Subject: [PATCH 03/26] fix(protobuf_config/da_client): minor cleanup --- core/lib/protobuf_config/src/da_client.rs | 87 ++++++++++++----------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index cf0413f5ccb..c46ecc95a5c 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -15,50 +15,55 @@ impl ProtoRepr for proto::DataAvailabilityClient { let config = required(&self.config).context("config")?; let client = match config { - proto::data_availability_client::Config::Avail(conf) => Avail(AvailConfig { - api_node_url: if conf.gas_relay_mode.unwrap_or(false) { - None - } else { - Some( - required(&conf.api_node_url) - .context("api_node_url")? - .clone(), - ) - }, - bridge_api_url: required(&conf.bridge_api_url) - .context("bridge_api_url")? - .clone(), - app_id: if conf.gas_relay_mode.unwrap_or(false) { - None - } else { - Some(*required(&conf.app_id).context("app_id")?) - }, - timeout: *required(&conf.timeout).context("timeout")? as usize, - max_retries: *required(&conf.max_retries).context("max_retries")? as usize, - gas_relay_mode: conf - .gas_relay_mode - .context("gas_relay_mode") - .unwrap_or(false), - // if gas_relay_mode is true, then we need to set the gas_relay_api_url and gas_relay_api_key - gas_relay_api_url: if conf.gas_relay_mode.unwrap_or(false) { - Some( - required(&conf.gas_relay_api_url) - .context("gas_relay_api_url")? + proto::data_availability_client::Config::Avail(conf) => { + Avail(if conf.gas_relay_mode.unwrap_or(false) { + AvailConfig { + api_node_url: None, + bridge_api_url: required(&conf.bridge_api_url) + .context("bridge_api_url")? .clone(), - ) + app_id: None, + timeout: *required(&conf.timeout).context("timeout")? as usize, + max_retries: *required(&conf.max_retries).context("max_retries")? as usize, + gas_relay_mode: conf + .gas_relay_mode + .context("gas_relay_mode") + .unwrap_or(false), + // if gas_relay_mode is true, then we need to set the gas_relay_api_url and gas_relay_api_key + gas_relay_api_url: Some( + required(&conf.gas_relay_api_url) + .context("gas_relay_api_url")? + .clone(), + ), + gas_relay_api_key: Some( + required(&conf.gas_relay_api_key) + .context("gas_relay_api_key")? + .clone(), + ), + } } else { - None - }, - gas_relay_api_key: if conf.gas_relay_mode.unwrap_or(false) { - Some( - required(&conf.gas_relay_api_key) - .context("gas_relay_api_key")? + AvailConfig { + api_node_url: Some( + required(&conf.api_node_url) + .context("api_node_url")? + .clone(), + ), + bridge_api_url: required(&conf.bridge_api_url) + .context("bridge_api_url")? .clone(), - ) - } else { - None - }, - }), + app_id: Some(*required(&conf.app_id).context("app_id")?), + timeout: *required(&conf.timeout).context("timeout")? as usize, + max_retries: *required(&conf.max_retries).context("max_retries")? as usize, + gas_relay_mode: conf + .gas_relay_mode + .context("gas_relay_mode") + .unwrap_or(false), + // if gas_relay_mode is not true, then the gas_relay_api_url and gas_relay_api_key are not required + gas_relay_api_url: None, + gas_relay_api_key: None, + } + }) + } proto::data_availability_client::Config::ObjectStore(conf) => { ObjectStore(object_store_proto::ObjectStore::read(conf)?) } From b1cc9b1fa65fd8c8a4bb4969de2b6fa056264d2a Mon Sep 17 00:00:00 2001 From: vibhurajeev Date: Wed, 9 Oct 2024 19:35:02 +0400 Subject: [PATCH 04/26] fix(config/da_client): moved gas relay api key to secrets --- .../lib/config/src/configs/da_client/avail.rs | 23 +++++++++++++++- core/lib/config/src/testonly.rs | 6 +++-- core/lib/env_config/src/da_client.rs | 13 +++++---- core/lib/protobuf_config/src/da_client.rs | 9 +------ .../src/proto/config/da_client.proto | 1 - .../src/proto/config/secrets.proto | 1 + core/lib/protobuf_config/src/secrets.rs | 27 +++++++++++++++++-- core/node/da_clients/src/avail/client.rs | 20 +++++++++++--- 8 files changed, 78 insertions(+), 22 deletions(-) diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index 5bbdf238fc2..ed3e0089a49 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -1,6 +1,27 @@ +use std::str::FromStr; + +use secrecy::ExposeSecret as _; +use secrecy::Secret; use serde::Deserialize; use zksync_basic_types::seed_phrase::SeedPhrase; +#[derive(Clone, Debug)] +pub struct GasRelayAPIKey(pub Secret); + +impl PartialEq for GasRelayAPIKey { + fn eq(&self, other: &Self) -> bool { + self.0.expose_secret().eq(other.0.expose_secret()) + } +} + +impl FromStr for GasRelayAPIKey { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(GasRelayAPIKey(s.parse()?)) + } +} + #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct AvailConfig { pub api_node_url: Option, @@ -10,10 +31,10 @@ pub struct AvailConfig { pub max_retries: usize, pub gas_relay_mode: bool, pub gas_relay_api_url: Option, - pub gas_relay_api_key: Option, } #[derive(Clone, Debug, PartialEq)] pub struct AvailSecrets { pub seed_phrase: Option, + pub gas_relay_api_key: Option, } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index c5e34b1ec80..a34ab9b45ee 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -16,7 +16,9 @@ use zksync_crypto_primitives::K256PrivateKey; use crate::{ configs::{ - self, da_client::DAClientConfig::Avail, eth_sender::PubdataSendingMode, + self, + da_client::{avail::GasRelayAPIKey, DAClientConfig::Avail}, + eth_sender::PubdataSendingMode, external_price_api_client::ForcedPriceClientConfig, }, AvailConfig, @@ -952,7 +954,6 @@ impl Distribution for EncodeDist { max_retries: self.sample(rng), gas_relay_mode: self.sample(rng), gas_relay_api_url: self.sample(rng), - gas_relay_api_key: self.sample(rng), }) } } @@ -961,6 +962,7 @@ impl Distribution for EncodeDist { fn sample(&self, rng: &mut R) -> configs::secrets::DataAvailabilitySecrets { configs::secrets::DataAvailabilitySecrets::Avail(configs::da_client::avail::AvailSecrets { seed_phrase: Some(SeedPhrase(Secret::new(self.sample(rng)))), + gas_relay_api_key: Some(GasRelayAPIKey(Secret::new(self.sample(rng)))), }) } } diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index 94da120ec08..353828de834 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -34,7 +34,14 @@ impl FromEnv for DataAvailabilitySecrets { .ok() .map(|s| s.parse()) .transpose()?; - Self::Avail(AvailSecrets { seed_phrase }) + let gas_relay_api_key = env::var("DA_GAS_RELAY_API_KEY") + .ok() + .map(|s| s.parse()) + .transpose()?; + Self::Avail(AvailSecrets { + seed_phrase, + gas_relay_api_key, + }) } _ => anyhow::bail!("Unknown DA client name: {}", client_tag), }; @@ -94,7 +101,6 @@ mod tests { max_retries: usize, gas_relay_mode: bool, gas_relay_api_url: &str, - gas_relay_api_key: &str, ) -> DAClientConfig { DAClientConfig::Avail(AvailConfig { api_node_url: Some(api_node_url.to_string()), @@ -104,7 +110,6 @@ mod tests { max_retries, gas_relay_mode, gas_relay_api_url: Some(gas_relay_api_url.to_string()), - gas_relay_api_key: Some(gas_relay_api_key.to_string()), }) } @@ -120,7 +125,6 @@ mod tests { DA_MAX_RETRIES="3" DA_GAS_RELAY_MODE="true" DA_GAS_RELAY_API_URL="localhost:23456" - DA_GAS_RELAY_API_KEY="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" "#; lock.set_env(config); @@ -136,7 +140,6 @@ mod tests { "3".parse::().unwrap(), true, "localhost:23456", - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" ) ); } diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index c46ecc95a5c..2d3b1195106 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -35,11 +35,6 @@ impl ProtoRepr for proto::DataAvailabilityClient { .context("gas_relay_api_url")? .clone(), ), - gas_relay_api_key: Some( - required(&conf.gas_relay_api_key) - .context("gas_relay_api_key")? - .clone(), - ), } } else { AvailConfig { @@ -58,9 +53,8 @@ impl ProtoRepr for proto::DataAvailabilityClient { .gas_relay_mode .context("gas_relay_mode") .unwrap_or(false), - // if gas_relay_mode is not true, then the gas_relay_api_url and gas_relay_api_key are not required + // if gas_relay_mode is not true, then the gas_relay_api_url is not required gas_relay_api_url: None, - gas_relay_api_key: None, } }) } @@ -84,7 +78,6 @@ impl ProtoRepr for proto::DataAvailabilityClient { max_retries: Some(config.max_retries as u64), gas_relay_mode: Some(config.gas_relay_mode), gas_relay_api_url: config.gas_relay_api_url.clone(), - gas_relay_api_key: config.gas_relay_api_key.clone(), }, )), }, diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index d996df5e9af..4d106f34d76 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -12,7 +12,6 @@ message AvailConfig { optional uint64 max_retries = 6; optional bool gas_relay_mode = 7; optional string gas_relay_api_url = 8; - optional string gas_relay_api_key = 9; reserved 3; reserved "seed"; } diff --git a/core/lib/protobuf_config/src/proto/config/secrets.proto b/core/lib/protobuf_config/src/proto/config/secrets.proto index 17b915b3f08..43c4542783c 100644 --- a/core/lib/protobuf_config/src/proto/config/secrets.proto +++ b/core/lib/protobuf_config/src/proto/config/secrets.proto @@ -21,6 +21,7 @@ message ConsensusSecrets { message AvailSecret { optional string seed_phrase = 1; + optional string gas_relay_api_key = 2; } message DataAvailabilitySecrets { diff --git a/core/lib/protobuf_config/src/secrets.rs b/core/lib/protobuf_config/src/secrets.rs index 58735148007..40035f752c9 100644 --- a/core/lib/protobuf_config/src/secrets.rs +++ b/core/lib/protobuf_config/src/secrets.rs @@ -5,7 +5,7 @@ use secrecy::ExposeSecret; use zksync_basic_types::{seed_phrase::SeedPhrase, url::SensitiveUrl}; use zksync_config::configs::{ consensus::{AttesterSecretKey, ConsensusSecrets, NodeSecretKey, ValidatorSecretKey}, - da_client::avail::AvailSecrets, + da_client::avail::{AvailSecrets, GasRelayAPIKey}, secrets::{DataAvailabilitySecrets, Secrets}, DatabaseSecrets, L1Secrets, }; @@ -110,6 +110,12 @@ impl ProtoRepr for proto::DataAvailabilitySecrets { ) .unwrap(), ), + gas_relay_api_key: Some( + GasRelayAPIKey::from_str( + required(&avail_secret.gas_relay_api_key).context("seed_phrase")?, + ) + .unwrap(), + ), }), }; @@ -133,7 +139,24 @@ impl ProtoRepr for proto::DataAvailabilitySecrets { None }; - Some(DaSecrets::Avail(AvailSecret { seed_phrase })) + let gas_relay_api_key = if config.gas_relay_api_key.is_some() { + Some( + config + .clone() + .gas_relay_api_key + .unwrap() + .0 + .expose_secret() + .to_string(), + ) + } else { + None + }; + + Some(DaSecrets::Avail(AvailSecret { + seed_phrase, + gas_relay_api_key, + })) } }; diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 50bd1d62a65..91dd26fbbb3 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -11,7 +11,7 @@ use bytes::Bytes; use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; use subxt_signer::ExposeSecret; -use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets}; +use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets, GasRelayAPIKey}; use zksync_da_client::{ types::{DAError, DispatchResponse, InclusionData}, DataAvailabilityClient, @@ -25,6 +25,7 @@ pub struct AvailClient { config: AvailConfig, sdk_client: Option>, api_client: Arc, + gas_relay_api_key: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -87,6 +88,7 @@ impl AvailClient { config, sdk_client: None, api_client: Arc::new(api_client), + gas_relay_api_key: secrets.gas_relay_api_key, }); } @@ -101,6 +103,7 @@ impl AvailClient { config, sdk_client: Some(Arc::new(sdk_client)), api_client: Arc::new(api_client), + gas_relay_api_key: None, }) } } @@ -125,7 +128,12 @@ impl DataAvailabilityClient for AvailClient { .header("Content-Type", "text/plain") .header( "Authorization", - self.config.gas_relay_api_key.clone().unwrap(), + self.gas_relay_api_key + .as_ref() + .expect("No gas relay api key") + .0 + .expose_secret() + .clone(), ) .send() .await @@ -154,7 +162,13 @@ impl DataAvailabilityClient for AvailClient { .get(&status_url) .header( "Authorization", - self.config.gas_relay_api_key.clone().unwrap(), + &self + .gas_relay_api_key + .as_ref() + .expect("No gas relay api key") + .0 + .expose_secret() + .clone(), ) .send() .await From 2c1646045a445082a76dc5e0075a396abf847064 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Fri, 11 Oct 2024 03:30:06 +0530 Subject: [PATCH 05/26] fix(protobuf_config): remove redundant gas_relay_mode parse --- .gitignore | 2 +- core/lib/protobuf_config/src/da_client.rs | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 86ed40c7041..c898f1fdfa8 100644 --- a/.gitignore +++ b/.gitignore @@ -114,7 +114,7 @@ prover/data/keys/setup_* # ZK Stack CLI chains/era/configs/* -chains/gateway/* +chains/avail/* configs/* era-observability/ core/tests/ts-integration/deployments-zk diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index 2d3b1195106..e4fa511ed10 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -25,11 +25,7 @@ impl ProtoRepr for proto::DataAvailabilityClient { app_id: None, timeout: *required(&conf.timeout).context("timeout")? as usize, max_retries: *required(&conf.max_retries).context("max_retries")? as usize, - gas_relay_mode: conf - .gas_relay_mode - .context("gas_relay_mode") - .unwrap_or(false), - // if gas_relay_mode is true, then we need to set the gas_relay_api_url and gas_relay_api_key + gas_relay_mode: true, gas_relay_api_url: Some( required(&conf.gas_relay_api_url) .context("gas_relay_api_url")? @@ -49,11 +45,7 @@ impl ProtoRepr for proto::DataAvailabilityClient { app_id: Some(*required(&conf.app_id).context("app_id")?), timeout: *required(&conf.timeout).context("timeout")? as usize, max_retries: *required(&conf.max_retries).context("max_retries")? as usize, - gas_relay_mode: conf - .gas_relay_mode - .context("gas_relay_mode") - .unwrap_or(false), - // if gas_relay_mode is not true, then the gas_relay_api_url is not required + gas_relay_mode: false, gas_relay_api_url: None, } }) From f235786e4f5c80deaa69a46deec2c97854c7cb04 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Fri, 11 Oct 2024 03:49:55 +0530 Subject: [PATCH 06/26] feat(avail): implement tokenize for merkleproofinput --- core/node/da_clients/src/avail/client.rs | 106 ++++++++++++++--------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 91dd26fbbb3..dd03e5871d0 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -1,21 +1,20 @@ -use std::{fmt::Debug, sync::Arc}; - -use alloy::{ - primitives::{B256, U256}, - sol, - sol_types::SolValue, -}; use anyhow::anyhow; use async_trait::async_trait; use bytes::Bytes; use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, sync::Arc}; use subxt_signer::ExposeSecret; use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets, GasRelayAPIKey}; use zksync_da_client::{ types::{DAError, DispatchResponse, InclusionData}, DataAvailabilityClient, }; +use zksync_types::{ + ethabi::{self, Token}, + web3::contract::{Tokenizable, Tokenize}, + H256, U256, +}; use crate::avail::sdk::RawAvailClient; @@ -31,14 +30,14 @@ pub struct AvailClient { #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct BridgeAPIResponse { - blob_root: Option, - bridge_root: Option, + blob_root: Option, + bridge_root: Option, data_root_index: Option, - data_root_proof: Option>, - leaf: Option, + data_root_proof: Option>, + leaf: Option, leaf_index: Option, - leaf_proof: Option>, - range_hash: Option, + leaf_proof: Option>, + range_hash: Option, error: Option, } @@ -54,29 +53,53 @@ pub struct GasRelayAPIStatusResponse { #[derive(Deserialize, Serialize, Debug, Clone)] pub struct GasRelayAPISubmission { - block_hash: Option, + block_hash: Option, extrinsic_index: Option, } -sol! { - #[derive(Deserialize, Serialize, Debug)] - struct MerkleProofInput { - // proof of inclusion for the data root - bytes32[] dataRootProof; - // proof of inclusion of leaf within blob/bridge root - bytes32[] leafProof; - // abi.encodePacked(startBlock, endBlock) of header range commitment on vectorx - bytes32 rangeHash; - // index of the data root in the commitment tree - uint256 dataRootIndex; - // blob root to check proof against, or reconstruct the data root - bytes32 blobRoot; - // bridge root to check proof against, or reconstruct the data root - bytes32 bridgeRoot; - // leaf being proven - bytes32 leaf; - // index of the leaf in the blob/bridge root tree - uint256 leafIndex; +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct MerkleProofInput { + // proof of inclusion for the data root + data_root_proof: Vec, + // proof of inclusion of leaf within blob/bridge root + leaf_proof: Vec, + // abi.encodePacked(startBlock, endBlock) of header range commitment on vectorx + range_hash: H256, + // index of the data root in the commitment tree + data_root_index: U256, + // blob root to check proof against, or reconstruct the data root + blob_root: H256, + // bridge root to check proof against, or reconstruct the data root + bridge_root: H256, + // leaf being proven + leaf: H256, + // index of the leaf in the blob/bridge root tree + leaf_index: U256, +} + +impl Tokenize for MerkleProofInput { + fn into_tokens(self) -> Vec { + vec![Token::Tuple(vec![ + Token::Array( + self.data_root_proof + .iter() + .map(|x| Token::FixedBytes(x.as_bytes().to_vec())) + .collect(), + ), + Token::Array( + self.leaf_proof + .iter() + .map(|x| Token::FixedBytes(x.as_bytes().to_vec())) + .collect(), + ), + Token::FixedBytes(self.range_hash.as_bytes().to_vec()), + Token::Uint(self.data_root_index), + Token::FixedBytes(self.blob_root.as_bytes().to_vec()), + Token::FixedBytes(self.bridge_root.as_bytes().to_vec()), + Token::FixedBytes(self.leaf.as_bytes().to_vec()), + Token::Uint(self.leaf_index), + ])] } } @@ -273,17 +296,18 @@ impl DataAvailabilityClient for AvailClient { } } let attestation_data: MerkleProofInput = MerkleProofInput { - dataRootProof: bridge_api_data.data_root_proof.unwrap(), - leafProof: bridge_api_data.leaf_proof.unwrap(), - rangeHash: bridge_api_data.range_hash.unwrap(), - dataRootIndex: bridge_api_data.data_root_index.unwrap(), - blobRoot: bridge_api_data.blob_root.unwrap(), - bridgeRoot: bridge_api_data.bridge_root.unwrap(), + data_root_proof: bridge_api_data.data_root_proof.unwrap(), + leaf_proof: bridge_api_data.leaf_proof.unwrap(), + range_hash: bridge_api_data.range_hash.unwrap(), + data_root_index: bridge_api_data.data_root_index.unwrap(), + blob_root: bridge_api_data.blob_root.unwrap(), + bridge_root: bridge_api_data.bridge_root.unwrap(), leaf: bridge_api_data.leaf.unwrap(), - leafIndex: bridge_api_data.leaf_index.unwrap(), + leaf_index: bridge_api_data.leaf_index.unwrap(), }; Ok(Some(InclusionData { - data: attestation_data.abi_encode(), + // convert vec into vec + data: ethabi::encode(&attestation_data.into_tokens()), })) } From aab3e78c246057b4db3734fdf65a818556a42c41 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Fri, 11 Oct 2024 04:04:23 +0530 Subject: [PATCH 07/26] chore(deps): remove alloy dep --- Cargo.toml | 1 - core/node/da_clients/Cargo.toml | 1 - core/node/da_clients/src/avail/client.rs | 8 +++----- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c983899372c..22370ec61c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,6 @@ categories = ["cryptography"] [workspace.dependencies] # "External" dependencies -alloy = { version = "0.1", features = ["serde", "sol-types"] } anyhow = "1" assert_matches = "1.5" async-trait = "0.1" diff --git a/core/node/da_clients/Cargo.toml b/core/node/da_clients/Cargo.toml index db469481102..c2036371436 100644 --- a/core/node/da_clients/Cargo.toml +++ b/core/node/da_clients/Cargo.toml @@ -39,4 +39,3 @@ parity-scale-codec = { workspace = true, features = ["derive"] } subxt-signer = { workspace = true, features = ["sr25519", "native"] } reqwest = { workspace = true } bytes = { workspace = true } -alloy = { workspace = true } diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index dd03e5871d0..bcb55c4d027 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -289,10 +289,9 @@ impl DataAvailabilityClient for AvailClient { .await; // usually takes 15 mins on Hex retries += 1; if retries > self.config.max_retries { - return Err(DAError { - error: anyhow!("Failed to get inclusion data"), - is_retriable: true, - }); + return Err(to_retriable_da_error(anyhow!( + "Failed to get inclusion data" + ))); } } let attestation_data: MerkleProofInput = MerkleProofInput { @@ -306,7 +305,6 @@ impl DataAvailabilityClient for AvailClient { leaf_index: bridge_api_data.leaf_index.unwrap(), }; Ok(Some(InclusionData { - // convert vec into vec data: ethabi::encode(&attestation_data.into_tokens()), })) } From c6434c90e7815ce40f6d540cafb166f97d026357 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Fri, 11 Oct 2024 04:22:02 +0530 Subject: [PATCH 08/26] fix(.gitignore): readd removed folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c898f1fdfa8..adf3b779961 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,7 @@ prover/data/keys/setup_* # ZK Stack CLI chains/era/configs/* +chains/gateway/* chains/avail/* configs/* era-observability/ From dec3b950f87454a6788a979e9652488971de8110 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Mon, 14 Oct 2024 04:05:19 +0530 Subject: [PATCH 09/26] fix(protobuf-secrets,avail-client): make da secrets one-of, remove loop from inclusion data logic --- core/lib/protobuf_config/src/secrets.rs | 42 +++++++++++++++------ core/node/da_clients/src/avail/client.rs | 47 +++++++++--------------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/core/lib/protobuf_config/src/secrets.rs b/core/lib/protobuf_config/src/secrets.rs index 40035f752c9..0dbc68a3746 100644 --- a/core/lib/protobuf_config/src/secrets.rs +++ b/core/lib/protobuf_config/src/secrets.rs @@ -10,6 +10,7 @@ use zksync_config::configs::{ DatabaseSecrets, L1Secrets, }; use zksync_protobuf::{required, ProtoRepr}; +use zksync_types::seed_phrase; use crate::{ proto::{ @@ -103,20 +104,37 @@ impl ProtoRepr for proto::DataAvailabilitySecrets { let secrets = required(&self.da_secrets).context("config")?; let client = match secrets { - DaSecrets::Avail(avail_secret) => DataAvailabilitySecrets::Avail(AvailSecrets { - seed_phrase: Some( - SeedPhrase::from_str( - required(&avail_secret.seed_phrase).context("seed_phrase")?, + DaSecrets::Avail(avail_secret) => { + let seed_phrase = if avail_secret.seed_phrase.is_some() { + Some( + SeedPhrase::from_str( + required(&avail_secret.seed_phrase).context("seed_phrase")?, + ) + .unwrap(), ) - .unwrap(), - ), - gas_relay_api_key: Some( - GasRelayAPIKey::from_str( - required(&avail_secret.gas_relay_api_key).context("seed_phrase")?, + } else { + None + }; + let gas_relay_api_key = if avail_secret.gas_relay_api_key.is_some() { + Some( + GasRelayAPIKey::from_str( + required(&avail_secret.gas_relay_api_key).context("seed_phrase")?, + ) + .unwrap(), ) - .unwrap(), - ), - }), + } else { + None + }; + if seed_phrase.is_none() && gas_relay_api_key.is_none() { + return Err(anyhow::anyhow!( + "At least one of seed_phrase or gas_relay_api_key must be provided" + )); + } + DataAvailabilitySecrets::Avail(AvailSecrets { + seed_phrase, + gas_relay_api_key, + }) + } }; Ok(client) diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index bcb55c4d027..19f889fec7a 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -12,7 +12,7 @@ use zksync_da_client::{ }; use zksync_types::{ ethabi::{self, Token}, - web3::contract::{Tokenizable, Tokenize}, + web3::contract::Tokenize, H256, U256, }; @@ -263,37 +263,24 @@ impl DataAvailabilityClient for AvailClient { "{}/eth/proof/{}?index={}", self.config.bridge_api_url, block_hash, tx_idx ); - let mut response: reqwest::Response; - let mut retries = self.config.max_retries; - let mut response_text: String; - let mut bridge_api_data: BridgeAPIResponse; - loop { - response = self - .api_client - .get(&url) - .send() - .await - .map_err(to_retriable_da_error)?; - response_text = response.text().await.unwrap(); - - if let Ok(data) = serde_json::from_str::(&response_text) { - bridge_api_data = data; - if bridge_api_data.error.is_none() { - break; - } - } - tokio::time::sleep(tokio::time::Duration::from_secs( - u64::try_from(480).unwrap(), - )) - .await; // usually takes 15 mins on Hex - retries += 1; - if retries > self.config.max_retries { - return Err(to_retriable_da_error(anyhow!( - "Failed to get inclusion data" - ))); - } + let response = self + .api_client + .get(&url) + .send() + .await + .map_err(to_retriable_da_error)?; + let bridge_api_data = response + .json::() + .await + .map_err(to_retriable_da_error)?; + if bridge_api_data.error.is_some() { + return Err(to_retriable_da_error(anyhow!(format!( + "Bridge API returned error: {}", + bridge_api_data.error.unwrap() + )))); } + let attestation_data: MerkleProofInput = MerkleProofInput { data_root_proof: bridge_api_data.data_root_proof.unwrap(), leaf_proof: bridge_api_data.leaf_proof.unwrap(), From d57cd928a2eeb597fc411183a6a523c31594b1bf Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Mon, 14 Oct 2024 17:04:27 +0530 Subject: [PATCH 10/26] feat(zksync-basic-types): make new api key type and change gas relay api key to use type --- core/lib/basic_types/src/api_key.rs | 20 ++++++++++++++++ core/lib/basic_types/src/lib.rs | 1 + .../lib/config/src/configs/da_client/avail.rs | 24 ++----------------- core/lib/config/src/testonly.rs | 7 +++--- core/lib/protobuf_config/src/secrets.rs | 7 +++--- core/node/da_clients/src/avail/client.rs | 5 ++-- 6 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 core/lib/basic_types/src/api_key.rs diff --git a/core/lib/basic_types/src/api_key.rs b/core/lib/basic_types/src/api_key.rs new file mode 100644 index 00000000000..eadf4e9051b --- /dev/null +++ b/core/lib/basic_types/src/api_key.rs @@ -0,0 +1,20 @@ +use std::str::FromStr; + +use secrecy::{ExposeSecret, Secret}; + +#[derive(Debug, Clone)] +pub struct APIKey(pub Secret); + +impl PartialEq for APIKey { + fn eq(&self, other: &Self) -> bool { + self.0.expose_secret().eq(other.0.expose_secret()) + } +} + +impl FromStr for APIKey { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(APIKey(s.parse()?)) + } +} diff --git a/core/lib/basic_types/src/lib.rs b/core/lib/basic_types/src/lib.rs index 197bd8eb7aa..d21a956bcd4 100644 --- a/core/lib/basic_types/src/lib.rs +++ b/core/lib/basic_types/src/lib.rs @@ -24,6 +24,7 @@ use serde::{de, Deserialize, Deserializer, Serialize}; #[macro_use] mod macros; +pub mod api_key; pub mod basic_fri_types; pub mod commitment; pub mod network; diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index ed3e0089a49..2fca43ee126 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -1,27 +1,7 @@ -use std::str::FromStr; - -use secrecy::ExposeSecret as _; -use secrecy::Secret; use serde::Deserialize; +use zksync_basic_types::api_key::APIKey; use zksync_basic_types::seed_phrase::SeedPhrase; -#[derive(Clone, Debug)] -pub struct GasRelayAPIKey(pub Secret); - -impl PartialEq for GasRelayAPIKey { - fn eq(&self, other: &Self) -> bool { - self.0.expose_secret().eq(other.0.expose_secret()) - } -} - -impl FromStr for GasRelayAPIKey { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - Ok(GasRelayAPIKey(s.parse()?)) - } -} - #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct AvailConfig { pub api_node_url: Option, @@ -36,5 +16,5 @@ pub struct AvailConfig { #[derive(Clone, Debug, PartialEq)] pub struct AvailSecrets { pub seed_phrase: Option, - pub gas_relay_api_key: Option, + pub gas_relay_api_key: Option, } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index a34ab9b45ee..3098d1a26b1 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -3,6 +3,7 @@ use std::num::NonZeroUsize; use rand::{distributions::Distribution, Rng}; use secrecy::Secret; use zksync_basic_types::{ + api_key::APIKey, basic_fri_types::CircuitIdRoundTuple, commitment::L1BatchCommitmentMode, network::Network, @@ -16,9 +17,7 @@ use zksync_crypto_primitives::K256PrivateKey; use crate::{ configs::{ - self, - da_client::{avail::GasRelayAPIKey, DAClientConfig::Avail}, - eth_sender::PubdataSendingMode, + self, da_client::DAClientConfig::Avail, eth_sender::PubdataSendingMode, external_price_api_client::ForcedPriceClientConfig, }, AvailConfig, @@ -962,7 +961,7 @@ impl Distribution for EncodeDist { fn sample(&self, rng: &mut R) -> configs::secrets::DataAvailabilitySecrets { configs::secrets::DataAvailabilitySecrets::Avail(configs::da_client::avail::AvailSecrets { seed_phrase: Some(SeedPhrase(Secret::new(self.sample(rng)))), - gas_relay_api_key: Some(GasRelayAPIKey(Secret::new(self.sample(rng)))), + gas_relay_api_key: Some(APIKey(Secret::new(self.sample(rng)))), }) } } diff --git a/core/lib/protobuf_config/src/secrets.rs b/core/lib/protobuf_config/src/secrets.rs index 0dbc68a3746..f7e1ac46c0e 100644 --- a/core/lib/protobuf_config/src/secrets.rs +++ b/core/lib/protobuf_config/src/secrets.rs @@ -2,15 +2,14 @@ use std::str::FromStr; use anyhow::Context; use secrecy::ExposeSecret; -use zksync_basic_types::{seed_phrase::SeedPhrase, url::SensitiveUrl}; +use zksync_basic_types::{api_key::APIKey, seed_phrase::SeedPhrase, url::SensitiveUrl}; use zksync_config::configs::{ consensus::{AttesterSecretKey, ConsensusSecrets, NodeSecretKey, ValidatorSecretKey}, - da_client::avail::{AvailSecrets, GasRelayAPIKey}, + da_client::avail::AvailSecrets, secrets::{DataAvailabilitySecrets, Secrets}, DatabaseSecrets, L1Secrets, }; use zksync_protobuf::{required, ProtoRepr}; -use zksync_types::seed_phrase; use crate::{ proto::{ @@ -117,7 +116,7 @@ impl ProtoRepr for proto::DataAvailabilitySecrets { }; let gas_relay_api_key = if avail_secret.gas_relay_api_key.is_some() { Some( - GasRelayAPIKey::from_str( + APIKey::from_str( required(&avail_secret.gas_relay_api_key).context("seed_phrase")?, ) .unwrap(), diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 19f889fec7a..03cd87fe01b 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -5,12 +5,13 @@ use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, sync::Arc}; use subxt_signer::ExposeSecret; -use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets, GasRelayAPIKey}; +use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets}; use zksync_da_client::{ types::{DAError, DispatchResponse, InclusionData}, DataAvailabilityClient, }; use zksync_types::{ + api_key::APIKey, ethabi::{self, Token}, web3::contract::Tokenize, H256, U256, @@ -24,7 +25,7 @@ pub struct AvailClient { config: AvailConfig, sdk_client: Option>, api_client: Arc, - gas_relay_api_key: Option, + gas_relay_api_key: Option, } #[derive(Deserialize, Serialize, Debug, Clone)] From d69cec1d8ae9f8b62e031d9db5c51ce55fba6475 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Tue, 15 Oct 2024 00:58:41 +0530 Subject: [PATCH 11/26] feat(avail-client,protobuf-da-client,env-config): enum-ify modes and configs --- .../lib/config/src/configs/da_client/avail.rs | 23 +- core/lib/config/src/testonly.rs | 18 +- core/lib/env_config/src/da_client.rs | 38 ++-- core/lib/protobuf_config/src/da_client.rs | 63 ++++-- core/node/da_clients/src/avail/client.rs | 205 ++++++------------ core/node/da_clients/src/avail/sdk.rs | 90 +++++++- 6 files changed, 248 insertions(+), 189 deletions(-) diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index 2fca43ee126..db30004f6b8 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -2,15 +2,30 @@ use serde::Deserialize; use zksync_basic_types::api_key::APIKey; use zksync_basic_types::seed_phrase::SeedPhrase; +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub enum AvailClientConfig { + Default(AvailDefaultConfig), + GasRelay(AvailGasRelayConfig), +} + #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct AvailConfig { - pub api_node_url: Option, pub bridge_api_url: String, - pub app_id: Option, + pub gas_relay_mode: bool, + pub config: AvailClientConfig, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub struct AvailDefaultConfig { + pub api_node_url: String, + pub app_id: u32, pub timeout: usize, pub max_retries: usize, - pub gas_relay_mode: bool, - pub gas_relay_api_url: Option, +} + +#[derive(Clone, Debug, PartialEq, Deserialize)] +pub struct AvailGasRelayConfig { + pub gas_relay_api_url: String, } #[derive(Clone, Debug, PartialEq)] diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 3098d1a26b1..1e616d9f943 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -17,7 +17,12 @@ use zksync_crypto_primitives::K256PrivateKey; use crate::{ configs::{ - self, da_client::DAClientConfig::Avail, eth_sender::PubdataSendingMode, + self, + da_client::{ + avail::{AvailClientConfig, AvailDefaultConfig}, + DAClientConfig::Avail, + }, + eth_sender::PubdataSendingMode, external_price_api_client::ForcedPriceClientConfig, }, AvailConfig, @@ -946,13 +951,14 @@ impl Distribution for EncodeDist { impl Distribution for EncodeDist { fn sample(&self, rng: &mut R) -> configs::da_client::DAClientConfig { Avail(AvailConfig { - api_node_url: self.sample(rng), bridge_api_url: self.sample(rng), - app_id: self.sample(rng), - timeout: self.sample(rng), - max_retries: self.sample(rng), gas_relay_mode: self.sample(rng), - gas_relay_api_url: self.sample(rng), + config: AvailClientConfig::Default(AvailDefaultConfig { + api_node_url: self.sample(rng), + app_id: self.sample(rng), + timeout: self.sample(rng), + max_retries: self.sample(rng), + }), }) } } diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index 353828de834..5d52de4d19b 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -54,7 +54,10 @@ impl FromEnv for DataAvailabilitySecrets { mod tests { use zksync_config::{ configs::{ - da_client::{DAClientConfig, DAClientConfig::ObjectStore}, + da_client::{ + avail::{AvailClientConfig, AvailDefaultConfig}, + DAClientConfig::{self, ObjectStore}, + }, object_store::ObjectStoreMode::GCS, }, AvailConfig, ObjectStoreConfig, @@ -100,16 +103,16 @@ mod tests { timeout: usize, max_retries: usize, gas_relay_mode: bool, - gas_relay_api_url: &str, ) -> DAClientConfig { DAClientConfig::Avail(AvailConfig { - api_node_url: Some(api_node_url.to_string()), bridge_api_url: bridge_api_url.to_string(), - app_id: Some(app_id), - timeout, - max_retries, gas_relay_mode, - gas_relay_api_url: Some(gas_relay_api_url.to_string()), + config: AvailClientConfig::Default(AvailDefaultConfig { + api_node_url: api_node_url.to_string(), + app_id, + timeout, + max_retries, + }), }) } @@ -123,7 +126,7 @@ mod tests { DA_APP_ID="1" DA_TIMEOUT="2" DA_MAX_RETRIES="3" - DA_GAS_RELAY_MODE="true" + DA_GAS_RELAY_MODE="false" DA_GAS_RELAY_API_URL="localhost:23456" "#; @@ -138,8 +141,7 @@ mod tests { "1".parse::().unwrap(), "2".parse::().unwrap(), "3".parse::().unwrap(), - true, - "localhost:23456", + false, ) ); } @@ -150,19 +152,23 @@ mod tests { let config = r#" DA_CLIENT="Avail" DA_SECRETS_SEED_PHRASE="bottom drive obey lake curtain smoke basket hold race lonely fit walk" + DA_SECRETS_GAS_RELAY_API_KEY="abcdefghijklmnopqrstuvwxyz0123456789" "#; lock.set_env(config); - let actual = match DataAvailabilitySecrets::from_env().unwrap() { - DataAvailabilitySecrets::Avail(avail) => avail.seed_phrase, + let (actual_seed, actual_key) = match DataAvailabilitySecrets::from_env().unwrap() { + DataAvailabilitySecrets::Avail(avail) => (avail.seed_phrase, avail.gas_relay_api_key), }; assert_eq!( - actual.unwrap(), - "bottom drive obey lake curtain smoke basket hold race lonely fit walk" - .parse() - .unwrap() + (actual_seed.unwrap(), actual_key.unwrap()), + ( + "bottom drive obey lake curtain smoke basket hold race lonely fit walk" + .parse() + .unwrap(), + "abcdefghijklmnopqrstuvwxyz0123456789".parse().unwrap() + ) ); } } diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index e4fa511ed10..e30019576a1 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -1,8 +1,10 @@ use anyhow::Context; use zksync_config::configs::{ - da_client::avail::AvailConfig, - da_client::DAClientConfig::{Avail, ObjectStore}, - {self}, + self, + da_client::{ + avail::{AvailClientConfig, AvailConfig, AvailDefaultConfig, AvailGasRelayConfig}, + DAClientConfig::{Avail, ObjectStore}, + }, }; use zksync_protobuf::{required, ProtoRepr}; @@ -18,35 +20,31 @@ impl ProtoRepr for proto::DataAvailabilityClient { proto::data_availability_client::Config::Avail(conf) => { Avail(if conf.gas_relay_mode.unwrap_or(false) { AvailConfig { - api_node_url: None, bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? .clone(), - app_id: None, - timeout: *required(&conf.timeout).context("timeout")? as usize, - max_retries: *required(&conf.max_retries).context("max_retries")? as usize, gas_relay_mode: true, - gas_relay_api_url: Some( - required(&conf.gas_relay_api_url) + config: AvailClientConfig::GasRelay(AvailGasRelayConfig { + gas_relay_api_url: required(&conf.gas_relay_api_url) .context("gas_relay_api_url")? .clone(), - ), + }), } } else { AvailConfig { - api_node_url: Some( - required(&conf.api_node_url) - .context("api_node_url")? - .clone(), - ), bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? .clone(), - app_id: Some(*required(&conf.app_id).context("app_id")?), - timeout: *required(&conf.timeout).context("timeout")? as usize, - max_retries: *required(&conf.max_retries).context("max_retries")? as usize, gas_relay_mode: false, - gas_relay_api_url: None, + config: AvailClientConfig::Default(AvailDefaultConfig { + api_node_url: required(&conf.api_node_url) + .context("api_node_url")? + .clone(), + app_id: *required(&conf.app_id).context("app_id")?, + timeout: *required(&conf.timeout).context("timeout")? as usize, + max_retries: *required(&conf.max_retries).context("max_retries")? + as usize, + }), } }) } @@ -63,13 +61,30 @@ impl ProtoRepr for proto::DataAvailabilityClient { Avail(config) => Self { config: Some(proto::data_availability_client::Config::Avail( proto::AvailConfig { - api_node_url: config.api_node_url.clone(), bridge_api_url: Some(config.bridge_api_url.clone()), - app_id: config.app_id, - timeout: Some(config.timeout as u64), - max_retries: Some(config.max_retries as u64), gas_relay_mode: Some(config.gas_relay_mode), - gas_relay_api_url: config.gas_relay_api_url.clone(), + api_node_url: match &config.config { + AvailClientConfig::Default(conf) => Some(conf.api_node_url.clone()), + AvailClientConfig::GasRelay(_) => None, + }, + app_id: match &config.config { + AvailClientConfig::Default(conf) => Some(conf.app_id), + AvailClientConfig::GasRelay(_) => None, + }, + timeout: match &config.config { + AvailClientConfig::Default(conf) => Some(conf.timeout as u64), + AvailClientConfig::GasRelay(_) => None, + }, + max_retries: match &config.config { + AvailClientConfig::Default(conf) => Some(conf.max_retries as u64), + AvailClientConfig::GasRelay(_) => None, + }, + gas_relay_api_url: match &config.config { + AvailClientConfig::GasRelay(conf) => { + Some(conf.gas_relay_api_url.clone()) + } + AvailClientConfig::Default(_) => None, + }, }, )), }, diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 03cd87fe01b..da68d8d1c5a 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -1,11 +1,14 @@ use anyhow::anyhow; use async_trait::async_trait; +use blake2::digest::crypto_common::rand_core::block; use bytes::Bytes; use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, sync::Arc}; use subxt_signer::ExposeSecret; -use zksync_config::configs::da_client::avail::{AvailConfig, AvailSecrets}; +use zksync_config::configs::da_client::avail::{ + AvailClientConfig, AvailConfig, AvailDefaultConfig, AvailGasRelayConfig, AvailSecrets, +}; use zksync_da_client::{ types::{DAError, DispatchResponse, InclusionData}, DataAvailabilityClient, @@ -17,15 +20,20 @@ use zksync_types::{ H256, U256, }; -use crate::avail::sdk::RawAvailClient; +use crate::avail::sdk::{GasRelayClient, RawAvailClient}; + +#[derive(Debug, Clone)] +enum AvailClientMode { + Default(RawAvailClient), + GasRelay(GasRelayClient), +} /// An implementation of the `DataAvailabilityClient` trait that interacts with the Avail network. #[derive(Debug, Clone)] pub struct AvailClient { config: AvailConfig, - sdk_client: Option>, - api_client: Arc, - gas_relay_api_key: Option, + sdk_client: Arc, + api_client: Arc, // bridge API reqwest client } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -42,22 +50,6 @@ pub struct BridgeAPIResponse { error: Option, } -#[derive(Deserialize, Serialize, Debug, Clone)] -pub struct GasRelayAPISubmissionResponse { - submission_id: String, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -pub struct GasRelayAPIStatusResponse { - submission: GasRelayAPISubmission, -} - -#[derive(Deserialize, Serialize, Debug, Clone)] -pub struct GasRelayAPISubmission { - block_hash: Option, - extrinsic_index: Option, -} - #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] struct MerkleProofInput { @@ -106,28 +98,43 @@ impl Tokenize for MerkleProofInput { impl AvailClient { pub async fn new(config: AvailConfig, secrets: AvailSecrets) -> anyhow::Result { - let api_client = reqwest::Client::new(); + let api_client = Arc::new(reqwest::Client::new()); if config.gas_relay_mode { + let gas_relay_api_key = secrets + .gas_relay_api_key + .ok_or_else(|| anyhow::anyhow!("Gas relay API key is missing"))?; + let gas_relay_config: AvailGasRelayConfig = match config.config.clone() { + AvailClientConfig::GasRelay(conf) => conf, + _ => unreachable!(), // validated in protobuf config + }; + let gas_relay_client = GasRelayClient::new( + &gas_relay_config.gas_relay_api_url, + gas_relay_api_key.0.expose_secret(), + Arc::clone(&api_client), + ) + .await?; return Ok(Self { config, - sdk_client: None, - api_client: Arc::new(api_client), - gas_relay_api_key: secrets.gas_relay_api_key, + sdk_client: Arc::new(AvailClientMode::GasRelay(gas_relay_client)), + api_client, }); } + let default_config = match &config.config { + AvailClientConfig::Default(conf) => conf.clone(), + _ => unreachable!(), // validated in protobug config + }; let seed_phrase = secrets .seed_phrase - .ok_or_else(|| anyhow::anyhow!("seed phrase"))?; + .ok_or_else(|| anyhow::anyhow!("Seed phrase is missing"))?; // these unwraps are safe because we validate in protobuf config let sdk_client = - RawAvailClient::new(config.app_id.unwrap(), seed_phrase.0.expose_secret()).await?; + RawAvailClient::new(default_config.app_id, seed_phrase.0.expose_secret()).await?; Ok(Self { config, - sdk_client: Some(Arc::new(sdk_client)), - api_client: Arc::new(api_client), - gas_relay_api_key: None, + sdk_client: Arc::new(AvailClientMode::Default(sdk_client)), + api_client, }) } } @@ -139,116 +146,42 @@ impl DataAvailabilityClient for AvailClient { _: u32, // batch_number data: Vec, ) -> anyhow::Result { - if self.config.gas_relay_mode { - let submit_url = format!( - "{}/user/submit_raw_data?token=ethereum", - self.config.gas_relay_api_url.clone().unwrap() - ); - // send the data to the gas relay - let submit_response = self - .api_client - .post(&submit_url) - .body(Bytes::from(data)) - .header("Content-Type", "text/plain") - .header( - "Authorization", - self.gas_relay_api_key - .as_ref() - .expect("No gas relay api key") - .0 - .expose_secret() - .clone(), - ) - .send() - .await - .map_err(to_retriable_da_error)?; - let submit_response_text = submit_response - .text() - .await - .map_err(to_retriable_da_error)?; - let submit_response_struct: GasRelayAPISubmissionResponse = - serde_json::from_str(&submit_response_text.clone()) - .map_err(to_retriable_da_error)?; - let status_url = format!( - "{}/user/get_submission_info?submission_id={}", - self.config.gas_relay_api_url.clone().unwrap(), - submit_response_struct.submission_id - ); - let mut retries = 0; - let mut status_response: reqwest::Response; - let mut status_response_text: String; - let mut status_response_struct: GasRelayAPIStatusResponse; - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(u64::try_from(40).unwrap())) - .await; // usually takes 20s to finalize - status_response = self - .api_client - .get(&status_url) - .header( - "Authorization", - &self - .gas_relay_api_key - .as_ref() - .expect("No gas relay api key") - .0 - .expose_secret() - .clone(), - ) - .send() + match self.sdk_client.as_ref() { + AvailClientMode::Default(client) => { + let default_config = match &self.config.config { + AvailClientConfig::Default(conf) => conf, + _ => unreachable!(), // validated in protobuf config + }; + let ws_client = WsClientBuilder::default() + .build(default_config.api_node_url.clone().as_str()) .await - .map_err(to_retriable_da_error)?; - status_response_text = status_response - .text() + .map_err(to_non_retriable_da_error)?; + + let extrinsic = client + .build_extrinsic(&ws_client, data) + .await + .map_err(to_non_retriable_da_error)?; + + let block_hash = client + .submit_extrinsic(&ws_client, extrinsic.as_str()) + .await + .map_err(to_non_retriable_da_error)?; + let tx_id = client + .get_tx_id(&ws_client, block_hash.as_str(), extrinsic.as_str()) + .await + .map_err(to_non_retriable_da_error)?; + Ok(DispatchResponse::from(format!("{}:{}", block_hash, tx_id))) + } + AvailClientMode::GasRelay(client) => { + let (block_hash, extrinsic_index) = client + .post_data(data) .await .map_err(to_retriable_da_error)?; - status_response_struct = - serde_json::from_str(&status_response_text).map_err(to_retriable_da_error)?; - if status_response_struct.submission.block_hash.is_some() { - break; - } - retries += 1; - if retries > self.config.max_retries { - return Err(to_retriable_da_error(anyhow!( - "Failed to get gas relay status" - ))); - } + Ok(DispatchResponse { + blob_id: format!("{:x}:{}", block_hash, extrinsic_index), + }) } - return Ok(DispatchResponse { - blob_id: format!( - "{:x}:{}", - status_response_struct.submission.block_hash.unwrap(), - status_response_struct.submission.extrinsic_index.unwrap() - ), - }); } - let client = WsClientBuilder::default() - .build(self.config.api_node_url.clone().unwrap().as_str()) - .await - .map_err(to_non_retriable_da_error)?; - - let extrinsic = self - .sdk_client - .as_ref() - .unwrap() - .build_extrinsic(&client, data) - .await - .map_err(to_non_retriable_da_error)?; - - let block_hash = self - .sdk_client - .as_ref() - .unwrap() - .submit_extrinsic(&client, extrinsic.as_str()) - .await - .map_err(to_non_retriable_da_error)?; - let tx_id = self - .sdk_client - .as_ref() - .unwrap() - .get_tx_id(&client, block_hash.as_str(), extrinsic.as_str()) - .await - .map_err(to_non_retriable_da_error)?; - Ok(DispatchResponse::from(format!("{}:{}", block_hash, tx_id))) } async fn get_inclusion_data( @@ -277,7 +210,7 @@ impl DataAvailabilityClient for AvailClient { .map_err(to_retriable_da_error)?; if bridge_api_data.error.is_some() { return Err(to_retriable_da_error(anyhow!(format!( - "Bridge API returned error: {}", + "Bridge API returned an error: {}", bridge_api_data.error.unwrap() )))); } diff --git a/core/node/da_clients/src/avail/sdk.rs b/core/node/da_clients/src/avail/sdk.rs index 002422109d0..896051e0f0d 100644 --- a/core/node/da_clients/src/avail/sdk.rs +++ b/core/node/da_clients/src/avail/sdk.rs @@ -1,20 +1,23 @@ //! Minimal reimplementation of the Avail SDK client required for the DA client implementation. //! This is considered to be a temporary solution until a mature SDK is available on crates.io -use std::fmt::Debug; +use std::{fmt::Debug, sync::Arc}; +use bytes::Bytes; use jsonrpsee::{ core::client::{Client, ClientT, Subscription, SubscriptionClientT}, rpc_params, }; use parity_scale_codec::{Compact, Decode, Encode}; use scale_encode::EncodeAsFields; +use serde::{Deserialize, Serialize}; use subxt_signer::{ bip39::Mnemonic, sr25519::{Keypair, Signature}, }; +use zksync_types::H256; -use crate::avail::client::to_non_retriable_da_error; +use crate::avail::client::{to_non_retriable_da_error, to_retriable_da_error}; const PROTOCOL_VERSION: u8 = 4; @@ -287,7 +290,7 @@ impl RawAvailClient { let status = sub.next().await.transpose()?; if status.is_some() && status.as_ref().unwrap().is_object() { - if let Some(block_hash) = status.unwrap().get("inBlock") { + if let Some(block_hash) = status.unwrap().get("finalized") { break block_hash .as_str() .ok_or_else(|| anyhow::anyhow!("Invalid block hash"))? @@ -369,3 +372,84 @@ fn ss58hash(data: &[u8]) -> Vec { ctx.update(data); ctx.finalize().to_vec() } + +/// An implementation of the `DataAvailabilityClient` trait that interacts with the Avail network. +#[derive(Debug, Clone)] +pub(crate) struct GasRelayClient { + api_url: String, + api_key: String, + api_client: Arc, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GasRelayAPISubmissionResponse { + submission_id: String, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GasRelayAPIStatusResponse { + submission: GasRelayAPISubmission, +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct GasRelayAPISubmission { + block_hash: Option, + extrinsic_index: Option, +} + +impl GasRelayClient { + pub(crate) async fn new( + api_url: &str, + api_key: &str, + api_client: Arc, + ) -> anyhow::Result { + Ok(Self { + api_url: api_url.to_owned(), + api_key: api_key.to_owned(), + api_client, + }) + } + + pub(crate) async fn post_data(&self, data: Vec) -> anyhow::Result<(H256, u64)> { + let submit_url = format!("{}/user/submit_raw_data?token=ethereum", &self.api_url); + // send the data to the gas relay + let submit_response = self + .api_client + .post(&submit_url) + .body(Bytes::from(data)) + .header("Content-Type", "text/plain") + .header("Authorization", &self.api_key) + .send() + .await + .map_err(to_retriable_da_error)?; + let submit_response = submit_response + .json::() + .await + .map_err(to_retriable_da_error)?; + let status_url = format!( + "{}/user/get_submission_info?submission_id={}", + self.api_url, submit_response.submission_id + ); + let (block_hash, extrinsic_index) = loop { + tokio::time::sleep(tokio::time::Duration::from_secs(u64::try_from(41).unwrap())).await; // usually takes 40s to finalize + let status_response = self + .api_client + .get(&status_url) + .header("Authorization", &self.api_key) + .send() + .await + .map_err(to_retriable_da_error)?; + let status_response = status_response + .json::() + .await + .map_err(to_retriable_da_error)?; + if status_response.submission.block_hash.is_some() { + break ( + status_response.submission.block_hash.unwrap(), + status_response.submission.extrinsic_index.unwrap(), + ); + } + }; + Ok((block_hash, extrinsic_index)) + } +} From f3a86876b3aa4ab6a73faf54a83cacc6bf5b2cb1 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Tue, 15 Oct 2024 01:03:38 +0530 Subject: [PATCH 12/26] feat(protobuf-da-client): remove timeout and retries --- core/lib/config/src/configs/da_client/avail.rs | 2 -- core/lib/config/src/testonly.rs | 2 -- core/lib/env_config/src/da_client.rs | 8 -------- core/lib/protobuf_config/src/da_client.rs | 11 ----------- .../protobuf_config/src/proto/config/da_client.proto | 9 ++++----- 5 files changed, 4 insertions(+), 28 deletions(-) diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index db30004f6b8..fb4b91c2165 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -19,8 +19,6 @@ pub struct AvailConfig { pub struct AvailDefaultConfig { pub api_node_url: String, pub app_id: u32, - pub timeout: usize, - pub max_retries: usize, } #[derive(Clone, Debug, PartialEq, Deserialize)] diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 1e616d9f943..1d7dd84628b 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -956,8 +956,6 @@ impl Distribution for EncodeDist { config: AvailClientConfig::Default(AvailDefaultConfig { api_node_url: self.sample(rng), app_id: self.sample(rng), - timeout: self.sample(rng), - max_retries: self.sample(rng), }), }) } diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index 5d52de4d19b..8d4c74b5757 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -100,8 +100,6 @@ mod tests { api_node_url: &str, bridge_api_url: &str, app_id: u32, - timeout: usize, - max_retries: usize, gas_relay_mode: bool, ) -> DAClientConfig { DAClientConfig::Avail(AvailConfig { @@ -110,8 +108,6 @@ mod tests { config: AvailClientConfig::Default(AvailDefaultConfig { api_node_url: api_node_url.to_string(), app_id, - timeout, - max_retries, }), }) } @@ -124,8 +120,6 @@ mod tests { DA_API_NODE_URL="localhost:12345" DA_BRIDGE_API_URL="localhost:54321" DA_APP_ID="1" - DA_TIMEOUT="2" - DA_MAX_RETRIES="3" DA_GAS_RELAY_MODE="false" DA_GAS_RELAY_API_URL="localhost:23456" "#; @@ -139,8 +133,6 @@ mod tests { "localhost:12345", "localhost:54321", "1".parse::().unwrap(), - "2".parse::().unwrap(), - "3".parse::().unwrap(), false, ) ); diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index e30019576a1..7c7f723c130 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -41,9 +41,6 @@ impl ProtoRepr for proto::DataAvailabilityClient { .context("api_node_url")? .clone(), app_id: *required(&conf.app_id).context("app_id")?, - timeout: *required(&conf.timeout).context("timeout")? as usize, - max_retries: *required(&conf.max_retries).context("max_retries")? - as usize, }), } }) @@ -71,14 +68,6 @@ impl ProtoRepr for proto::DataAvailabilityClient { AvailClientConfig::Default(conf) => Some(conf.app_id), AvailClientConfig::GasRelay(_) => None, }, - timeout: match &config.config { - AvailClientConfig::Default(conf) => Some(conf.timeout as u64), - AvailClientConfig::GasRelay(_) => None, - }, - max_retries: match &config.config { - AvailClientConfig::Default(conf) => Some(conf.max_retries as u64), - AvailClientConfig::GasRelay(_) => None, - }, gas_relay_api_url: match &config.config { AvailClientConfig::GasRelay(conf) => { Some(conf.gas_relay_api_url.clone()) diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index 4d106f34d76..a1298861075 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -8,11 +8,10 @@ message AvailConfig { optional string api_node_url = 1; optional string bridge_api_url = 2; optional uint32 app_id = 4; - optional uint64 timeout = 5; - optional uint64 max_retries = 6; - optional bool gas_relay_mode = 7; - optional string gas_relay_api_url = 8; - reserved 3; reserved "seed"; + optional bool gas_relay_mode = 5; + optional string gas_relay_api_url = 6; + reserved "gas_relay_api_key"; + reserved "seed"; } message DataAvailabilityClient { From 0606be1669b73040e7fdcb9ec13bece2606092ae Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Tue, 15 Oct 2024 01:21:20 +0530 Subject: [PATCH 13/26] lint: clippy --- core/node/da_clients/src/avail/client.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index da68d8d1c5a..340d034908f 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -1,20 +1,17 @@ use anyhow::anyhow; use async_trait::async_trait; -use blake2::digest::crypto_common::rand_core::block; -use bytes::Bytes; use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, sync::Arc}; use subxt_signer::ExposeSecret; use zksync_config::configs::da_client::avail::{ - AvailClientConfig, AvailConfig, AvailDefaultConfig, AvailGasRelayConfig, AvailSecrets, + AvailClientConfig, AvailConfig, AvailGasRelayConfig, AvailSecrets, }; use zksync_da_client::{ types::{DAError, DispatchResponse, InclusionData}, DataAvailabilityClient, }; use zksync_types::{ - api_key::APIKey, ethabi::{self, Token}, web3::contract::Tokenize, H256, U256, From ca1b3810a96f86194bf5b4d087c8fd00b3a255a5 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Tue, 15 Oct 2024 21:17:00 +0530 Subject: [PATCH 14/26] feat(avail-client,protobuf-da-client): remove fields and add timeout --- Cargo.lock | 2 + .../lib/config/src/configs/da_client/avail.rs | 2 +- core/lib/config/src/testonly.rs | 2 +- core/lib/env_config/src/da_client.rs | 30 ++++---- core/lib/protobuf_config/src/da_client.rs | 57 +++++++-------- .../src/proto/config/da_client.proto | 23 ++++-- core/node/da_clients/src/avail/client.rs | 73 ++++++++----------- 7 files changed, 99 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11c37bda57f..5684682ce88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10012,11 +10012,13 @@ dependencies = [ "base58", "blake2 0.10.6", "blake2b_simd", + "bytes", "flate2", "futures 0.3.30", "hex", "jsonrpsee 0.23.2", "parity-scale-codec", + "reqwest 0.12.7", "scale-encode", "serde", "serde_json", diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index fb4b91c2165..c5afd31eedf 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -11,7 +11,7 @@ pub enum AvailClientConfig { #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct AvailConfig { pub bridge_api_url: String, - pub gas_relay_mode: bool, + pub timeout: usize, pub config: AvailClientConfig, } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index 1d7dd84628b..fb8efb739f4 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -952,7 +952,7 @@ impl Distribution for EncodeDist { fn sample(&self, rng: &mut R) -> configs::da_client::DAClientConfig { Avail(AvailConfig { bridge_api_url: self.sample(rng), - gas_relay_mode: self.sample(rng), + timeout: self.sample(rng), config: AvailClientConfig::Default(AvailDefaultConfig { api_node_url: self.sample(rng), app_id: self.sample(rng), diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index 8d4c74b5757..d07ffdabd37 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -30,14 +30,19 @@ impl FromEnv for DataAvailabilitySecrets { let client_tag = std::env::var("DA_CLIENT")?; let secrets = match client_tag.as_str() { AVAIL_CLIENT_CONFIG_NAME => { - let seed_phrase = env::var("DA_SECRETS_SEED_PHRASE") - .ok() - .map(|s| s.parse()) - .transpose()?; - let gas_relay_api_key = env::var("DA_GAS_RELAY_API_KEY") - .ok() - .map(|s| s.parse()) - .transpose()?; + let seed_phrase: Option = + env::var("DA_SECRETS_SEED_PHRASE") + .ok() + .map(|s| s.parse()) + .transpose()?; + let gas_relay_api_key: Option = + env::var("DA_SECRETS_GAS_RELAY_API_KEY") + .ok() + .map(|s| s.parse()) + .transpose()?; + if seed_phrase.is_none() && gas_relay_api_key.is_none() { + anyhow::bail!("No secrets provided for Avail DA client"); + } Self::Avail(AvailSecrets { seed_phrase, gas_relay_api_key, @@ -100,11 +105,11 @@ mod tests { api_node_url: &str, bridge_api_url: &str, app_id: u32, - gas_relay_mode: bool, + timeout: usize, ) -> DAClientConfig { DAClientConfig::Avail(AvailConfig { bridge_api_url: bridge_api_url.to_string(), - gas_relay_mode, + timeout, config: AvailClientConfig::Default(AvailDefaultConfig { api_node_url: api_node_url.to_string(), app_id, @@ -120,8 +125,7 @@ mod tests { DA_API_NODE_URL="localhost:12345" DA_BRIDGE_API_URL="localhost:54321" DA_APP_ID="1" - DA_GAS_RELAY_MODE="false" - DA_GAS_RELAY_API_URL="localhost:23456" + DA_TIMEOUT="2" "#; lock.set_env(config); @@ -133,7 +137,7 @@ mod tests { "localhost:12345", "localhost:54321", "1".parse::().unwrap(), - false, + "2".parse::().unwrap(), ) ); } diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index 7c7f723c130..98247ff5ada 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -18,31 +18,31 @@ impl ProtoRepr for proto::DataAvailabilityClient { let client = match config { proto::data_availability_client::Config::Avail(conf) => { - Avail(if conf.gas_relay_mode.unwrap_or(false) { - AvailConfig { + Avail(match conf.config.as_ref() { + Some(proto::avail_config::Config::Default(default_conf)) => AvailConfig { bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? .clone(), - gas_relay_mode: true, - config: AvailClientConfig::GasRelay(AvailGasRelayConfig { - gas_relay_api_url: required(&conf.gas_relay_api_url) - .context("gas_relay_api_url")? + timeout: *required(&conf.timeout).context("timeout")? as usize, + config: AvailClientConfig::Default(AvailDefaultConfig { + api_node_url: required(&default_conf.api_node_url) + .context("api_node_url")? .clone(), + app_id: *required(&default_conf.app_id).context("app_id")?, }), - } - } else { - AvailConfig { + }, + Some(proto::avail_config::Config::GasRelay(gas_relay_conf)) => AvailConfig { bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? .clone(), - gas_relay_mode: false, - config: AvailClientConfig::Default(AvailDefaultConfig { - api_node_url: required(&conf.api_node_url) - .context("api_node_url")? + timeout: *required(&conf.timeout).context("timeout")? as usize, + config: AvailClientConfig::GasRelay(AvailGasRelayConfig { + gas_relay_api_url: required(&gas_relay_conf.gas_relay_api_url) + .context("gas_relay_api_url")? .clone(), - app_id: *required(&conf.app_id).context("app_id")?, }), - } + }, + None => return Err(anyhow::anyhow!("Invalid Avail DA configuration")), }) } proto::data_availability_client::Config::ObjectStore(conf) => { @@ -59,20 +59,19 @@ impl ProtoRepr for proto::DataAvailabilityClient { config: Some(proto::data_availability_client::Config::Avail( proto::AvailConfig { bridge_api_url: Some(config.bridge_api_url.clone()), - gas_relay_mode: Some(config.gas_relay_mode), - api_node_url: match &config.config { - AvailClientConfig::Default(conf) => Some(conf.api_node_url.clone()), - AvailClientConfig::GasRelay(_) => None, - }, - app_id: match &config.config { - AvailClientConfig::Default(conf) => Some(conf.app_id), - AvailClientConfig::GasRelay(_) => None, - }, - gas_relay_api_url: match &config.config { - AvailClientConfig::GasRelay(conf) => { - Some(conf.gas_relay_api_url.clone()) - } - AvailClientConfig::Default(_) => None, + timeout: Some(config.timeout as u64), + config: match &config.config { + AvailClientConfig::Default(conf) => Some( + proto::avail_config::Config::Default(proto::AvailDefaultConfig { + api_node_url: Some(conf.api_node_url.clone()), + app_id: Some(conf.app_id), + }), + ), + AvailClientConfig::GasRelay(conf) => Some( + proto::avail_config::Config::GasRelay(proto::AvailGasRelayConfig { + gas_relay_api_url: Some(conf.gas_relay_api_url.clone()), + }), + ), }, }, )), diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index a1298861075..e9888635578 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -5,13 +5,26 @@ package zksync.config.da_client; import "zksync/config/object_store.proto"; message AvailConfig { + optional string bridge_api_url = 1; + optional uint64 timeout = 2; + oneof config { + AvailDefaultConfig default = 4; + AvailGasRelayConfig gas_relay = 5; + } + reserved 3; + reserved "seed"; + reserved 6; + reserved "max_retries"; +} + +message AvailDefaultConfig { optional string api_node_url = 1; - optional string bridge_api_url = 2; - optional uint32 app_id = 4; - optional bool gas_relay_mode = 5; - optional string gas_relay_api_url = 6; + optional uint32 app_id = 2; +} + +message AvailGasRelayConfig { + optional string gas_relay_api_url = 1; reserved "gas_relay_api_key"; - reserved "seed"; } message DataAvailabilityClient { diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 340d034908f..2acd43f1f66 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -4,9 +4,7 @@ use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; use std::{fmt::Debug, sync::Arc}; use subxt_signer::ExposeSecret; -use zksync_config::configs::da_client::avail::{ - AvailClientConfig, AvailConfig, AvailGasRelayConfig, AvailSecrets, -}; +use zksync_config::configs::da_client::avail::{AvailClientConfig, AvailConfig, AvailSecrets}; use zksync_da_client::{ types::{DAError, DispatchResponse, InclusionData}, DataAvailabilityClient, @@ -96,43 +94,38 @@ impl Tokenize for MerkleProofInput { impl AvailClient { pub async fn new(config: AvailConfig, secrets: AvailSecrets) -> anyhow::Result { let api_client = Arc::new(reqwest::Client::new()); - if config.gas_relay_mode { - let gas_relay_api_key = secrets - .gas_relay_api_key - .ok_or_else(|| anyhow::anyhow!("Gas relay API key is missing"))?; - let gas_relay_config: AvailGasRelayConfig = match config.config.clone() { - AvailClientConfig::GasRelay(conf) => conf, - _ => unreachable!(), // validated in protobuf config - }; - let gas_relay_client = GasRelayClient::new( - &gas_relay_config.gas_relay_api_url, - gas_relay_api_key.0.expose_secret(), - Arc::clone(&api_client), - ) - .await?; - return Ok(Self { - config, - sdk_client: Arc::new(AvailClientMode::GasRelay(gas_relay_client)), - api_client, - }); + match config.config.clone() { + AvailClientConfig::GasRelay(conf) => { + let gas_relay_api_key = secrets + .gas_relay_api_key + .ok_or_else(|| anyhow::anyhow!("Gas relay API key is missing"))?; + let gas_relay_client = GasRelayClient::new( + &conf.gas_relay_api_url, + gas_relay_api_key.0.expose_secret(), + Arc::clone(&api_client), + ) + .await?; + Ok(Self { + config, + sdk_client: Arc::new(AvailClientMode::GasRelay(gas_relay_client)), + api_client, + }) + } + AvailClientConfig::Default(conf) => { + let seed_phrase = secrets + .seed_phrase + .ok_or_else(|| anyhow::anyhow!("Seed phrase is missing"))?; + // these unwraps are safe because we validate in protobuf config + let sdk_client = + RawAvailClient::new(conf.app_id, seed_phrase.0.expose_secret()).await?; + + Ok(Self { + config, + sdk_client: Arc::new(AvailClientMode::Default(sdk_client)), + api_client, + }) + } } - - let default_config = match &config.config { - AvailClientConfig::Default(conf) => conf.clone(), - _ => unreachable!(), // validated in protobug config - }; - let seed_phrase = secrets - .seed_phrase - .ok_or_else(|| anyhow::anyhow!("Seed phrase is missing"))?; - // these unwraps are safe because we validate in protobuf config - let sdk_client = - RawAvailClient::new(default_config.app_id, seed_phrase.0.expose_secret()).await?; - - Ok(Self { - config, - sdk_client: Arc::new(AvailClientMode::Default(sdk_client)), - api_client, - }) } } @@ -189,12 +182,10 @@ impl DataAvailabilityClient for AvailClient { error: anyhow!("Invalid blob ID format"), is_retriable: false, })?; - let url = format!( "{}/eth/proof/{}?index={}", self.config.bridge_api_url, block_hash, tx_idx ); - let response = self .api_client .get(&url) From 5304bc1a0aa23a6d02f65f7c9b7160199fe2ce64 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Tue, 15 Oct 2024 21:26:11 +0530 Subject: [PATCH 15/26] feat(avail-client): introduce timeouts for inclusion checks --- core/node/da_clients/src/avail/client.rs | 39 ++++++++++++++---------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 2acd43f1f66..5515978f3d6 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -186,22 +186,29 @@ impl DataAvailabilityClient for AvailClient { "{}/eth/proof/{}?index={}", self.config.bridge_api_url, block_hash, tx_idx ); - let response = self - .api_client - .get(&url) - .send() - .await - .map_err(to_retriable_da_error)?; - let bridge_api_data = response - .json::() - .await - .map_err(to_retriable_da_error)?; - if bridge_api_data.error.is_some() { - return Err(to_retriable_da_error(anyhow!(format!( - "Bridge API returned an error: {}", - bridge_api_data.error.unwrap() - )))); - } + // record current time + let current_timestamp = std::time::Instant::now(); + let bridge_api_data = loop { + let response = self + .api_client + .get(&url) + .send() + .await + .map_err(to_retriable_da_error)?; + let bridge_api_data = response + .json::() + .await + .map_err(to_retriable_da_error)?; + if bridge_api_data.error.is_none() { + break bridge_api_data; + } + if current_timestamp.elapsed().as_secs() > self.config.timeout as u64 { + return Err(to_non_retriable_da_error(anyhow!( + "Inclusion check timeout exceeded" + ))); + } + tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + }; let attestation_data: MerkleProofInput = MerkleProofInput { data_root_proof: bridge_api_data.data_root_proof.unwrap(), From de45684297fd46f32deaef35617d5193b20d818c Mon Sep 17 00:00:00 2001 From: dimazhornyk Date: Wed, 16 Oct 2024 11:15:36 +0200 Subject: [PATCH 16/26] fix: proto and env configs --- .../lib/config/src/configs/da_client/avail.rs | 10 +++-- core/lib/config/src/testonly.rs | 2 +- core/lib/env_config/src/da_client.rs | 44 +++++++++++++------ core/lib/protobuf_config/src/da_client.rs | 28 ++++++------ .../src/proto/config/da_client.proto | 19 ++++---- core/node/da_clients/src/avail/client.rs | 7 +-- 6 files changed, 66 insertions(+), 44 deletions(-) diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index c5afd31eedf..d6ec4861b48 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -1,10 +1,13 @@ use serde::Deserialize; -use zksync_basic_types::api_key::APIKey; -use zksync_basic_types::seed_phrase::SeedPhrase; +use zksync_basic_types::{api_key::APIKey, seed_phrase::SeedPhrase}; + +pub const AVAIL_GAS_RELAY_CLIENT_NAME: &str = "GasRelay"; +pub const AVAIL_FULL_CLIENT_NAME: &str = "FullClient"; #[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(tag = "avail_client")] pub enum AvailClientConfig { - Default(AvailDefaultConfig), + FullClient(AvailDefaultConfig), GasRelay(AvailGasRelayConfig), } @@ -12,6 +15,7 @@ pub enum AvailClientConfig { pub struct AvailConfig { pub bridge_api_url: String, pub timeout: usize, + #[serde(flatten)] pub config: AvailClientConfig, } diff --git a/core/lib/config/src/testonly.rs b/core/lib/config/src/testonly.rs index fb8efb739f4..0d6cddbcbe9 100644 --- a/core/lib/config/src/testonly.rs +++ b/core/lib/config/src/testonly.rs @@ -953,7 +953,7 @@ impl Distribution for EncodeDist { Avail(AvailConfig { bridge_api_url: self.sample(rng), timeout: self.sample(rng), - config: AvailClientConfig::Default(AvailDefaultConfig { + config: AvailClientConfig::FullClient(AvailDefaultConfig { api_node_url: self.sample(rng), app_id: self.sample(rng), }), diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index d07ffdabd37..bf1c3121f16 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -2,19 +2,35 @@ use std::env; use zksync_config::configs::{ da_client::{ - avail::AvailSecrets, DAClientConfig, AVAIL_CLIENT_CONFIG_NAME, - OBJECT_STORE_CLIENT_CONFIG_NAME, + avail::{ + AvailClientConfig, AvailSecrets, AVAIL_FULL_CLIENT_NAME, AVAIL_GAS_RELAY_CLIENT_NAME, + }, + DAClientConfig, AVAIL_CLIENT_CONFIG_NAME, OBJECT_STORE_CLIENT_CONFIG_NAME, }, secrets::DataAvailabilitySecrets, + AvailConfig, }; use crate::{envy_load, FromEnv}; impl FromEnv for DAClientConfig { fn from_env() -> anyhow::Result { - let client_tag = std::env::var("DA_CLIENT")?; + let client_tag = env::var("DA_CLIENT")?; let config = match client_tag.as_str() { - AVAIL_CLIENT_CONFIG_NAME => Self::Avail(envy_load("da_avail_config", "DA_")?), + AVAIL_CLIENT_CONFIG_NAME => Self::Avail(AvailConfig { + bridge_api_url: env::var("DA_BRIDGE_API_URL").ok().unwrap(), + timeout: env::var("DA_TIMEOUT")?.parse()?, + + config: match env::var("DA_AVAIL_CLIENT_TYPE")?.as_str() { + AVAIL_FULL_CLIENT_NAME => { + AvailClientConfig::FullClient(envy_load("da_avail_full_client", "DA_")?) + } + AVAIL_GAS_RELAY_CLIENT_NAME => { + AvailClientConfig::GasRelay(envy_load("da_avail_gas_relay", "DA_")?) + } + _ => anyhow::bail!("Unknown Avail DA client type"), + }, + }), OBJECT_STORE_CLIENT_CONFIG_NAME => { Self::ObjectStore(envy_load("da_object_store", "DA_")?) } @@ -33,13 +49,11 @@ impl FromEnv for DataAvailabilitySecrets { let seed_phrase: Option = env::var("DA_SECRETS_SEED_PHRASE") .ok() - .map(|s| s.parse()) - .transpose()?; + .map(|s| s.parse().unwrap()); let gas_relay_api_key: Option = env::var("DA_SECRETS_GAS_RELAY_API_KEY") .ok() - .map(|s| s.parse()) - .transpose()?; + .map(|s| s.parse().unwrap()); if seed_phrase.is_none() && gas_relay_api_key.is_none() { anyhow::bail!("No secrets provided for Avail DA client"); } @@ -110,7 +124,7 @@ mod tests { DAClientConfig::Avail(AvailConfig { bridge_api_url: bridge_api_url.to_string(), timeout, - config: AvailClientConfig::Default(AvailDefaultConfig { + config: AvailClientConfig::FullClient(AvailDefaultConfig { api_node_url: api_node_url.to_string(), app_id, }), @@ -122,10 +136,13 @@ mod tests { let mut lock = MUTEX.lock(); let config = r#" DA_CLIENT="Avail" - DA_API_NODE_URL="localhost:12345" + DA_AVAIL_CLIENT_TYPE="FullClient" + DA_BRIDGE_API_URL="localhost:54321" - DA_APP_ID="1" DA_TIMEOUT="2" + + DA_API_NODE_URL="localhost:12345" + DA_APP_ID="1" "#; lock.set_env(config); @@ -148,7 +165,6 @@ mod tests { let config = r#" DA_CLIENT="Avail" DA_SECRETS_SEED_PHRASE="bottom drive obey lake curtain smoke basket hold race lonely fit walk" - DA_SECRETS_GAS_RELAY_API_KEY="abcdefghijklmnopqrstuvwxyz0123456789" "#; lock.set_env(config); @@ -158,12 +174,12 @@ mod tests { }; assert_eq!( - (actual_seed.unwrap(), actual_key.unwrap()), + (actual_seed.unwrap(), actual_key), ( "bottom drive obey lake curtain smoke basket hold race lonely fit walk" .parse() .unwrap(), - "abcdefghijklmnopqrstuvwxyz0123456789".parse().unwrap() + None ) ); } diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index 98247ff5ada..a398624bbc5 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -19,18 +19,20 @@ impl ProtoRepr for proto::DataAvailabilityClient { let client = match config { proto::data_availability_client::Config::Avail(conf) => { Avail(match conf.config.as_ref() { - Some(proto::avail_config::Config::Default(default_conf)) => AvailConfig { - bridge_api_url: required(&conf.bridge_api_url) - .context("bridge_api_url")? - .clone(), - timeout: *required(&conf.timeout).context("timeout")? as usize, - config: AvailClientConfig::Default(AvailDefaultConfig { - api_node_url: required(&default_conf.api_node_url) - .context("api_node_url")? + Some(proto::avail_config::Config::FullClient(full_client__conf)) => { + AvailConfig { + bridge_api_url: required(&conf.bridge_api_url) + .context("bridge_api_url")? .clone(), - app_id: *required(&default_conf.app_id).context("app_id")?, - }), - }, + timeout: *required(&conf.timeout).context("timeout")? as usize, + config: AvailClientConfig::FullClient(AvailDefaultConfig { + api_node_url: required(&full_client__conf.api_node_url) + .context("api_node_url")? + .clone(), + app_id: *required(&full_client__conf.app_id).context("app_id")?, + }), + } + } Some(proto::avail_config::Config::GasRelay(gas_relay_conf)) => AvailConfig { bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? @@ -61,8 +63,8 @@ impl ProtoRepr for proto::DataAvailabilityClient { bridge_api_url: Some(config.bridge_api_url.clone()), timeout: Some(config.timeout as u64), config: match &config.config { - AvailClientConfig::Default(conf) => Some( - proto::avail_config::Config::Default(proto::AvailDefaultConfig { + AvailClientConfig::FullClient(conf) => Some( + proto::avail_config::Config::FullClient(proto::AvailClientConfig { api_node_url: Some(conf.api_node_url.clone()), app_id: Some(conf.app_id), }), diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index e9888635578..1dadc22c268 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -5,26 +5,25 @@ package zksync.config.da_client; import "zksync/config/object_store.proto"; message AvailConfig { - optional string bridge_api_url = 1; - optional uint64 timeout = 2; + optional string bridge_api_url = 2; + optional uint64 timeout = 5; oneof config { - AvailDefaultConfig default = 4; - AvailGasRelayConfig gas_relay = 5; + AvailClientConfig full_client = 7; + AvailGasRelayConfig gas_relay = 8; } - reserved 3; - reserved "seed"; - reserved 6; - reserved "max_retries"; + reserved 1; reserved "api_node_url"; + reserved 3; reserved "seed"; + reserved 4; reserved "app_id"; + reserved 6; reserved "max_retries"; } -message AvailDefaultConfig { +message AvailClientConfig { optional string api_node_url = 1; optional uint32 app_id = 2; } message AvailGasRelayConfig { optional string gas_relay_api_url = 1; - reserved "gas_relay_api_key"; } message DataAvailabilityClient { diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 5515978f3d6..5efcb64befd 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -1,8 +1,9 @@ +use std::{fmt::Debug, sync::Arc}; + use anyhow::anyhow; use async_trait::async_trait; use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; -use std::{fmt::Debug, sync::Arc}; use subxt_signer::ExposeSecret; use zksync_config::configs::da_client::avail::{AvailClientConfig, AvailConfig, AvailSecrets}; use zksync_da_client::{ @@ -111,7 +112,7 @@ impl AvailClient { api_client, }) } - AvailClientConfig::Default(conf) => { + AvailClientConfig::FullClient(conf) => { let seed_phrase = secrets .seed_phrase .ok_or_else(|| anyhow::anyhow!("Seed phrase is missing"))?; @@ -139,7 +140,7 @@ impl DataAvailabilityClient for AvailClient { match self.sdk_client.as_ref() { AvailClientMode::Default(client) => { let default_config = match &self.config.config { - AvailClientConfig::Default(conf) => conf, + AvailClientConfig::FullClient(conf) => conf, _ => unreachable!(), // validated in protobuf config }; let ws_client = WsClientBuilder::default() From e04cbe8530364b6be6711d4a601e5816e3ec8549 Mon Sep 17 00:00:00 2001 From: dimazhornyk Date: Wed, 16 Oct 2024 12:31:14 +0200 Subject: [PATCH 17/26] update contracts --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 6b1f483f93b..84d5e3716f6 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 6b1f483f93baab3b1d44e8f4efaece71377c2d45 +Subproject commit 84d5e3716f645909e8144c7d50af9dd6dd9ded62 From 6dd2671cd78c1cba8b2e82e049cd26292c28ebc4 Mon Sep 17 00:00:00 2001 From: dimazhornyk Date: Wed, 16 Oct 2024 12:37:11 +0200 Subject: [PATCH 18/26] fix: camel_case --- core/lib/protobuf_config/src/da_client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index a398624bbc5..ecf49886fd7 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -19,17 +19,17 @@ impl ProtoRepr for proto::DataAvailabilityClient { let client = match config { proto::data_availability_client::Config::Avail(conf) => { Avail(match conf.config.as_ref() { - Some(proto::avail_config::Config::FullClient(full_client__conf)) => { + Some(proto::avail_config::Config::FullClient(full_client_conf)) => { AvailConfig { bridge_api_url: required(&conf.bridge_api_url) .context("bridge_api_url")? .clone(), timeout: *required(&conf.timeout).context("timeout")? as usize, config: AvailClientConfig::FullClient(AvailDefaultConfig { - api_node_url: required(&full_client__conf.api_node_url) + api_node_url: required(&full_client_conf.api_node_url) .context("api_node_url")? .clone(), - app_id: *required(&full_client__conf.app_id).context("app_id")?, + app_id: *required(&full_client_conf.app_id).context("app_id")?, }), } } From f3b524aca10cce1cd2adee0f7cbd30a982c497e1 Mon Sep 17 00:00:00 2001 From: dimazhornyk Date: Wed, 16 Oct 2024 13:33:26 +0200 Subject: [PATCH 19/26] fix: linter --- core/node/da_clients/src/avail/client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 5efcb64befd..f59d276a37c 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -20,7 +20,7 @@ use crate::avail::sdk::{GasRelayClient, RawAvailClient}; #[derive(Debug, Clone)] enum AvailClientMode { - Default(RawAvailClient), + Default(Box), GasRelay(GasRelayClient), } @@ -122,7 +122,7 @@ impl AvailClient { Ok(Self { config, - sdk_client: Arc::new(AvailClientMode::Default(sdk_client)), + sdk_client: Arc::new(AvailClientMode::Default(Box::new(sdk_client))), api_client, }) } From ef6531e51dd5967acc3dae07ffe12702413b141a Mon Sep 17 00:00:00 2001 From: dimazhornyk Date: Thu, 17 Oct 2024 12:58:35 +0200 Subject: [PATCH 20/26] decrease polling interval --- core/node/da_clients/src/avail/sdk.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/node/da_clients/src/avail/sdk.rs b/core/node/da_clients/src/avail/sdk.rs index 896051e0f0d..9f9eb73ccf6 100644 --- a/core/node/da_clients/src/avail/sdk.rs +++ b/core/node/da_clients/src/avail/sdk.rs @@ -422,16 +422,20 @@ impl GasRelayClient { .send() .await .map_err(to_retriable_da_error)?; + let submit_response = submit_response .json::() .await .map_err(to_retriable_da_error)?; + let status_url = format!( "{}/user/get_submission_info?submission_id={}", self.api_url, submit_response.submission_id ); + let (block_hash, extrinsic_index) = loop { - tokio::time::sleep(tokio::time::Duration::from_secs(u64::try_from(41).unwrap())).await; // usually takes 40s to finalize + // usually takes around 40s to finalize, but polling every 5s is enough here + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; let status_response = self .api_client .get(&status_url) @@ -439,10 +443,12 @@ impl GasRelayClient { .send() .await .map_err(to_retriable_da_error)?; + let status_response = status_response .json::() .await .map_err(to_retriable_da_error)?; + if status_response.submission.block_hash.is_some() { break ( status_response.submission.block_hash.unwrap(), @@ -450,6 +456,7 @@ impl GasRelayClient { ); } }; + Ok((block_hash, extrinsic_index)) } } From fcf68ebc8fdcd52ad3072b3ff1cf6b1254b625a0 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Fri, 18 Oct 2024 22:29:54 +0530 Subject: [PATCH 21/26] fix(protobuf-da-client): remove redundant fields from match --- contracts | 2 +- core/lib/protobuf_config/src/da_client.rs | 40 ++++++++++------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/contracts b/contracts index 84d5e3716f6..6b1f483f93b 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 84d5e3716f645909e8144c7d50af9dd6dd9ded62 +Subproject commit 6b1f483f93baab3b1d44e8f4efaece71377c2d45 diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index ecf49886fd7..a3253b18118 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -17,36 +17,30 @@ impl ProtoRepr for proto::DataAvailabilityClient { let config = required(&self.config).context("config")?; let client = match config { - proto::data_availability_client::Config::Avail(conf) => { - Avail(match conf.config.as_ref() { + proto::data_availability_client::Config::Avail(conf) => Avail(AvailConfig { + bridge_api_url: required(&conf.bridge_api_url) + .context("bridge_api_url")? + .clone(), + timeout: *required(&conf.timeout).context("timeout")? as usize, + config: match conf.config.as_ref() { Some(proto::avail_config::Config::FullClient(full_client_conf)) => { - AvailConfig { - bridge_api_url: required(&conf.bridge_api_url) - .context("bridge_api_url")? + AvailClientConfig::FullClient(AvailDefaultConfig { + api_node_url: required(&full_client_conf.api_node_url) + .context("api_node_url")? .clone(), - timeout: *required(&conf.timeout).context("timeout")? as usize, - config: AvailClientConfig::FullClient(AvailDefaultConfig { - api_node_url: required(&full_client_conf.api_node_url) - .context("api_node_url")? - .clone(), - app_id: *required(&full_client_conf.app_id).context("app_id")?, - }), - } + app_id: *required(&full_client_conf.app_id).context("app_id")?, + }) } - Some(proto::avail_config::Config::GasRelay(gas_relay_conf)) => AvailConfig { - bridge_api_url: required(&conf.bridge_api_url) - .context("bridge_api_url")? - .clone(), - timeout: *required(&conf.timeout).context("timeout")? as usize, - config: AvailClientConfig::GasRelay(AvailGasRelayConfig { + Some(proto::avail_config::Config::GasRelay(gas_relay_conf)) => { + AvailClientConfig::GasRelay(AvailGasRelayConfig { gas_relay_api_url: required(&gas_relay_conf.gas_relay_api_url) .context("gas_relay_api_url")? .clone(), - }), - }, + }) + } None => return Err(anyhow::anyhow!("Invalid Avail DA configuration")), - }) - } + }, + }), proto::data_availability_client::Config::ObjectStore(conf) => { ObjectStore(object_store_proto::ObjectStore::read(conf)?) } From 9963ae2a6efceb324e70e62b888a5ac3ee2fd879 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Fri, 18 Oct 2024 23:33:40 +0530 Subject: [PATCH 22/26] fix(protobuf-secrets): make matching more logical --- core/lib/protobuf_config/src/secrets.rs | 30 ++++++++++--------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/core/lib/protobuf_config/src/secrets.rs b/core/lib/protobuf_config/src/secrets.rs index f7e1ac46c0e..07ab340c231 100644 --- a/core/lib/protobuf_config/src/secrets.rs +++ b/core/lib/protobuf_config/src/secrets.rs @@ -104,25 +104,19 @@ impl ProtoRepr for proto::DataAvailabilitySecrets { let client = match secrets { DaSecrets::Avail(avail_secret) => { - let seed_phrase = if avail_secret.seed_phrase.is_some() { - Some( - SeedPhrase::from_str( - required(&avail_secret.seed_phrase).context("seed_phrase")?, - ) - .unwrap(), - ) - } else { - None + let seed_phrase = match avail_secret.seed_phrase.as_ref() { + Some(seed) => match SeedPhrase::from_str(seed) { + Ok(seed) => Some(seed), + Err(_) => None, + }, + None => None, }; - let gas_relay_api_key = if avail_secret.gas_relay_api_key.is_some() { - Some( - APIKey::from_str( - required(&avail_secret.gas_relay_api_key).context("seed_phrase")?, - ) - .unwrap(), - ) - } else { - None + let gas_relay_api_key = match avail_secret.gas_relay_api_key.as_ref() { + Some(api_key) => match APIKey::from_str(api_key) { + Ok(api_key) => Some(api_key), + Err(_) => None, + }, + None => None, }; if seed_phrase.is_none() && gas_relay_api_key.is_none() { return Err(anyhow::anyhow!( From b9b2ea5b701593c0f12cfe4c2c93217df4cc43af Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Sat, 19 Oct 2024 00:51:10 +0530 Subject: [PATCH 23/26] fix(da_clients-avail-sdk): remove superfluous error mappings --- core/node/da_clients/src/avail/sdk.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/core/node/da_clients/src/avail/sdk.rs b/core/node/da_clients/src/avail/sdk.rs index 9f9eb73ccf6..4db70e17bb5 100644 --- a/core/node/da_clients/src/avail/sdk.rs +++ b/core/node/da_clients/src/avail/sdk.rs @@ -17,7 +17,7 @@ use subxt_signer::{ }; use zksync_types::H256; -use crate::avail::client::{to_non_retriable_da_error, to_retriable_da_error}; +use crate::avail::client::to_non_retriable_da_error; const PROTOCOL_VERSION: u8 = 4; @@ -420,13 +420,11 @@ impl GasRelayClient { .header("Content-Type", "text/plain") .header("Authorization", &self.api_key) .send() - .await - .map_err(to_retriable_da_error)?; + .await?; let submit_response = submit_response .json::() - .await - .map_err(to_retriable_da_error)?; + .await?; let status_url = format!( "{}/user/get_submission_info?submission_id={}", @@ -441,13 +439,9 @@ impl GasRelayClient { .get(&status_url) .header("Authorization", &self.api_key) .send() - .await - .map_err(to_retriable_da_error)?; + .await?; - let status_response = status_response - .json::() - .await - .map_err(to_retriable_da_error)?; + let status_response = status_response.json::().await?; if status_response.submission.block_hash.is_some() { break ( From cd50d1a02eac6673b635e77fce02ebcfe264b93f Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Mon, 21 Oct 2024 16:48:06 +0530 Subject: [PATCH 24/26] fix(avail-client,protobuf-da-client): add max_retries to gas relay, cap sleep to timeout for inclusion checks --- core/lib/config/src/configs/da_client/avail.rs | 1 + core/lib/env_config/src/da_client.rs | 1 - core/lib/protobuf_config/src/da_client.rs | 4 ++++ .../src/proto/config/da_client.proto | 1 + core/node/da_clients/src/avail/client.rs | 7 ++++++- core/node/da_clients/src/avail/sdk.rs | 13 +++++++++++-- 6 files changed, 23 insertions(+), 4 deletions(-) diff --git a/core/lib/config/src/configs/da_client/avail.rs b/core/lib/config/src/configs/da_client/avail.rs index d6ec4861b48..b8e9db0f393 100644 --- a/core/lib/config/src/configs/da_client/avail.rs +++ b/core/lib/config/src/configs/da_client/avail.rs @@ -28,6 +28,7 @@ pub struct AvailDefaultConfig { #[derive(Clone, Debug, PartialEq, Deserialize)] pub struct AvailGasRelayConfig { pub gas_relay_api_url: String, + pub max_retries: usize, } #[derive(Clone, Debug, PartialEq)] diff --git a/core/lib/env_config/src/da_client.rs b/core/lib/env_config/src/da_client.rs index bf1c3121f16..1043786fc1e 100644 --- a/core/lib/env_config/src/da_client.rs +++ b/core/lib/env_config/src/da_client.rs @@ -20,7 +20,6 @@ impl FromEnv for DAClientConfig { AVAIL_CLIENT_CONFIG_NAME => Self::Avail(AvailConfig { bridge_api_url: env::var("DA_BRIDGE_API_URL").ok().unwrap(), timeout: env::var("DA_TIMEOUT")?.parse()?, - config: match env::var("DA_AVAIL_CLIENT_TYPE")?.as_str() { AVAIL_FULL_CLIENT_NAME => { AvailClientConfig::FullClient(envy_load("da_avail_full_client", "DA_")?) diff --git a/core/lib/protobuf_config/src/da_client.rs b/core/lib/protobuf_config/src/da_client.rs index a3253b18118..a17a8711a27 100644 --- a/core/lib/protobuf_config/src/da_client.rs +++ b/core/lib/protobuf_config/src/da_client.rs @@ -36,6 +36,9 @@ impl ProtoRepr for proto::DataAvailabilityClient { gas_relay_api_url: required(&gas_relay_conf.gas_relay_api_url) .context("gas_relay_api_url")? .clone(), + max_retries: *required(&gas_relay_conf.max_retries) + .context("max_retries")? + as usize, }) } None => return Err(anyhow::anyhow!("Invalid Avail DA configuration")), @@ -66,6 +69,7 @@ impl ProtoRepr for proto::DataAvailabilityClient { AvailClientConfig::GasRelay(conf) => Some( proto::avail_config::Config::GasRelay(proto::AvailGasRelayConfig { gas_relay_api_url: Some(conf.gas_relay_api_url.clone()), + max_retries: Some(conf.max_retries as u64), }), ), }, diff --git a/core/lib/protobuf_config/src/proto/config/da_client.proto b/core/lib/protobuf_config/src/proto/config/da_client.proto index 1dadc22c268..73fa2435996 100644 --- a/core/lib/protobuf_config/src/proto/config/da_client.proto +++ b/core/lib/protobuf_config/src/proto/config/da_client.proto @@ -24,6 +24,7 @@ message AvailClientConfig { message AvailGasRelayConfig { optional string gas_relay_api_url = 1; + optional uint64 max_retries = 2; } message DataAvailabilityClient { diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index f59d276a37c..1af47cdca53 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -103,6 +103,7 @@ impl AvailClient { let gas_relay_client = GasRelayClient::new( &conf.gas_relay_api_url, gas_relay_api_key.0.expose_secret(), + conf.max_retries, Arc::clone(&api_client), ) .await?; @@ -208,7 +209,11 @@ impl DataAvailabilityClient for AvailClient { "Inclusion check timeout exceeded" ))); } - tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + tokio::time::sleep(tokio::time::Duration::from_secs(std::cmp::min( + self.config.timeout as u64, + 60, + ))) + .await; }; let attestation_data: MerkleProofInput = MerkleProofInput { diff --git a/core/node/da_clients/src/avail/sdk.rs b/core/node/da_clients/src/avail/sdk.rs index 4db70e17bb5..c4f1a16cb87 100644 --- a/core/node/da_clients/src/avail/sdk.rs +++ b/core/node/da_clients/src/avail/sdk.rs @@ -378,6 +378,7 @@ fn ss58hash(data: &[u8]) -> Vec { pub(crate) struct GasRelayClient { api_url: String, api_key: String, + max_retries: usize, api_client: Arc, } @@ -401,11 +402,13 @@ impl GasRelayClient { pub(crate) async fn new( api_url: &str, api_key: &str, + max_retries: usize, api_client: Arc, ) -> anyhow::Result { Ok(Self { api_url: api_url.to_owned(), api_key: api_key.to_owned(), + max_retries, api_client, }) } @@ -431,9 +434,11 @@ impl GasRelayClient { self.api_url, submit_response.submission_id ); + let mut retries: usize = 0; + let (block_hash, extrinsic_index) = loop { - // usually takes around 40s to finalize, but polling every 5s is enough here - tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + // minimum finalization time is 40s but can go upto 90s depending on network usage + tokio::time::sleep(tokio::time::Duration::from_secs(40)).await; let status_response = self .api_client .get(&status_url) @@ -449,6 +454,10 @@ impl GasRelayClient { status_response.submission.extrinsic_index.unwrap(), ); } + retries += 1; + if retries > self.max_retries { + anyhow::bail!("Gas relay submission max_retries exceeded"); + } }; Ok((block_hash, extrinsic_index)) From 2efd52765f8b71800343307d2055a075098ea044 Mon Sep 17 00:00:00 2001 From: qedk <1994constant@gmail.com> Date: Mon, 21 Oct 2024 16:56:26 +0530 Subject: [PATCH 25/26] chore(contracts): set to current commit --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 6b1f483f93b..84d5e3716f6 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 6b1f483f93baab3b1d44e8f4efaece71377c2d45 +Subproject commit 84d5e3716f645909e8144c7d50af9dd6dd9ded62 From 3ea1ed9a365977569cef2f88719d33f954b53e7d Mon Sep 17 00:00:00 2001 From: dimazhornyk Date: Wed, 23 Oct 2024 10:18:40 +0200 Subject: [PATCH 26/26] add backon --- Cargo.lock | 1 + core/node/da_clients/Cargo.toml | 1 + core/node/da_clients/src/avail/client.rs | 43 ++++++---------- core/node/da_clients/src/avail/sdk.rs | 62 ++++++++++++++++-------- 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4c1aa8817f..f1884764fb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10010,6 +10010,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "backon", "base58", "blake2 0.10.6", "blake2b_simd", diff --git a/core/node/da_clients/Cargo.toml b/core/node/da_clients/Cargo.toml index c2036371436..fa2f15920bd 100644 --- a/core/node/da_clients/Cargo.toml +++ b/core/node/da_clients/Cargo.toml @@ -39,3 +39,4 @@ parity-scale-codec = { workspace = true, features = ["derive"] } subxt-signer = { workspace = true, features = ["sr25519", "native"] } reqwest = { workspace = true } bytes = { workspace = true } +backon.workspace = true diff --git a/core/node/da_clients/src/avail/client.rs b/core/node/da_clients/src/avail/client.rs index 1af47cdca53..f858335134e 100644 --- a/core/node/da_clients/src/avail/client.rs +++ b/core/node/da_clients/src/avail/client.rs @@ -1,7 +1,8 @@ -use std::{fmt::Debug, sync::Arc}; +use std::{fmt::Debug, sync::Arc, time::Duration}; use anyhow::anyhow; use async_trait::async_trait; +use backon::{ConstantBuilder, Retryable}; use jsonrpsee::ws_client::WsClientBuilder; use serde::{Deserialize, Serialize}; use subxt_signer::ExposeSecret; @@ -188,33 +189,19 @@ impl DataAvailabilityClient for AvailClient { "{}/eth/proof/{}?index={}", self.config.bridge_api_url, block_hash, tx_idx ); - // record current time - let current_timestamp = std::time::Instant::now(); - let bridge_api_data = loop { - let response = self - .api_client - .get(&url) - .send() - .await - .map_err(to_retriable_da_error)?; - let bridge_api_data = response - .json::() - .await - .map_err(to_retriable_da_error)?; - if bridge_api_data.error.is_none() { - break bridge_api_data; - } - if current_timestamp.elapsed().as_secs() > self.config.timeout as u64 { - return Err(to_non_retriable_da_error(anyhow!( - "Inclusion check timeout exceeded" - ))); - } - tokio::time::sleep(tokio::time::Duration::from_secs(std::cmp::min( - self.config.timeout as u64, - 60, - ))) - .await; - }; + + let response = self + .api_client + .get(&url) + .timeout(Duration::from_secs(self.config.timeout as u64)) + .send() + .await + .map_err(to_retriable_da_error)?; + + let bridge_api_data = response + .json::() + .await + .map_err(to_retriable_da_error)?; let attestation_data: MerkleProofInput = MerkleProofInput { data_root_proof: bridge_api_data.data_root_proof.unwrap(), diff --git a/core/node/da_clients/src/avail/sdk.rs b/core/node/da_clients/src/avail/sdk.rs index c4f1a16cb87..bad85820a5a 100644 --- a/core/node/da_clients/src/avail/sdk.rs +++ b/core/node/da_clients/src/avail/sdk.rs @@ -1,8 +1,9 @@ //! Minimal reimplementation of the Avail SDK client required for the DA client implementation. //! This is considered to be a temporary solution until a mature SDK is available on crates.io -use std::{fmt::Debug, sync::Arc}; +use std::{fmt::Debug, sync::Arc, time}; +use backon::{ConstantBuilder, Retryable}; use bytes::Bytes; use jsonrpsee::{ core::client::{Client, ClientT, Subscription, SubscriptionClientT}, @@ -399,6 +400,8 @@ pub struct GasRelayAPISubmission { } impl GasRelayClient { + const DEFAULT_INCLUSION_DELAY: time::Duration = time::Duration::from_secs(60); + const RETRY_DELAY: time::Duration = time::Duration::from_secs(5); pub(crate) async fn new( api_url: &str, api_key: &str, @@ -434,31 +437,48 @@ impl GasRelayClient { self.api_url, submit_response.submission_id ); - let mut retries: usize = 0; - - let (block_hash, extrinsic_index) = loop { - // minimum finalization time is 40s but can go upto 90s depending on network usage - tokio::time::sleep(tokio::time::Duration::from_secs(40)).await; - let status_response = self - .api_client + tokio::time::sleep(Self::DEFAULT_INCLUSION_DELAY).await; + let status_response = (async || { + self.api_client .get(&status_url) .header("Authorization", &self.api_key) .send() - .await?; - - let status_response = status_response.json::().await?; - - if status_response.submission.block_hash.is_some() { - break ( - status_response.submission.block_hash.unwrap(), - status_response.submission.extrinsic_index.unwrap(), - ); + .await + }) + .retry( + &ConstantBuilder::default() + .with_delay(Self::RETRY_DELAY) + .with_max_times(self.max_retries), + ) + .when(|response| async { + if response.is_err() { + return true; } - retries += 1; - if retries > self.max_retries { - anyhow::bail!("Gas relay submission max_retries exceeded"); + let status_response = response.as_ref().unwrap(); + if status_response.status().is_success() { + let status_response = status_response.json::().await; + if status_response.is_ok() { + let status_response = status_response.unwrap(); + if status_response.submission.block_hash.is_some() + && status_response.submission.extrinsic_index.is_some() + { + return false; + } + } } - }; + true + }) + .await?; + + let status_response = status_response.json::().await?; + let (block_hash, extrinsic_index) = ( + status_response.submission.block_hash.ok_or_else(|| { + anyhow::anyhow!("Block hash not found in the response from the gas relay") + })?, + status_response.submission.extrinsic_index.ok_or_else(|| { + anyhow::anyhow!("Extrinsic index not found in the response from the gas relay") + })?, + ); Ok((block_hash, extrinsic_index)) }