Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/client/data-availability/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ blockifier = { workspace = true, default-features = true }
mc-commitment-state-diff = { workspace = true, default-features = true }
mc-db = { workspace = true, default-features = true }
pallet-starknet-runtime-api = { workspace = true, features = ["std"] }
starknet-accounts = { workspace = true, default-features = true }
starknet_api = { workspace = true, default-features = true }
starknet-core = { workspace = true, default-features = true }
starknet-ff = { workspace = true, default-features = true }
starknet-providers = { workspace = true, default-features = true }
starknet-signers = { workspace = true, default-features = true }

# Ethereum
ethers = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/client/data-availability/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod avail;
pub mod celestia;
pub mod ethereum;
mod sharp;
pub mod starknet;
pub mod utils;

mod da_metrics;
Expand Down Expand Up @@ -41,6 +42,7 @@ pub enum DaLayer {
Ethereum,
#[cfg(feature = "avail")]
Avail,
Starknet,
}

/// Data availability modes in which Madara can be initialized.
Expand Down
26 changes: 26 additions & 0 deletions crates/client/data-availability/src/starknet/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use std::fs::File;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};

use crate::DaMode;

#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub struct StarknetConfig {
pub http_provider: String,
pub core_contracts: String,
pub sequencer_key: String,
pub account_address: String,
pub chain_id: String,
pub mode: DaMode,
pub poll_interval_ms: Option<u64>,
}

impl TryFrom<&PathBuf> for StarknetConfig {
type Error = String;

fn try_from(path: &PathBuf) -> Result<Self, Self::Error> {
let file = File::open(path).map_err(|e| format!("error opening da config: {e}"))?;
serde_json::from_reader(file).map_err(|e| format!("error parsing da config: {e}"))
}
}
154 changes: 154 additions & 0 deletions crates/client/data-availability/src/starknet/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
pub mod config;

use std::collections::HashMap;
use std::ops::Add;

use anyhow::{anyhow, Result};
use async_trait::async_trait;
use ethers::types::{I256, U256};
use reqwest::Url;
use starknet_accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount};
use starknet_core::types::{BlockId, BlockTag, FunctionCall};
use starknet_core::utils::get_selector_from_name;
use starknet_ff::FieldElement;
use starknet_providers::jsonrpc::HttpTransport;
use starknet_providers::{JsonRpcClient, Provider};
use starknet_signers::{LocalWallet, SigningKey};

use crate::{DaClient, DaMode};

#[derive(Debug)]
pub struct StarknetClient {
provider: JsonRpcClient<HttpTransport>,
da_contract: FieldElement,
sequencer_account: SingleOwnerAccount<JsonRpcClient<HttpTransport>, LocalWallet>,
mode: DaMode,
}

