Skip to content

Commit

Permalink
add getSignaturesForAsset endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
tahsintunan committed Jan 10, 2024
1 parent c421351 commit 8a1e259
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 5 deletions.
36 changes: 35 additions & 1 deletion das_api/src/api/api_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use digital_asset_types::{
},
dapi::{
get_asset, get_asset_proofs, get_assets, get_assets_by_authority, get_assets_by_creator,
get_assets_by_group, get_assets_by_owner, get_proof_for_asset, search_assets,
get_assets_by_group, get_assets_by_owner, get_proof_for_asset, get_signatures_for_asset,
search_assets,
},
rpc::{
filter::{AssetSortBy, SearchConditionType},
Expand Down Expand Up @@ -468,6 +469,39 @@ impl ApiContract for DasApi {
.map_err(Into::into)
}

async fn get_signatures_for_asset(
self: &DasApi,
payload: GetSignaturesForAsset,
) -> Result<TransactionSignatureList, DasApiError> {
let GetSignaturesForAsset {
id,
limit,
page,
before,
after,
tree,
leaf_index,
cursor,
} = payload;

if !((id.is_some() && tree.is_none() && leaf_index.is_none())
|| (id.is_none() && tree.is_some() && leaf_index.is_some()))
{
return Err(DasApiError::ValidationError(
"Must provide either 'id' or both 'tree' and 'leafIndex'".to_string(),
));
}
let id = validate_opt_pubkey(&id)?;
let tree = validate_opt_pubkey(&tree)?;

let page_options =
self.validate_pagination(&limit, &page, &before, &after, &cursor, &None)?;

get_signatures_for_asset(&self.db_connection, id, tree, leaf_index, page_options)
.await
.map_err(Into::into)
}

async fn get_grouping(
self: &DasApi,
payload: GetGrouping,
Expand Down
25 changes: 24 additions & 1 deletion das_api/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::DasApiError;
use async_trait::async_trait;
use digital_asset_types::rpc::filter::SearchConditionType;
use digital_asset_types::rpc::options::Options;
use digital_asset_types::rpc::response::AssetList;
use digital_asset_types::rpc::response::{AssetList, TransactionSignatureList};
use digital_asset_types::rpc::{filter::AssetSorting, response::GetGroupingResponse};
use digital_asset_types::rpc::{Asset, AssetProof, Interface, OwnershipModel, RoyaltyModel};
use open_rpc_derive::{document_rpc, rpc};
Expand Down Expand Up @@ -147,6 +147,20 @@ pub struct GetGrouping {
pub group_value: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct GetSignaturesForAsset {
pub id: Option<String>,
pub limit: Option<u32>,
pub page: Option<u32>,
pub before: Option<String>,
pub after: Option<String>,
pub tree: Option<String>,
pub leaf_index: Option<i64>,
#[serde(default)]
pub cursor: Option<String>,
}

#[document_rpc]
#[async_trait]
pub trait ApiContract: Send + Sync + 'static {
Expand Down Expand Up @@ -220,6 +234,15 @@ pub trait ApiContract: Send + Sync + 'static {
summary = "Search for assets by a variety of parameters"
)]
async fn search_assets(&self, payload: SearchAssets) -> Result<AssetList, DasApiError>;
#[rpc(
name = "getSignaturesForAsset",
params = "named",
summary = "Get transaction signatures for an asset"
)]
async fn get_signatures_for_asset(
&self,
payload: GetSignaturesForAsset,
) -> Result<TransactionSignatureList, DasApiError>;
#[rpc(
name = "getGrouping",
params = "named",
Expand Down
11 changes: 11 additions & 0 deletions das_api/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ impl RpcApiBuilder {
)?;
module.register_alias("getAssetsByGroup", "get_assets_by_group")?;

module.register_async_method(
"getSignaturesForAsset",
|rpc_params, rpc_context| async move {
let payload = rpc_params.parse::<GetSignaturesForAsset>()?;
rpc_context
.get_signatures_for_asset(payload)
.await
.map_err(Into::into)
},
)?;

module.register_async_method("search_assets", |rpc_params, rpc_context| async move {
let payload = rpc_params.parse::<SearchAssets>()?;
rpc_context.search_assets(payload).await.map_err(Into::into)
Expand Down
82 changes: 80 additions & 2 deletions digital_asset_types/src/dao/scopes/asset.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::dao::{
asset::{self},
asset_authority, asset_creators, asset_data, asset_grouping, Cursor, FullAsset, GroupingSize,
Pagination,
asset_authority, asset_creators, asset_data, asset_grouping, cl_audits_v2,
sea_orm_active_enums::Instruction,
Cursor, FullAsset, GroupingSize, Pagination,
};

use indexmap::IndexMap;
Expand Down Expand Up @@ -397,3 +398,80 @@ pub async fn get_by_id(
groups: grouping,
})
}

pub async fn fetch_transactions(
conn: &impl ConnectionTrait,
tree: Vec<u8>,
leaf_idx: i64,
pagination: &Pagination,
limit: u64,
) -> Result<Vec<(String, String)>, DbErr> {
let stmt = cl_audits_v2::Entity::find()
.filter(cl_audits_v2::Column::Tree.eq(tree))
.filter(cl_audits_v2::Column::LeafIdx.eq(leaf_idx))
.order_by(cl_audits_v2::Column::Seq, sea_orm::Order::Asc);

let stmt = paginate(
pagination,
limit,
stmt,
sea_orm::Order::Asc,
asset::Column::Id,
);
let transactions = stmt.all(conn).await?;
let transaction_list = transactions
.into_iter()
.map(|transaction| {
let tx = bs58::encode(transaction.tx).into_string();
let ix = Instruction::to_str(&transaction.instruction).to_string();
(tx, ix)
})
.collect();

Ok(transaction_list)
}

