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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exclude = [
"examples/crates",
"examples/github",
"examples/submission_api",
"examples/blob_api",
"examples/block_api",
"examples/storage_api",
"examples/chain_api",
Expand Down
38 changes: 38 additions & 0 deletions client/src/clients/main_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use avail::{
balances::types::AccountData,
system::{storage as SystemStorage, types::AccountInfo},
};
#[cfg(feature = "next")]
use avail_rust_core::rpc::{blob::{Blob, BlobInfo}, kate::DataProof};
use avail_rust_core::{
AccountId, AccountIdLike, AvailHeader, BlockInfo, H256, HashNumber, StorageMap,
grandpa::GrandpaJustification,
Expand Down Expand Up @@ -805,6 +807,42 @@ impl ChainApi {
Ok(with_retry_on_error(f, retry_on_error).await?)
}

#[cfg(feature = "next")]
pub async fn blob_get_blob(&self, blob_hash: H256, block_hash: Option<H256>) -> Result<Blob, Error> {
let retry_on_error = self
.retry_on_error
.unwrap_or_else(|| self.client.is_global_retries_enabled());

let f = || async move { rpc::blob::get_blob_v2(&self.client.rpc_client, blob_hash, block_hash).await };

Ok(with_retry_on_error(f, retry_on_error).await?)
}

/// Retrieve indexed blob info
#[cfg(feature = "next")]
pub async fn blob_get_blob_info(&self, blob_hash: H256) -> Result<BlobInfo, Error> {
let retry_on_error = self
.retry_on_error
.unwrap_or_else(|| self.client.is_global_retries_enabled());

let f = || async move { rpc::blob::get_blob_info(&self.client.rpc_client, blob_hash).await };

Ok(with_retry_on_error(f, retry_on_error).await?)
}

/// Return inclusion proof for a blob. If `at` is `Some(hash)` the proof is computed for that block,
/// otherwise the node will try to use its indexed finalized block for the blob.
#[cfg(feature = "next")]
pub async fn blob_inclusion_proof(&self, blob_hash: H256, at: Option<H256>) -> Result<DataProof, Error> {
let retry_on_error = self
.retry_on_error
.unwrap_or_else(|| self.client.is_global_retries_enabled());

let f = || async move { rpc::blob::inclusion_proof(&self.client.rpc_client, blob_hash, at).await };

Ok(with_retry_on_error(f, retry_on_error).await?)
}

fn should_retry(&self) -> bool {
self.retry_on_error
.unwrap_or_else(|| self.client.is_global_retries_enabled())
Expand Down
73 changes: 73 additions & 0 deletions core/src/rpc/blob.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
use crate::RpcError;
use crate::rpc::kate::DataProof;
use codec::{Decode, Encode};
use primitive_types::H256;
use serde::{Deserialize, Serialize};
use subxt_rpcs::{RpcClient, rpc_params};

/// A data blob
#[derive(Clone, Debug, Encode, Decode, Serialize, Deserialize)]
pub struct Blob {
/// The hash of the blob.
pub blob_hash: H256,
/// The actual data of this blob.
pub data: Vec<u8>,
/// The size of the blob.
pub size: u64,
}

/// Ownership entry as returned by the node indexer / runtime summary.
#[derive(Clone, Debug, Encode, Decode, Serialize, Deserialize)]
pub struct OwnershipEntry {
/// The address that owns the blob
pub address: String,
/// The babe key of the validator
pub babe_key: String,
/// The corresponding peer id
pub encoded_peer_id: String,
/// The signature of the holder
pub signature: Vec<u8>,
}

/// BlobInfo returned by `blob_getBlobInfo`
#[derive(Clone, Debug, Encode, Decode, Serialize, Deserialize)]
pub struct BlobInfo {
/// The hash of the blob.
pub hash: H256,
/// Block hash where this blob was observed (canonical/finalized).
pub block_hash: H256,
/// Block number where this blob was observed.
pub block_number: u32,
/// Ownership entries for the blob.
pub ownership: Vec<OwnershipEntry>,
}

pub async fn submit_blob(client: &RpcClient, metadata_signed_transaction: &[u8], blob: &[u8]) -> Result<(), RpcError> {
use base64::Engine;
let encoded_metadata = base64::engine::general_purpose::STANDARD.encode(&metadata_signed_transaction);
Expand All @@ -10,3 +51,35 @@ pub async fn submit_blob(client: &RpcClient, metadata_signed_transaction: &[u8],
let _value: () = client.request("blob_submitBlob", params).await?;
Ok(())
}

pub async fn get_blob(
client: &RpcClient,
block_hash: H256,
blob_index: u32,
blob_hash: H256,
) -> Result<Blob, RpcError> {
let params = rpc_params![block_hash, blob_index, blob_hash];
let value: Blob = client.request("blob_getBlob", params).await?;
Ok(value)
}

pub async fn get_blob_v2(client: &RpcClient, blob_hash: H256, block_hash: Option<H256>) -> Result<Blob, RpcError> {
let params = rpc_params![blob_hash, block_hash];
let value: Blob = client.request("blob_getBlobV2", params).await?;
Ok(value)
}

/// Get canonical blob indexing info (if any) for a given blob_hash.
pub async fn get_blob_info(client: &RpcClient, blob_hash: H256) -> Result<BlobInfo, RpcError> {
let params = rpc_params![blob_hash];
let value: BlobInfo = client.request("blob_getBlobInfo", params).await?;
Ok(value)
}

/// Get inclusion proof for a blob. If `at` is `Some(block_hash)` the proof
/// is generated for that block; if `None` the node will use its indexed finalised block.
pub async fn inclusion_proof(client: &RpcClient, blob_hash: H256, at: Option<H256>) -> Result<DataProof, RpcError> {
let params = rpc_params![blob_hash, at];
let value: DataProof = client.request("blob_inclusionProof", params).await?;
Ok(value)
}
2 changes: 2 additions & 0 deletions examples/blob_api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
Cargo.lock
8 changes: 8 additions & 0 deletions examples/blob_api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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", "next","reqwest"] }
hex = "0.4.3"
tokio = { version = "1.45.0", features = ["rt-multi-thread", "macros"] }
51 changes: 51 additions & 0 deletions examples/blob_api/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use avail_rust::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Error> {
let client = Client::new(LOCAL_ENDPOINT).await?;

let blob_hash = H256::from_slice(&hex::decode("66fae1a211a6179044a9f85c2c16cbf747cdd2c19143880c690de4006768efb7").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(())
}