Skip to content

Commit

Permalink
Publish State Diff to Eth (keep-starknet-strange#7)
Browse files Browse the repository at this point in the history
* strucutre for sending blob added

* [test]: function defined

* added condition to fetch nonce when the state_diff len is not-zero

* updated the condition for nonce getting, to not get it for one contract address

* test added for fft function

* removed some unused comments

* updated test_state_update_to_blob_Data test

* deleted extra files

* issue resolved

* mocking added for state update to blob function

* estimation added for txn and chain id dynamically setting

* txn inclusion logic added and code cleanup

* error message updated for blob length error

* linting fix

* linting fix

* toml linting fixed

* added nonce and to address

---------

Co-authored-by: apoorvsadana <[email protected]>
  • Loading branch information
Mohiiit and apoorvsadana authored Jun 17, 2024
1 parent 6174f9d commit 4343a41
Show file tree
Hide file tree
Showing 27 changed files with 31,799 additions and 124 deletions.
460 changes: 421 additions & 39 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ authors = ["Apoorv Sadana <@apoorvsadana>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[workspace.dependencies]


num = { version = "0.4.1" }
ethereum-da-client = { path = "crates/da_clients/ethereum" }
async-trait = { version = "0.1.77" }
da-client-interface = { path = "crates/da_clients/da-client-interface" }
axum = { version = "0.7.4" }
axum-macros = { version = "0.4.1" }
color-eyre = { version = "0.6.2" }
Expand All @@ -34,7 +39,8 @@ tracing = { version = "0.1.40" }
tracing-subscriber = { version = "0.3.18" }
url = { version = "2.5.0" }
uuid = { version = "1.7.0" }
num-bigint = { version = "0.4.4" }
httpmock = { version = "0.7.0" }
da-client-interface = { path = "crates/da_clients/da-client-interface" }
ethereum-da-client = { path = "crates/da_clients/ethereum" }
utils = { path = "crates/utils" }
num-traits = "0.2"
lazy_static = "1.4.0"
7 changes: 5 additions & 2 deletions crates/da_clients/da-client-interface/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use async_trait::async_trait;
use color_eyre::Result;
use mockall::{automock, predicate::*};
use starknet::core::types::FieldElement;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DaVerificationStatus {
Expand All @@ -19,9 +18,13 @@ pub enum DaVerificationStatus {
pub trait DaClient: Send + Sync {
/// Should publish the state diff to the DA layer and return an external id
/// which can be used to track the status of the DA transaction.
async fn publish_state_diff(&self, state_diff: Vec<FieldElement>) -> Result<String>;
async fn publish_state_diff(&self, state_diff: Vec<Vec<u8>>, to: &[u8; 32]) -> Result<String>;
/// Should verify the inclusion of the state diff in the DA layer and return the status
async fn verify_inclusion(&self, external_id: &str) -> Result<DaVerificationStatus>;
/// Should return the max blobs per txn
async fn max_blob_per_txn(&self) -> u64;
/// Should return the max bytes per blob
async fn max_bytes_per_blob(&self) -> u64;
}

/// Trait for every new DaConfig to implement
Expand Down
2 changes: 2 additions & 0 deletions crates/da_clients/ethereum/.env_example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PK="private_key_here"
ETHEREUM_RPC_URL="https://ethereum-holesky-rpc.publicnode.com"
18 changes: 16 additions & 2 deletions crates/da_clients/ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,29 @@ version.workspace = true
edition.workspace = true

[dependencies]
alloy = { git = "https://github.com/alloy-rs/alloy", rev = "86027c9bb984f3a12a30ffd2a3c5f2f06595f1d6", features = [
alloy = { git = "https://github.com/alloy-rs/alloy", rev = "68952c0", features = [
"consensus",
"providers",
"rpc-client",
"transport-http",
"network",
"eips",
"signers",
"signer-wallet",
] }
async-trait = { workspace = true }
c-kzg = "1.0.0"
color-eyre = { workspace = true }
da-client-interface = { workspace = true }
reqwest = { version = "0.11.24" }
dotenv = "0.15"
mockall = "0.12.1"
reqwest = { version = "0.12.3" }
rstest = { workspace = true }
serde = { version = "1.0.196", default-features = false, features = ["derive"] }
starknet = { workspace = true }
tokio = { workspace = true }
url = { workspace = true }
utils = { workspace = true }

[dev-dependencies]
tokio-test = "*"
2 changes: 2 additions & 0 deletions crates/da_clients/ethereum/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ use utils::env_utils::get_env_var_or_panic;
pub struct EthereumDaConfig {
pub rpc_url: String,
pub memory_pages_contract: String,
pub private_key: String,
}

impl DaConfig for EthereumDaConfig {
fn new_from_env() -> Self {
Self {
rpc_url: get_env_var_or_panic("ETHEREUM_RPC_URL"),
memory_pages_contract: get_env_var_or_panic("MEMORY_PAGES_CONTRACT_ADDRESS"),
private_key: get_env_var_or_panic("PRIVATE_KEY"),
}
}
}
197 changes: 187 additions & 10 deletions crates/da_clients/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,215 @@
#![allow(missing_docs)]
#![allow(clippy::missing_docs_in_private_items)]
use alloy::consensus::{
BlobTransactionSidecar, SignableTransaction, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope,
};
use alloy::eips::{eip2718::Encodable2718, eip2930::AccessList, eip4844::BYTES_PER_BLOB};
use alloy::network::{Ethereum, TxSigner};
use alloy::primitives::{bytes, Address, FixedBytes, TxHash, U256, U64};
use alloy::providers::{Provider, ProviderBuilder, RootProvider};
use alloy::rpc::client::RpcClient;
use alloy::signers::wallet::LocalWallet;
use alloy::transports::http::Http;
use async_trait::async_trait;

use color_eyre::Result;
use mockall::{automock, predicate::*};
use reqwest::Client;
use starknet::core::types::FieldElement;
use std::str::FromStr;
use url::Url;

use c_kzg::{Blob, KzgCommitment, KzgProof, KzgSettings};
use config::EthereumDaConfig;
use da_client_interface::{DaClient, DaVerificationStatus};

use dotenv::dotenv;
use std::{env, path::Path};
pub mod config;
pub struct EthereumDaClient {
#[allow(dead_code)]
provider: RpcClient<Http<Client>>,
provider: RootProvider<Ethereum, Http<Client>>,
wallet: LocalWallet,
trusted_setup: KzgSettings,
}

#[automock]
#[async_trait]
impl DaClient for EthereumDaClient {
async fn publish_state_diff(&self, _state_diff: Vec<FieldElement>) -> Result<String> {
unimplemented!()
async fn publish_state_diff(&self, state_diff: Vec<Vec<u8>>, to: &[u8; 32]) -> Result<String> {
dotenv().ok();
let provider = &self.provider;
let trusted_setup = &self.trusted_setup;
let wallet = &self.wallet;
let addr = wallet.address();

let (sidecar_blobs, sidecar_commitments, sidecar_proofs) = prepare_sidecar(&state_diff, trusted_setup).await?;
let sidecar = BlobTransactionSidecar::new(sidecar_blobs, sidecar_commitments, sidecar_proofs);

let eip1559_est = provider.estimate_eip1559_fees(None).await?;
let chain_id: u64 = provider.get_chain_id().await?.to_string().parse()?;

let max_fee_per_blob_gas: u128 = provider.get_blob_base_fee().await?.to_string().parse()?;
let max_priority_fee_per_gas: u128 = provider.get_max_priority_fee_per_gas().await?.to_string().parse()?;

let nonce = provider.get_transaction_count(addr, None).await?.to_string().parse()?;
let to = FixedBytes(*to);

let tx = TxEip4844 {
chain_id,
nonce,
gas_limit: 30_000_000,
max_fee_per_gas: eip1559_est.max_fee_per_gas.to_string().parse()?,
max_priority_fee_per_gas,
to: Address::from_word(to),
value: U256::from(0),
access_list: AccessList(vec![]),
blob_versioned_hashes: sidecar.versioned_hashes().collect(),
max_fee_per_blob_gas,
input: bytes!(),
};
let tx_sidecar = TxEip4844WithSidecar { tx: tx.clone(), sidecar: sidecar.clone() };
let mut variant = TxEip4844Variant::from(tx_sidecar);

// Sign and submit
let signature = wallet.sign_transaction(&mut variant).await?;
let tx_signed = variant.into_signed(signature);
let tx_envelope: TxEnvelope = tx_signed.into();
let encoded = tx_envelope.encoded_2718();

let pending_tx = provider.send_raw_transaction(&encoded).await?;

Ok(pending_tx.tx_hash().to_string())
}

async fn verify_inclusion(&self, _external_id: &str) -> Result<DaVerificationStatus> {
todo!()
async fn verify_inclusion(&self, external_id: &str) -> Result<DaVerificationStatus> {
let provider = &self.provider;
let tx_hash: TxHash = external_id.parse().unwrap();
let txn_response = provider.get_transaction_receipt(tx_hash).await?;

match txn_response {
None => Ok(DaVerificationStatus::Pending),
Some(receipt) => match receipt.status_code {
Some(status) if status == U64::from(1) => Ok(DaVerificationStatus::Verified),
_ => Ok(DaVerificationStatus::Rejected),
},
}
}

async fn max_blob_per_txn(&self) -> u64 {
6
}

async fn max_bytes_per_blob(&self) -> u64 {
131072
}
}

impl From<EthereumDaConfig> for EthereumDaClient {
fn from(config: EthereumDaConfig) -> Self {
let provider = RpcClient::builder()
.reqwest_http(Url::from_str(config.rpc_url.as_str()).expect("Failed to parse ETHEREUM_RPC_URL"));
EthereumDaClient { provider }
let client =
RpcClient::new_http(Url::from_str(config.rpc_url.as_str()).expect("Failed to parse ETHEREUM_RPC_URL"));
let provider = ProviderBuilder::<_, Ethereum>::new().on_client(client);
let wallet: LocalWallet = env::var("PK").expect("PK must be set").parse().expect("issue while parsing");
// let wallet: LocalWallet = config.private_key.as_str().parse();
let trusted_setup = KzgSettings::load_trusted_setup_file(Path::new("./trusted_setup.txt"))
.expect("issue while loading the trusted setup");
EthereumDaClient { provider, wallet, trusted_setup }
}
}

async fn prepare_sidecar(
state_diff: &[Vec<u8>],
trusted_setup: &KzgSettings,
) -> Result<(Vec<FixedBytes<131072>>, Vec<FixedBytes<48>>, Vec<FixedBytes<48>>)> {
let mut sidecar_blobs = vec![];
let mut sidecar_commitments = vec![];
let mut sidecar_proofs = vec![];

for blob_data in state_diff {
let mut fixed_size_blob: [u8; BYTES_PER_BLOB] = [0; BYTES_PER_BLOB];
fixed_size_blob.copy_from_slice(blob_data.as_slice());

let blob = Blob::new(fixed_size_blob);

let commitment = KzgCommitment::blob_to_kzg_commitment(&blob, trusted_setup)?;
let proof = KzgProof::compute_blob_kzg_proof(&blob, &commitment.to_bytes(), trusted_setup)?;

sidecar_blobs.push(FixedBytes::new(fixed_size_blob));
sidecar_commitments.push(FixedBytes::new(commitment.to_bytes().into_inner()));
sidecar_proofs.push(FixedBytes::new(proof.to_bytes().into_inner()));
}

Ok((sidecar_blobs, sidecar_commitments, sidecar_proofs))
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::{self, BufRead};

#[tokio::test]
async fn test_kzg() {
let trusted_setup = KzgSettings::load_trusted_setup_file(Path::new("./trusted_setup.txt"))
.expect("Error loading trusted setup file");

// hex of the blob data from the block 630872 of L2
// https://voyager.online/block/0x3333f2f6b32776ac031e7ed373858c656d6d1040e47b73c94e762e6ed4cedf3 (L2)
// https://etherscan.io/tx/0x6b9fc547764a5d6e4451b5236b92e74c70800250f00fc1974fc0a75a459dc12e (L1)
let file_path = "./test_utils/hex_block_630872.txt";

// open the file and store the data as a single string
let file = File::open(file_path).expect("Unable to load the file for hex");
let reader = io::BufReader::new(file);
let mut data = String::new();
for line in reader.lines().map_while(Result::ok) {
data.push_str(&line);
}

// create vec<u8> from the hex string
let data_v8 = hex_string_to_u8_vec(&data).expect("error creating hex string from data");

// creation of sidecar
let (_sidecar_blobs, sidecar_commitments, sidecar_proofs) =
prepare_sidecar(&[data_v8], &trusted_setup).await.expect("Error creating the sidecar blobs");

// blob commitment from L1
let commitment_vector = hex_string_to_u8_vec(
"adece1d251a1671e134d57204ef111308818dacf97d2372b28b53f947682de715fd0a75f57496124ec97609a52e8ca52",
)
.expect("Error creating the vector of u8 from commitment");
let commitment_fixedbytes: FixedBytes<48> = FixedBytes::from_slice(&commitment_vector);

// blob proof from L1
let proof_vector = hex_string_to_u8_vec(
"999371598a3807abe20956a5754f9894f2d8fe2a0f8fd49bb13f294282121be1118627f2f9fe4e2ea0b9760addd41a0c",
)
.expect("Error creating the vector of u8 from proof");
let proog_fixedbytes: FixedBytes<48> = FixedBytes::from_slice(&proof_vector);

// blob commitment and proof should be equal to the blob created by prepare_sidecar
assert_eq!(sidecar_commitments[0], commitment_fixedbytes);
assert_eq!(sidecar_proofs[0], proog_fixedbytes);
}

fn hex_string_to_u8_vec(hex_str: &str) -> Result<Vec<u8>, String> {
// Remove any spaces or non-hex characters from the input string
let cleaned_str: String = hex_str.chars().filter(|c| c.is_ascii_hexdigit()).collect();

// Convert the cleaned hex string to a Vec<u8>
let mut result = Vec::new();
for chunk in cleaned_str.as_bytes().chunks(2) {
if let Ok(byte_val) = u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16) {
result.push(byte_val);
} else {
return Err(format!("Error parsing hex string: {}", cleaned_str));
}
}
println!("length of vec<u8>: {}", result.len());
Ok(result)
}

fn _vec_u8_to_hex_string(data: &[u8]) -> String {
let hex_chars: Vec<String> = data.iter().map(|byte| format!("{:02X}", byte)).collect();
hex_chars.join("")
}
}
1 change: 1 addition & 0 deletions crates/da_clients/ethereum/test_utils/hex_block_630872.txt

Large diffs are not rendered by default.

Loading

0 comments on commit 4343a41

Please sign in to comment.