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 13 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
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 @@ -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"
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>>) -> 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 }
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"
mockall = "0.12.1"
serde = { version = "1.0.196", default-features = false, features = ["derive"] }
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"),
}
}
}
196 changes: 186 additions & 10 deletions crates/da_clients/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,214 @@
#![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, 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>>) -> 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 tx = TxEip4844 {
chain_id,
nonce: 0, // can be block number
Copy link
Contributor

@unstark unstark Jun 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm not missing something, it won't be set right?
There should be NonceFiller in the alloy provider chain to handle that, couldn't find it explicitly set.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya makes sense, @Mohiiit let's fetch the nonce from the RPC itself

gas_limit: 30_000_000,
max_fee_per_gas: eip1559_est.max_fee_per_gas.to_string().parse()?,
max_priority_fee_per_gas,
to: addr, // maybe to the L1 contract for verification??
Copy link
Contributor

@unstark unstark Jun 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to, input, and gas_limit should probably be function arguments, if the goal is to reuse Starknet core contract, that would be its address, updateStateKzgDA calldata, and appropriate gas_limit to account for the spendings

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right. i think we should fill in to and gas_limit for now. input can be done in a refactor later on when we integrate updateState

@Mohiiit we can ideally take to as an input and gas_limit can be estimated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @unstark, I have set the nonce by fetching it directly and used the to address as an input. Created an issue for the gas limit for now :)

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 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 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() {
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