Skip to content

Commit

Permalink
feat : added update state call with blob data
Browse files Browse the repository at this point in the history
  • Loading branch information
ocdbytes committed Jul 13, 2024
1 parent 6cbe8e1 commit bed503f
Show file tree
Hide file tree
Showing 10 changed files with 11,753 additions and 23 deletions.
6 changes: 4 additions & 2 deletions crates/orchestrator/src/jobs/da_job/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ impl Job for DaJob {
));
}

// IMP : No longer needed as the blob will be published in the transaction in the state_update_job
// making the txn to the DA layer
// TODO: move the core contract address to the config
let external_id = config.da_client().publish_state_diff(blob_array, &[0; 32]).await?;
// let external_id = config.da_client().publish_state_diff(blob_array, &[0; 32]).await?;
// Ok(external_id)

Ok(external_id)
Ok("da_job_process_done".to_string())
}

async fn verify_job(&self, config: &Config, job: &mut JobItem) -> Result<JobVerificationStatus> {
Expand Down
12 changes: 8 additions & 4 deletions crates/orchestrator/src/jobs/state_update_job/kzg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use alloy::eips::eip4844::BYTES_PER_BLOB;
use c_kzg::{Blob, Bytes32, KzgCommitment, KzgProof, KzgSettings};

/// Build KZG proof for a given block
pub async fn build_kzg_proof(block_number: u64, fetch_from_tests: Option<bool>) -> color_eyre::Result<KzgProof> {
pub async fn build_kzg_proof(
block_number: u64,
fetch_from_tests: Option<bool>,
) -> color_eyre::Result<(Vec<u8>, KzgProof)> {
let blob_data = fetch_blob_data_for_block(block_number, fetch_from_tests).await?;
let mut fixed_size_blob: [u8; BYTES_PER_BLOB] = [0; BYTES_PER_BLOB];
fixed_size_blob.copy_from_slice(blob_data.as_slice());
Expand All @@ -32,7 +35,7 @@ pub async fn build_kzg_proof(block_number: u64, fetch_from_tests: Option<bool>)
)?;
assert!(eval);

Ok(kzg_proof)
Ok((blob_data, kzg_proof))
}

/// Fetching the blob data (stored in s3 during DA job) for a particular block
Expand Down Expand Up @@ -109,12 +112,13 @@ mod tests {
async fn test_build_kzg_proof(#[case] block_number: u64) {
// testing the data in transaction :
// https://etherscan.io/tx/0x6b9fc547764a5d6e4451b5236b92e74c70800250f00fc1974fc0a75a459dc12e
let kzg_proof = build_kzg_proof(block_number, Some(true)).await.unwrap().to_bytes();
let (_, kzg_proof) = build_kzg_proof(block_number, Some(true)).await.unwrap();
let proof = kzg_proof.to_bytes();
let original_proof_from_l1 = Bytes48::from_hex(
"a168b317e7c44691ee1932bd12fc6ac22182277e8fc5cd4cd21adc0831c33b1359aa5171bba529c69dcfe6224b220f8f",
)
.unwrap();

assert_eq!(kzg_proof, original_proof_from_l1);
assert_eq!(proof, original_proof_from_l1);
}
}
14 changes: 9 additions & 5 deletions crates/orchestrator/src/jobs/state_update_job/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod kzg;
pub mod kzg;

use std::collections::HashMap;
use std::path::PathBuf;
Expand Down Expand Up @@ -229,8 +229,14 @@ impl StateUpdateJob {
let onchain_data_size = 0;
settlement_client.update_state_calldata(program_output, onchain_data_hash, onchain_data_size).await?
} else if snos.use_kzg_da == Felt252::ONE {
let kzg_proof = build_kzg_proof(block_no, fetch_from_tests).await?.to_owned();
settlement_client.update_state_blobs(vec![], kzg_proof).await?
let (blob_data, kzg_proof) = build_kzg_proof(block_no, fetch_from_tests).await?;

// Temp solution for formatting to blob
// TODO : Need to implement solution to handle multiple vectors (multiple data) as It will be updated in the upcoming starknet update.
let mut blob_data_vec: Vec<Vec<u8>> = Vec::new();
blob_data_vec.push(blob_data);

settlement_client.update_state_blobs_and_blob(vec![], kzg_proof.to_owned(), blob_data_vec).await?
} else {
return Err(eyre!("Block #{} - SNOS error, [use_kzg_da] should be either 0 or 1.", block_no));
};
Expand Down Expand Up @@ -275,5 +281,3 @@ impl StateUpdateJob {
job.metadata.insert(new_attempt_metadata_key, tx_hashes.join(","));
}
}