pub async fn get_signatures_for_asset(
conn: &impl ConnectionTrait,
asset_id: Option<Vec<u8>>,
tree_id: Option<Vec<u8>>,
leaf_idx: Option<i64>,
pagination: &Pagination,
limit: u64,
) -> Result<Vec<(String, String)>, DbErr> {
// if tree_id and leaf_idx are provided, use them directly to fetch transactions
if let (Some(tree_id), Some(leaf_idx)) = (tree_id, leaf_idx) {
let transactions = fetch_transactions(conn, tree_id, leaf_idx, pagination, limit).await?;
return Ok(transactions);
}

if asset_id.is_none() {
return Err(DbErr::Custom(
"Either 'id' or both 'tree' and 'leafIndex' must be provided".to_string(),
));
}

// if only asset_id is provided, fetch the latest tree and leaf_idx (asset.nonce) for the asset
// and use them to fetch transactions
let stmt = asset::Entity::find()
.distinct_on([(asset::Entity, asset::Column::Id)])
.filter(asset::Column::Id.eq(asset_id))
.order_by(asset::Column::Id, Order::Desc)
.limit(1);
let asset = stmt.one(conn).await?;
if let Some(asset) = asset {
let tree = asset
.tree_id
.ok_or(DbErr::RecordNotFound("Tree not found".to_string()))?;
if tree.is_empty() {
return Err(DbErr::Custom("Empty tree for asset".to_string()));
}
let leaf_idx = asset
.nonce
.ok_or(DbErr::RecordNotFound("Leaf ID does not exist".to_string()))?;
let transactions = fetch_transactions(conn, tree, leaf_idx, pagination, limit).await?;
Ok(transactions)
} else {
Ok(Vec::new())
}
}
26 changes: 26 additions & 0 deletions digital_asset_types/src/dapi/common/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::dao::Pagination;
use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping};
use crate::rpc::filter::{AssetSortBy, AssetSortDirection, AssetSorting};
use crate::rpc::options::Options;
use crate::rpc::response::TransactionSignatureList;
use crate::rpc::response::{AssetError, AssetList};
use crate::rpc::{
Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface,
Expand Down Expand Up @@ -83,6 +84,31 @@ pub fn build_asset_response(
}
}

pub fn build_transaction_signatures_response(
items: Vec<(String, String)>,
limit: u64,
pagination: &Pagination,
) -> TransactionSignatureList {
let total = items.len() as u32;
let (page, before, after) = match pagination {
Pagination::Keyset { before, after } => {
let bef = before.clone().and_then(|x| String::from_utf8(x).ok());
let aft = after.clone().and_then(|x| String::from_utf8(x).ok());
(None, bef, aft)
}
Pagination::Page { page } => (Some(*page), None, None),
Pagination::Cursor { .. } => (None, None, None),
};
TransactionSignatureList {
total,
limit: limit as u32,
page: page.map(|x| x as u32),
before,
after,
items,
}
}

pub fn create_sorting(sorting: AssetSorting) -> (sea_orm::query::Order, Option<asset::Column>) {
let sort_column = match sorting.sort_by {
AssetSortBy::Id => Some(asset::Column::Id),
Expand Down
5 changes: 4 additions & 1 deletion digital_asset_types/src/dapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ mod assets_by_creator;
mod assets_by_group;
mod assets_by_owner;
mod change_logs;
pub mod common;
mod get_asset;
mod search_assets;
mod signatures_for_asset;

pub mod common;
pub use assets_by_authority::*;
pub use assets_by_creator::*;
pub use assets_by_group::*;
pub use assets_by_owner::*;
pub use change_logs::*;
pub use get_asset::*;
pub use search_assets::*;
pub use signatures_for_asset::*;
32 changes: 32 additions & 0 deletions digital_asset_types/src/dapi/signatures_for_asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use crate::dao::scopes;
use crate::dao::PageOptions;

use crate::rpc::response::TransactionSignatureList;
use sea_orm::DatabaseConnection;
use sea_orm::DbErr;

use super::common::{build_transaction_signatures_response, create_pagination};

pub async fn get_signatures_for_asset(
db: &DatabaseConnection,
asset_id: Option<Vec<u8>>,
tree: Option<Vec<u8>>,
leaf_idx: Option<i64>,
page_options: PageOptions,
) -> Result<TransactionSignatureList, DbErr> {
let pagination = create_pagination(&page_options)?;
let transactions = scopes::asset::get_signatures_for_asset(
db,
asset_id,
tree,
leaf_idx,
&pagination,
page_options.limit,
)
.await?;
Ok(build_transaction_signatures_response(
transactions,
page_options.limit,
&pagination,
))
}
14 changes: 14 additions & 0 deletions digital_asset_types/src/rpc/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,17 @@ pub struct AssetList {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<AssetError>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)]
#[serde(default)]
pub struct TransactionSignatureList {
pub total: u32,
pub limit: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
pub items: Vec<(String, String)>,
}

0 comments on commit 8a1e259

Please sign in to comment.