diff --git a/Cargo.toml b/Cargo.toml index b78285b9..cb150a5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,19 @@ members = [ "client", ] exclude = [ + "examples/crates", + "examples/github", + "examples/submission_api", + "examples/blob_api", + "examples/block_api", + "examples/storage_api", + "examples/chain_api", + "examples/parallel_submission", + "examples/multisig", + "examples/batch", + "examples/estimating_fees", + "examples/custom_ext_event_storage", + "examples/subscriptions", "ffi", "scripts", ] diff --git a/client/examples/blob_api.rs b/client/examples/blob_api.rs index 8c6c2b0e..0e971070 100644 --- a/client/examples/blob_api.rs +++ b/client/examples/blob_api.rs @@ -17,7 +17,7 @@ async fn main() -> Result<(), Error> { client .tx() .data_availability() - .submit_blob_metadata(2, blob_hash, blob.len() as u64, commitments); + .submit_blob_metadata(2, blob_hash, blob.len() as u64, commitments, None, None); let tx = unsigned_tx.sign(&signer, Options::default()).await.unwrap().encode(); diff --git a/client/src/transaction_api.rs b/client/src/transaction_api.rs index febc3418..32df7d87 100644 --- a/client/src/transaction_api.rs +++ b/client/src/transaction_api.rs @@ -1192,8 +1192,10 @@ impl DataAvailability { blob_hash: H256, size: u64, commitments: Vec, + eval_point_seed: Option<[u8; 32]>, + eval_claim: Option<[u8; 16]>, ) -> SubmittableTransaction { - let value = avail::data_availability::tx::SubmitBlobMetadata { app_id, blob_hash, size, commitments }; + let value = avail::data_availability::tx::SubmitBlobMetadata { app_id, blob_hash, size, commitments, eval_point_seed, eval_claim }; SubmittableTransaction::from_encodable(self.0.clone(), value) } } diff --git a/core/src/header.rs b/core/src/header.rs index cfda79ce..6e21592a 100644 --- a/core/src/header.rs +++ b/core/src/header.rs @@ -1,7 +1,7 @@ use codec::{Compact, Decode, Encode}; use primitive_types::H256; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use subxt_core::config::{Hasher, Header, substrate::BlakeTwo256}; +use subxt_core::config::{substrate::BlakeTwo256, Hasher, Header as SubxtHeader}; pub use subxt_core::config::substrate::{Digest, DigestItem}; @@ -19,10 +19,11 @@ pub struct AvailHeader { } impl AvailHeader { + /// Data root of all DA data in this block, regardless of PCS (KZG/Fri). pub fn data_root(&self) -> H256 { match &self.extension { - HeaderExtension::V3(ext) => ext.commitment.data_root, - HeaderExtension::V4(ext) => ext.commitment.data_root, + HeaderExtension::Kzg(KzgHeader::V4(ext)) => ext.commitment.data_root, + HeaderExtension::Fri(FriHeader::V1(ext)) => ext.data_root, } } @@ -31,7 +32,7 @@ impl AvailHeader { } } -impl Header for AvailHeader { +impl SubxtHeader for AvailHeader { type Hasher = BlakeTwo256; type Number = u32; @@ -65,11 +66,32 @@ where } } +/// Top-level DA header extension: *which PCS + which version inside*. #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] -#[repr(u8)] +// #[serde(rename_all = "camelCase")] pub enum HeaderExtension { - V3(V3HeaderExtension) = 2, - V4(V4HeaderExtension) = 3, + /// KZG-based DA header (current mainnet scheme, v4). + Kzg(KzgHeader), + /// Fri/Binius-based DA header (new scheme). + Fri(FriHeader), +} + +impl Default for HeaderExtension { + fn default() -> Self { + HeaderExtension::Fri(FriHeader::V1(FriV1HeaderExtension::default())) + } +} + +/// KZG header variants (only v4 is used on-chain now). +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] +pub enum KzgHeader { + V4(V4HeaderExtension), +} + +/// Fri header variants (v1 for now). +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] +pub enum FriHeader { + V1(FriV1HeaderExtension), } #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -92,12 +114,6 @@ impl Decode for V3HeaderExtension { } } -impl Default for HeaderExtension { - fn default() -> Self { - Self::V3(Default::default()) - } -} - #[derive(Debug, Clone, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] pub struct CompactDataLookup { @@ -184,3 +200,28 @@ pub struct V4CompactDataLookup { pub index: Vec, pub rows_per_tx: Vec, } + +/// Fri blob commitment: one entry per blob in the block. +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] +#[serde(rename_all = "camelCase")] +pub struct FriBlobCommitment { + /// Blob size in bytes (original data). + pub size_bytes: u64, + /// Commitment to the encoded blob (Merkle root, 32 bytes). + pub commitment: H256, +} + +/// Version tag for Fri parameters. +/// This mirrors `FriParamsVersion(pub u8)` on-chain. +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] +#[serde(rename_all = "camelCase")] +pub struct FriParamsVersion(pub u8); + +/// Fri v1 header extension: aggregate of all blob commitments for the block. +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] +#[serde(rename_all = "camelCase")] +pub struct FriV1HeaderExtension { + pub blobs: Vec, + pub data_root: H256, + pub params_version: FriParamsVersion, +} diff --git a/core/src/types/pallets.rs b/core/src/types/pallets.rs index 4846d4a4..375dcc21 100644 --- a/core/src/types/pallets.rs +++ b/core/src/types/pallets.rs @@ -624,6 +624,8 @@ pub mod data_availability { pub blob_hash: H256, pub size: u64, pub commitments: Vec, + pub eval_point_seed: Option<[u8; 32]>, + pub eval_claim: Option<[u8; 16]>, } impl Encode for SubmitBlobMetadata { fn encode_to(&self, dest: &mut T) { @@ -631,6 +633,8 @@ pub mod data_availability { dest.write(&self.blob_hash.encode()); dest.write(&self.size.encode()); dest.write(&self.commitments.encode()); + dest.write(&self.eval_point_seed.encode()); + dest.write(&self.eval_claim.encode()); } } impl Decode for SubmitBlobMetadata { @@ -639,7 +643,9 @@ pub mod data_availability { let blob_hash = Decode::decode(input)?; let size = Decode::decode(input)?; let commitments = Decode::decode(input)?; - Ok(Self { app_id, blob_hash, size, commitments }) + let eval_point_seed = Decode::decode(input)?; + let eval_claim = Decode::decode(input)?; + Ok(Self { app_id, blob_hash, size, commitments, eval_point_seed, eval_claim }) } } impl HasHeader for SubmitBlobMetadata { diff --git a/examples/blob_api/.gitignore b/examples/blob_api/.gitignore new file mode 100644 index 00000000..a9d37c56 --- /dev/null +++ b/examples/blob_api/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/examples/blob_api/Cargo.toml b/examples/blob_api/Cargo.toml new file mode 100644 index 00000000..737628f8 --- /dev/null +++ b/examples/blob_api/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "blob-api-example" +edition = "2024" + +[dependencies] +avail-rust = { package = "avail-rust-client", path = "./../../client", default-features = false, features = ["native", "reqwest"] } +hex = "0.4.3" +tokio = { version = "1.45.0", features = ["rt-multi-thread", "macros"] } diff --git a/examples/blob_api/src/main.rs b/examples/blob_api/src/main.rs new file mode 100644 index 00000000..884c5419 --- /dev/null +++ b/examples/blob_api/src/main.rs @@ -0,0 +1,77 @@ +use avail_rust::{avail_rust_core::rpc::blob::submit_blob, prelude::*}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let client = Client::new(LOCAL_ENDPOINT).await?; + + // For testing blob submission tx + let blob = hex::decode("4141414141414141414141414141414141414141414141414141414141414141").unwrap(); + let blob_hash = H256::from_slice(&hex::decode("59cad5948673622c1d64e2322488bf01619f7ff45789741b15a9f782ce9290a8").unwrap()); + let eval_point_seed: [u8; 32] = hex::decode("8ed022d17e8ba7e14b1a62c62b55cd528f0bb9ac742e82e94584471b6ad6c48e").unwrap().try_into().expect("32 bytes"); + let eval_claim: [u8; 16] = hex::decode("679d078d1c77e2787c9908b731ddc03d").unwrap().try_into().expect("16 bytes"); + let commitments = hex::decode("329fef926f1a3b8c7d2b9ee76a599474f73b12156c5642fd835147751415b6c2").unwrap(); + + let signer = alice(); + let unsigned_tx = client.tx().data_availability().submit_blob_metadata( + 2, + blob_hash, + blob.len() as u64, + commitments, + Some(eval_point_seed), + Some(eval_claim), + ); + + let tx = unsigned_tx.sign(&signer, Options::default()).await.unwrap().encode(); + + if let Err(e) = submit_blob(&client.rpc_client, &tx, &blob).await { + println!("An error has occured: {e}"); + } else { + println!("Blob submitted"); + } + + // For testing blob RPCs + // let blob_hash = H256::from_slice(&hex::decode("59cad5948673622c1d64e2322488bf01619f7ff45789741b15a9f782ce9290a8").unwrap()); + // let block_hash = H256::from_slice(&hex::decode("9139401fe68807814ea852d97e646a022ad07885b03a3a99ecfbb99735435824").unwrap()); + + // + // getBlob + // + // Mode 1: using a specific block + // let blob_from_block = client + // .chain() + // .blob_get_blob(blob_hash, Some(block_hash)) + // .await?; + // println!("getBlob (block): {:?}", blob_from_block); + + // // Mode 2: using indexed storage info + // let blob_canonical = client + // .chain() + // .blob_get_blob(blob_hash, None) + // .await?; + // println!("getBlob (indexed_info): {:?}", blob_canonical); + + // // + // // getBlobInfo + // // + // let info = client.chain().blob_get_blob_info(blob_hash).await?; + // println!("blobInfo: {:?}", info); + + // // + // // inclusionProof + // // + // // Using Indexed info + // let proof = client + // .chain() + // .blob_inclusion_proof(blob_hash, None) + // .await?; + // println!("inclusion proof (indexed_info): {:?}", proof); + + // // Using a specific block + // let proof_block = client + // .chain() + // .blob_inclusion_proof(blob_hash, Some(block_hash)) + // .await?; + // println!("inclusion proof(block): {:?}", proof_block); + + Ok(()) +}