// publish state diff -> empty -> eth client : add in state update
5 changes: 3 additions & 2 deletions crates/orchestrator/src/tests/jobs/da_job/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ async fn test_process_job() {
let mut da_client = MockDaClient::new();
let internal_id = "1";

da_client.expect_publish_state_diff().times(1).returning(|_, _| Ok(internal_id.to_string()));
// No longer needed step is eliminated for ethereum
// da_client.expect_publish_state_diff().times(1).returning(|_, _| Ok(internal_id.to_string()));
da_client.expect_max_bytes_per_blob().times(1).returning(move || ETHEREUM_MAX_BYTES_PER_BLOB);
da_client.expect_max_blob_per_txn().times(1).returning(move || ETHEREUM_MAX_BLOB_PER_TXN);

Expand Down Expand Up @@ -91,7 +92,7 @@ async fn test_process_job() {
)
.await
.unwrap(),
internal_id
"da_job_process_done"
);

state_update_mock.assert();
Expand Down
15 changes: 12 additions & 3 deletions crates/orchestrator/src/tests/jobs/state_update_job/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::jobs::{
Job,
};

use crate::jobs::state_update_job::kzg::build_kzg_proof;
use httpmock::prelude::*;

#[rstest]
Expand Down Expand Up @@ -51,11 +52,12 @@ async fn test_process_job() {
let program_output: Vec<[u8; 32]> = vec![];
let block_proof: Vec<u8> = load_kzg_proof(block_no);
let block_proof: [u8; 48] = block_proof.try_into().expect("test proof should be 48 bytes");
let state_diff: Vec<Vec<u8>> = load_state_diff_file(block_no.parse::<u64>().unwrap()).await;
settlement_client
.expect_update_state_blobs()
.expect_update_state_blobs_and_blob()
// TODO: vec![] is program_output
.with(eq(program_output), eq(block_proof))
.returning(|_, _| Ok(String::from("0x5d17fac98d9454030426606019364f6e68d915b91f6210ef1e2628cd6987442")));
.with(eq(program_output), eq(block_proof), eq(state_diff))
.returning(|_, _, _| Ok(String::from("0x5d17fac98d9454030426606019364f6e68d915b91f6210ef1e2628cd6987442")));
}

