Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish State Diff to Eth #7

Merged
merged 18 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
458 changes: 419 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 @@ -15,7 +15,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 @@ -33,7 +38,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"
6 changes: 5 additions & 1 deletion crates/da_clients/da-client-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,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>>) -> 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"
16 changes: 14 additions & 2 deletions crates/da_clients/ethereum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@ 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 }
color-eyre = { workspace = true }
da-client-interface = { workspace = true }
reqwest = { version = "0.11.24" }
reqwest = { version = "0.12.3" }
c-kzg = "1.0.0"
dotenv = "0.15"
starknet = { workspace = true }
url = { workspace = true }
utils = { workspace = true }
rstest = { workspace = true }
tokio = { 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"),
}
}
}
179 changes: 172 additions & 7 deletions crates/da_clients/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,203 @@
#![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, FixedBytes, U256};
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 reqwest::async_impl::client::Client;
use reqwest::Client;
use starknet::core::types::FieldElement;
use starknet::core::types::FromByteArrayError;
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,
}

#[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>>) -> 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);

// chain id should be an env variable or should be in config?
let tx = TxEip4844 {
chain_id: 17000, // Holesky 17000 sepolia 11155111
Mohiiit marked this conversation as resolved.
Show resolved Hide resolved
nonce: 1,
gas_limit: 30_000_000,
max_fee_per_gas: 10000000100, //estimation.max_fee_per_gas.to_string().parse()?,
max_priority_fee_per_gas: 200000010,
Mohiiit marked this conversation as resolved.
Show resolved Hide resolved
to: addr,
value: U256::from(0),
access_list: AccessList(vec![]),
blob_versioned_hashes: sidecar.versioned_hashes().collect(),
max_fee_per_blob_gas: 7300000_535,
Mohiiit marked this conversation as resolved.
Show resolved Hide resolved
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 max_blob_per_txn(&self) -> u64 {
6
}

async fn max_bytes_per_blob(&self) -> u64 {
131072
}
Mohiiit marked this conversation as resolved.
Show resolved Hide resolved
}

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 as usize] = [0; BYTES_PER_BLOB as usize];
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 rstest::rstest;
use std::fs::File;
use std::io::{self, BufRead};
use std::io::{BufReader, Read};
use tokio_test;

#[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() {
if let Ok(line) = line {
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