#[async_trait]
impl DaClient for StarknetClient {
fn get_mode(&self) -> DaMode {
self.mode
}

async fn last_published_state(&self) -> Result<I256> {
let last_state = self
.provider
.call(
FunctionCall {
contract_address: self.da_contract,
entry_point_selector: get_selector_from_name("LastState")?,
calldata: vec![],
},
BlockId::Tag(BlockTag::Latest),
)
.await
.map_err(|e| anyhow!("call contract has error: {:?}", e))?;
if last_state.len() != 6 {
return Ok(I256::zero());
}

// In The current DA, the `last_published_state` ABI is as follows:
// ```LastState(self: @ContractState) -> (u256, u256, u256)```.
// The second parameter in the ABI represents the last published
// block number. Since in the Cairo contract, a `u256` is composed of two `u128` values, we
// extract the third element from the returned data as the last published block number.
last_state[2]
.try_into()
.map(|n: u128| I256::from(n))
.map_err(|_e| anyhow!("last published block number exceed i256 max"))
}

async fn publish_state_diff(&self, state_diff: Vec<U256>) -> Result<()> {
let state_diff_len = state_diff.len();
if state_diff_len < 3 {
return Err(anyhow!("invalid state diff"));
}

// In the current contract, the ABI for publishing the state diff is defined as follows:
// ```cairo
// fn UpdateState(
// ref self: ContractState,
// state_diff: Array<u256>,
// state_root: u256,
// block_number: u256,
// block_hash: u256
// ) -> bool
// ```
// In the Cairo contract, a `u256` is passed as two `u128` values.
// Additionally, the `Array` parameter requires passing the length at the beginning.
// Therefore, the overall calldata length is 1 + state_diff * 2 + 3 * 2.
let mut calldata = Vec::with_capacity(state_diff_len * 2 + 1);

// push state diff data len to calldata.
calldata.push(FieldElement::from(state_diff_len - 3));
for (index, ft) in state_diff.iter().enumerate() {
// TODO: set current block number to l2 last published block number. just for testnet.
if index == state_diff_len - 2 {
let last_published_block_number = U256::from(self.last_published_state().await?.as_u128());
let current_block_number = last_published_block_number.add(U256::one());
let low_ft: u128 = current_block_number.low_u128();
let high_ft: u128 = (current_block_number >> 128).low_u128();
calldata.push(FieldElement::from(low_ft));
calldata.push(FieldElement::from(high_ft));
continue;
}

let low_ft: u128 = ft.low_u128();
let high_ft: u128 = (ft >> 128).low_u128();
calldata.push(FieldElement::from(low_ft));
calldata.push(FieldElement::from(high_ft));
}

let res = self
.sequencer_account
.execute(vec![Call {
to: self.da_contract,
selector: get_selector_from_name("UpdateState").map_err(|e| anyhow!("get selector failed, {e}"))?,
calldata,
}])
.send()
.await
.map_err(|e| anyhow!("send transaction failed {e}"));

log::debug!("Publish block data to starknet result: {:#?}", res);

Ok(())
}

fn get_da_metric_labels(&self) -> HashMap<String, String> {
[("name".into(), "starknet".into())].iter().cloned().collect()
}
}

fn create_provider(url: &str) -> Result<JsonRpcClient<HttpTransport>, String> {
Url::parse(url).map(|url| JsonRpcClient::new(HttpTransport::new(url))).map_err(|e| format!("invalid http url, {e}"))
}

impl TryFrom<config::StarknetConfig> for StarknetClient {
type Error = String;

fn try_from(conf: config::StarknetConfig) -> Result<Self, Self::Error> {
let signer = FieldElement::from_hex_be(&conf.sequencer_key)
.map(|elem| LocalWallet::from(SigningKey::from_secret_scalar(elem)))
.map_err(|e| format!("invalid sequencer key, {e}"))?;

let da_contract =
FieldElement::from_hex_be(&conf.core_contracts).map_err(|e| format!("invalid da contract address, {e}"))?;

let chain_id =
FieldElement::from_byte_slice_be(conf.chain_id.as_bytes()).map_err(|e| format!("invalid chain id {e}"))?;

let provider = create_provider(&conf.http_provider)?;
let account = FieldElement::from_hex_be(&conf.account_address)
.map(|acc| SingleOwnerAccount::new(provider, signer, acc, chain_id, ExecutionEncoding::New))
.map_err(|e| format!("invalid sequencer address {e}"))?;

Ok(Self {
provider: create_provider(&conf.http_provider)?,
da_contract,
sequencer_account: account,
mode: conf.mode,
})
}
}
6 changes: 6 additions & 0 deletions crates/node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use mc_data_availability::avail::{config::AvailConfig, AvailClient};
use mc_data_availability::celestia::{config::CelestiaConfig, CelestiaClient};
use mc_data_availability::ethereum::config::EthereumConfig;
use mc_data_availability::ethereum::EthereumClient;
use mc_data_availability::starknet::config::StarknetConfig;
use mc_data_availability::starknet::StarknetClient;
use mc_data_availability::{DaClient, DaLayer, DataAvailabilityWorker};
use mc_genesis_data_provider::OnDiskGenesisConfig;
use mc_l1_messages::config::L1MessagesWorkerConfig;
Expand Down Expand Up @@ -450,6 +452,10 @@ pub fn new_full(
let avail_conf = AvailConfig::try_from(&da_path)?;
Arc::new(AvailClient::try_from(avail_conf).map_err(|e| ServiceError::Other(e.to_string()))?)
}
DaLayer::Starknet => {
let starknet_conf = StarknetConfig::try_from(&da_path)?;
Arc::new(StarknetClient::try_from(starknet_conf).map_err(|e| ServiceError::Other(e.to_string()))?)
}
};

task_manager.spawn_essential_handle().spawn(
Expand Down