let config = init_config(
Expand Down Expand Up @@ -149,3 +151,10 @@ fn load_kzg_proof(block_no: &str) -> Vec<u8> {
let proof_str = fs::read_to_string(file_path).expect("Unable to read kzg_proof.txt").replace("0x", "");
hex::decode(proof_str).unwrap()
}

async fn load_state_diff_file(block_no: u64) -> Vec<Vec<u8>> {
let mut state_diff_vec: Vec<Vec<u8>> = Vec::new();
let (blob_data, _kzg_proof) = build_kzg_proof(block_no, Some(true)).await.unwrap();
state_diff_vec.push(blob_data);
state_diff_vec
}
3 changes: 3 additions & 0 deletions crates/settlement-clients/ethereum/src/clients/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ impl StarknetValidityContractClient {
pub fn new(address: Address, client: Arc<LocalWalletSignerMiddleware>) -> Self {
Self { core_contract: StarknetValidityContract::new(address, client.clone()) }
}
pub fn contract_address(&self) -> Address {
*self.core_contract.address()
}
}

impl
Expand Down
174 changes: 167 additions & 7 deletions crates/settlement-clients/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ pub mod config;
pub mod conversion;
pub mod types;

use std::{str::FromStr, sync::Arc};

use alloy::consensus::{
BlobTransactionSidecar, SignableTransaction, TxEip4844, TxEip4844Variant, TxEip4844WithSidecar, TxEnvelope,
};
use alloy::eips::eip2718::Encodable2718;
use alloy::eips::eip2930::AccessList;
use alloy::eips::eip4844::BYTES_PER_BLOB;
use alloy::primitives::{Bytes, FixedBytes};
use alloy::{
network::EthereumWallet,
primitives::{Address, B256, U256},
Expand All @@ -13,13 +18,18 @@ use alloy::{
signers::local::PrivateKeySigner,
};
use async_trait::async_trait;
use c_kzg::{Blob, KzgCommitment, KzgProof, KzgSettings};
use color_eyre::Result;
use mockall::{automock, predicate::*};
use rstest::rstest;
use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;

use crate::clients::interfaces::validity_interface::StarknetValidityContractTrait;
use settlement_client_interface::{SettlementClient, SettlementVerificationStatus, SETTLEMENT_SETTINGS_NAME};
use utils::{env_utils::get_env_var_or_panic, settings::SettingsProvider};

use crate::clients::interfaces::validity_interface::StarknetValidityContractTrait;
use crate::clients::StarknetValidityContractClient;
use crate::config::EthereumSettlementConfig;
use crate::conversion::{slice_slice_u8_to_vec_u256, slice_u8_to_u256};
Expand All @@ -31,6 +41,8 @@ pub const ENV_PRIVATE_KEY: &str = "ETHEREUM_PRIVATE_KEY";
pub struct EthereumSettlementClient {
provider: Arc<EthHttpProvider>,
core_contract_client: StarknetValidityContractClient,
wallet: EthereumWallet,
wallet_address: Address,
}

impl EthereumSettlementClient {
Expand All @@ -39,16 +51,19 @@ impl EthereumSettlementClient {

let private_key = get_env_var_or_panic(ENV_PRIVATE_KEY);
let signer: PrivateKeySigner = private_key.parse().expect("Failed to parse private key");
let wallet = EthereumWallet::from(signer);
let wallet = EthereumWallet::from(signer.clone());

let wallet_address = signer.address();

let provider =
Arc::new(ProviderBuilder::new().with_recommended_fillers().wallet(wallet).on_http(settlement_cfg.rpc_url));
let provider = Arc::new(
ProviderBuilder::new().with_recommended_fillers().wallet(wallet.clone()).on_http(settlement_cfg.rpc_url),
);
let core_contract_client = StarknetValidityContractClient::new(
Address::from_slice(settlement_cfg.core_contract_address.as_bytes()).0.into(),
provider.clone(),
);

EthereumSettlementClient { provider, core_contract_client }
EthereumSettlementClient { provider, core_contract_client, wallet, wallet_address }
}
}

Expand Down Expand Up @@ -84,6 +99,52 @@ impl SettlementClient for EthereumSettlementClient {
Ok(format!("0x{:x}", tx_receipt.transaction_hash))
}

async fn update_state_blobs_and_blob(
&self,
program_output: Vec<[u8; 32]>,
kzg_proof: [u8; 48],
state_diff: Vec<Vec<u8>>,
) -> Result<String> {
let trusted_setup = KzgSettings::load_trusted_setup_file(Path::new("./trusted_setup.txt"))
.expect("issue while loading the trusted setup");
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 = self.provider.estimate_eip1559_fees(None).await?;
let chain_id: u64 = self.provider.get_chain_id().await?.to_string().parse()?;

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

let nonce = self.provider.get_transaction_count(self.wallet_address).await?.to_string().parse()?;

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: self.core_contract_client.contract_address(),
value: U256::from(0),
access_list: AccessList(vec![]),
blob_versioned_hashes: sidecar.versioned_hashes().collect(),
max_fee_per_blob_gas,
input: get_txn_input_bytes(program_output, kzg_proof),
};
let tx_sidecar = TxEip4844WithSidecar { tx: tx.clone(), sidecar: sidecar.clone() };
let mut variant = TxEip4844Variant::from(tx_sidecar);

// Sign and submit
let signature = self.wallet.default_signer().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 = self.provider.send_raw_transaction(&encoded).await?;

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

/// Should verify the inclusion of a tx in the settlement layer
async fn verify_tx_inclusion(&self, tx_hash: &str) -> Result<SettlementVerificationStatus> {
let tx_hash = B256::from_str(tx_hash)?;
Expand Down Expand Up @@ -113,3 +174,102 @@ impl SettlementClient for EthereumSettlementClient {
Ok(block_number.try_into()?)
}
}

/// To prepare the sidecar for EIP 4844 transaction
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))
}

