|
1 | | -use std::fs; |
| 1 | +use std::{env, fs}; |
2 | 2 |
|
| 3 | +use crate::get_sequencer_pubkey; |
| 4 | +use alloy_provider::{Provider, ProviderBuilder}; |
3 | 5 | use anyhow::{bail, Result}; |
| 6 | +use celestia_grpc_client::proto::celestia::zkism::v1::MsgCreateZkExecutionIsm; |
| 7 | +use celestia_grpc_client::proto::hyperlane::warp::v1::MsgSetToken; |
| 8 | +use celestia_grpc_client::types::ClientConfig; |
| 9 | +use celestia_grpc_client::CelestiaIsmClient; |
| 10 | +use celestia_rpc::{BlobClient, Client, HeaderClient}; |
| 11 | +use celestia_types::nmt::Namespace; |
| 12 | +use celestia_types::{Blob, ExtendedHeader}; |
| 13 | +use ev_types::v1::SignedData; |
| 14 | +use prost::Message; |
4 | 15 | use tracing::info; |
5 | 16 |
|
6 | 17 | use crate::commands::cli::VERSION; |
@@ -60,6 +71,117 @@ pub async fn start() -> Result<()> { |
60 | 71 | Ok(()) |
61 | 72 | } |
62 | 73 |
|
| 74 | +pub async fn create_zkism() -> Result<()> { |
| 75 | + let celestia_rpc_url = env::var("CELESTIA_RPC_URL")?; |
| 76 | + let reth_rpc_url = env::var("RETH_RPC_URL")?; |
| 77 | + let sequencer_rpc_url = env::var("SEQUENCER_RPC_URL")?; |
| 78 | + let namespace_hex = env::var("CELESTIA_NAMESPACE")?; |
| 79 | + let config = ClientConfig::from_env()?; |
| 80 | + let ism_client = CelestiaIsmClient::new(config).await?; |
| 81 | + let celestia_client = Client::new(&celestia_rpc_url, None).await?; |
| 82 | + let namespace: Namespace = Namespace::new_v0(&hex::decode(namespace_hex)?).unwrap(); |
| 83 | + |
| 84 | + // Find a Celestia height with at least one blob (brute force backwards starting from head) |
| 85 | + let (celestia_state, blobs) = brute_force_head(&celestia_client, namespace).await?; |
| 86 | + // DA HEIGHT |
| 87 | + let height: u64 = celestia_state.height().value(); |
| 88 | + // DA BLOCK HASH |
| 89 | + let block_hash = celestia_state.hash().as_bytes().to_vec(); |
| 90 | + let last_blob = blobs.last().expect("User Error: Can't use a 0-blob checkpoint"); |
| 91 | + let data = SignedData::decode(last_blob.data.as_slice())?; |
| 92 | + |
| 93 | + // EV BLOCK HEIGHT |
| 94 | + let last_blob_height = data.data.unwrap().metadata.unwrap().height; |
| 95 | + |
| 96 | + let provider = ProviderBuilder::new().connect_http(reth_rpc_url.parse()?); |
| 97 | + |
| 98 | + let block = provider |
| 99 | + .get_block(alloy_rpc_types::BlockId::Number( |
| 100 | + alloy_rpc_types::BlockNumberOrTag::Number(last_blob_height), |
| 101 | + )) |
| 102 | + .await? |
| 103 | + .ok_or_else(|| anyhow::anyhow!("Block not found"))?; |
| 104 | + |
| 105 | + // EV STATE ROOT |
| 106 | + let last_blob_state_root = block.header.state_root; |
| 107 | + // todo: deploy the ISM and Update |
| 108 | + let pub_key = get_sequencer_pubkey(sequencer_rpc_url).await?; |
| 109 | + |
| 110 | + let ev_hyperlane_vkey = fs::read("testdata/vkeys/ev-hyperlane-vkey-hash")?; |
| 111 | + let ev_combined_vkey = fs::read("testdata/vkeys/ev-range-exec-vkey-hash")?; |
| 112 | + let groth16_vkey = fs::read("testdata/vkeys/groth16_vk.bin")?; |
| 113 | + |
| 114 | + let create_message = MsgCreateZkExecutionIsm { |
| 115 | + creator: ism_client.signer_address().to_string(), |
| 116 | + state_root: last_blob_state_root.to_vec(), |
| 117 | + height: last_blob_height, |
| 118 | + celestia_header_hash: block_hash, |
| 119 | + celestia_height: height, |
| 120 | + namespace: namespace.as_bytes().to_vec(), |
| 121 | + sequencer_public_key: pub_key, |
| 122 | + groth16_vkey, |
| 123 | + state_transition_vkey: hex::decode(&ev_combined_vkey[2..]).unwrap(), |
| 124 | + state_membership_vkey: hex::decode(&ev_hyperlane_vkey[2..]).unwrap(), |
| 125 | + }; |
| 126 | + |
| 127 | + let response = ism_client.send_tx(create_message).await?; |
| 128 | + assert!(response.success); |
| 129 | + info!("ISM created successfully"); |
| 130 | + Ok(()) |
| 131 | +} |
| 132 | + |
| 133 | +pub async fn update_ism(ism_id: String, token_id: String) -> Result<()> { |
| 134 | + let config = ClientConfig::from_env()?; |
| 135 | + let ism_client = CelestiaIsmClient::new(config).await?; |
| 136 | + |
| 137 | + //todo update |
| 138 | + let message = MsgSetToken { |
| 139 | + owner: ism_client.signer_address().to_string(), |
| 140 | + token_id, |
| 141 | + new_owner: ism_client.signer_address().to_string(), |
| 142 | + ism_id, |
| 143 | + renounce_ownership: false, |
| 144 | + }; |
| 145 | + |
| 146 | + ism_client.send_tx(message).await?; |
| 147 | + info!("ISM updated successfully"); |
| 148 | + |
| 149 | + Ok(()) |
| 150 | +} |
| 151 | + |
63 | 152 | pub fn version() { |
64 | 153 | println!("version: {VERSION}"); |
65 | 154 | } |
| 155 | + |
| 156 | +async fn brute_force_head(celestia_client: &Client, namespace: Namespace) -> Result<(ExtendedHeader, Vec<Blob>)> { |
| 157 | + // Find a Celestia height with at least one blob (brute force backwards starting from head) |
| 158 | + let mut search_height: u64 = celestia_client.header_local_head().await.unwrap().height().value(); |
| 159 | + let (celestia_state, blobs) = loop { |
| 160 | + match celestia_client.header_get_by_height(search_height).await { |
| 161 | + Ok(state) => { |
| 162 | + let current_height = state.height().value(); |
| 163 | + match celestia_client.blob_get_all(current_height, &[namespace]).await { |
| 164 | + Ok(Some(blobs)) if !blobs.is_empty() => { |
| 165 | + info!("Found {} blob(s) at Celestia height {}", blobs.len(), current_height); |
| 166 | + break (state, blobs); |
| 167 | + } |
| 168 | + Ok(_) => { |
| 169 | + info!("No blobs at height {}, trying nexst height", current_height); |
| 170 | + search_height -= 1; |
| 171 | + } |
| 172 | + Err(e) => { |
| 173 | + info!( |
| 174 | + "Error fetching blobs at height {}: {}, trying next height", |
| 175 | + current_height, e |
| 176 | + ); |
| 177 | + search_height -= 1; |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + Err(e) => { |
| 182 | + return Err(anyhow::anyhow!("Failed to get header at height {search_height}: {e}")); |
| 183 | + } |
| 184 | + } |
| 185 | + }; |
| 186 | + Ok((celestia_state, blobs)) |
| 187 | +} |
0 commit comments