/// Function to construct the transaction for updating the state in core contract.
fn get_txn_input_bytes(program_output: Vec<[u8; 32]>, kzg_proof: [u8; 48]) -> Bytes {
let program_output_hex_string = vec_u8_32_to_hex_string(program_output);
let kzg_proof_hex_string = u8_48_to_hex_string(kzg_proof);
// cast keccak "updateStateKzgDA(uint256[] calldata programOutput, bytes calldata kzgProof)" | cut -b 1-10
let function_selector = "0x1a790556";

Bytes::from(program_output_hex_string + &kzg_proof_hex_string + function_selector)
}

#[allow(clippy::format_collect)]
fn vec_u8_32_to_hex_string(data: Vec<[u8; 32]>) -> String {
data.into_iter()
.map(|arr| {
// Convert the array to a hex string
let hex = arr.iter().map(|byte| format!("{:02x}", byte)).collect::<String>();

// Ensure the hex string is exactly 64 characters (32 bytes)
format!("{:0>64}", hex)
})
.collect()
}

fn u8_48_to_hex_string(data: [u8; 48]) -> String {
// Split the array into two parts
let (first_32, last_16) = data.split_at(32);

// Convert and pad each part
let first_hex = to_padded_hex(first_32);
let second_hex = to_padded_hex(last_16);

// Concatenate the two hex strings
first_hex + &second_hex
}

#[allow(clippy::format_collect)]
// Function to convert a slice of u8 to a padded hex string
fn to_padded_hex(slice: &[u8]) -> String {
let hex = slice.iter().map(|byte| format!("{:02x}", byte)).collect::<String>();
format!("{:0<64}", hex)
}

#[rstest]
fn test_data_conversion() {
let data: [u8; 48] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
];

let result = u8_48_to_hex_string(data);

assert_eq!(result, "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3000000000000000000000000000000000");

let mut data_2: [u8; 32] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32,
];
let mut data_vec: Vec<[u8; 32]> = Vec::new();
data_vec.push(data_2);
data_2.reverse();
data_vec.push(data_2);

let data_3: [u8; 32] = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
0, 0,
];
data_vec.push(data_3);

let result_2 = vec_u8_32_to_hex_string(data_vec);

assert_eq!(result_2, "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a0908070605040302010102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e0000");
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ pub trait SettlementClient: Send + Sync {
onchain_data_size: usize,
) -> Result<String>;

/// Should be used to update state on contract and publish the blob on ethereum.
async fn update_state_blobs_and_blob(
&self,
program_output: Vec<[u8; 32]>,
kzg_proof: [u8; 48],
state_diff: Vec<Vec<u8>>,
) -> Result<String>;

/// Should be used to update state on core contract when DA is in blobs/alt DA
async fn update_state_blobs(&self, program_output: Vec<[u8; 32]>, kzg_proof: [u8; 48]) -> Result<String>;

Expand Down
Loading

0 comments on commit bed503f

Please sign in to comment.