From d736e818878cdf30f3dd48cb59b10f17d6acb3e6 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Mon, 27 Nov 2023 11:31:37 -0500 Subject: [PATCH 1/3] apply helius pr#133 --- das_api/src/api/api_impl.rs | 214 ++++++++++++++---- das_api/src/api/mod.rs | 58 +++++ das_api/src/builder.rs | 21 ++ das_api/src/error.rs | 8 + das_api/src/validation.rs | 17 ++ digital_asset_types/src/dao/mod.rs | 49 +++- digital_asset_types/src/dao/scopes/asset.rs | 114 +++++++--- .../src/dapi/assets_by_authority.rs | 20 +- .../src/dapi/assets_by_creator.rs | 20 +- .../src/dapi/assets_by_group.rs | 20 +- .../src/dapi/assets_by_owner.rs | 20 +- digital_asset_types/src/dapi/change_logs.rs | 178 +++++++++++++-- digital_asset_types/src/dapi/common/asset.rs | 104 +++++---- digital_asset_types/src/dapi/get_asset.rs | 35 ++- digital_asset_types/src/dapi/search_assets.rs | 22 +- digital_asset_types/src/rpc/asset.rs | 2 + .../src/rpc/display_options.rs | 9 + digital_asset_types/src/rpc/filter.rs | 3 +- digital_asset_types/src/rpc/mod.rs | 1 + digital_asset_types/src/rpc/response.rs | 2 + 20 files changed, 739 insertions(+), 178 deletions(-) create mode 100644 digital_asset_types/src/rpc/display_options.rs diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs index 5b98c1340..e7ffa95bb 100644 --- a/das_api/src/api/api_impl.rs +++ b/das_api/src/api/api_impl.rs @@ -4,19 +4,23 @@ use digital_asset_types::{ sea_orm_active_enums::{ OwnerType, RoyaltyTargetType, SpecificationAssetClass, SpecificationVersions, }, - SearchAssetsQuery, + Cursor, PageOptions, SearchAssetsQuery, }, dapi::{ - get_asset, get_assets_by_authority, get_assets_by_creator, get_assets_by_group, - get_assets_by_owner, get_proof_for_asset, search_assets, + get_asset, get_asset_batch, get_asset_proof_batch, get_assets_by_authority, + get_assets_by_creator, get_assets_by_group, get_assets_by_owner, get_proof_for_asset, + search_assets, + }, + rpc::{ + filter::{AssetSortBy, SearchConditionType}, + response::GetGroupingResponse, }, - rpc::{filter::SearchConditionType, response::GetGroupingResponse}, rpc::{OwnershipModel, RoyaltyModel}, }; use open_rpc_derive::document_rpc; use sea_orm::{sea_query::ConditionType, ConnectionTrait, DbBackend, Statement}; -use crate::validation::validate_opt_pubkey; +use crate::validation::{validate_opt_pubkey, validate_search_with_name}; use open_rpc_schema::document::OpenrpcDocument; use { crate::api::*, @@ -46,21 +50,37 @@ impl DasApi { }) } + fn get_cursor(&self, cursor: &Option) -> Result { + match cursor { + Some(cursor_b64) => { + let cursor_vec = bs58::decode(cursor_b64) + .into_vec() + .map_err(|_| DasApiError::CursorValidationError(cursor_b64.clone()))?; + let cursor_struct = Cursor { + id: Some(cursor_vec), + }; + Ok(cursor_struct) + } + None => Ok(Cursor::default()), + } + } + fn validate_pagination( &self, limit: &Option, page: &Option, before: &Option, after: &Option, - ) -> Result<(), DasApiError> { - if page.is_none() && before.is_none() && after.is_none() { - return Err(DasApiError::PaginationEmptyError); - } + cursor: &Option, + sorting: &Option<&AssetSorting>, + ) -> Result { + let mut is_cursor_enabled = true; + let mut page_opt = PageOptions::default(); if let Some(limit) = limit { // make config item if *limit > 1000 { - return Err(DasApiError::PaginationError); + return Err(DasApiError::PaginationExceededError); } } @@ -70,20 +90,57 @@ impl DasApi { } // make config item - if before.is_some() || after.is_some() { + if before.is_some() || after.is_some() || cursor.is_some() { return Err(DasApiError::PaginationError); } + + is_cursor_enabled = false; } if let Some(before) = before { + if cursor.is_some() { + return Err(DasApiError::PaginationError); + } + if let Some(sort) = &sorting { + if sort.sort_by != AssetSortBy::Id { + return Err(DasApiError::PaginationSortingValidationError); + } + } validate_pubkey(before.clone())?; + is_cursor_enabled = false; } if let Some(after) = after { + if cursor.is_some() { + return Err(DasApiError::PaginationError); + } + if let Some(sort) = &sorting { + if sort.sort_by != AssetSortBy::Id { + return Err(DasApiError::PaginationSortingValidationError); + } + } validate_pubkey(after.clone())?; + is_cursor_enabled = false; } - Ok(()) + page_opt.limit = limit.map(|x| x as u64).unwrap_or(1000); + if is_cursor_enabled { + if let Some(sort) = &sorting { + if sort.sort_by != AssetSortBy::Id { + return Err(DasApiError::PaginationSortingValidationError); + } + page_opt.cursor = Some(self.get_cursor(&cursor)?); + } + } else { + page_opt.page = page.map(|x| x as u64); + page_opt.before = before + .clone() + .map(|x| bs58::decode(x).into_vec().unwrap_or_default()); + page_opt.after = after + .clone() + .map(|x| bs58::decode(x).into_vec().unwrap_or_default()); + } + Ok(page_opt) } } @@ -121,14 +178,76 @@ impl ApiContract for DasApi { .map_err(Into::into) } + async fn get_asset_proof_batch( + self: &DasApi, + payload: GetAssetProofBatch, + ) -> Result>, DasApiError> { + let GetAssetProofBatch { ids } = payload; + + let batch_size = ids.len(); + if batch_size > 1000 { + return Err(DasApiError::BatchSizeExceededError); + } + + let id_bytes = ids + .iter() + .map(|id| validate_pubkey(id.clone()).map(|id| id.to_bytes().to_vec())) + .collect::>, _>>()?; + + let proofs = get_asset_proof_batch(&self.db_connection, id_bytes).await?; + + let result: HashMap> = ids + .iter() + .map(|id| (id.clone(), proofs.get(id).cloned())) + .collect(); + Ok(result) + } + async fn get_asset(self: &DasApi, payload: GetAsset) -> Result { - let id = validate_pubkey(payload.id.clone())?; - let id_bytes = id.to_bytes().to_vec(); - get_asset(&self.db_connection, id_bytes) + let GetAsset { + id, + display_options, + } = payload; + let id_bytes = validate_pubkey(id.clone())?.to_bytes().to_vec(); + let display_options = display_options.unwrap_or_default(); + get_asset(&self.db_connection, id_bytes, &display_options.into()) .await .map_err(Into::into) } + async fn get_asset_batch( + self: &DasApi, + payload: GetAssetBatch, + ) -> Result>, DasApiError> { + let GetAssetBatch { + ids, + display_options, + } = payload; + + let batch_size = ids.len(); + if batch_size > 1000 { + return Err(DasApiError::BatchSizeExceededError); + } + + let id_bytes = ids + .iter() + .map(|id| validate_pubkey(id.clone()).map(|id| id.to_bytes().to_vec())) + .collect::>, _>>()?; + + let display_options = display_options.unwrap_or_default(); + + let assets = get_asset_batch( + &self.db_connection, + id_bytes, + batch_size as u64, + &display_options.into(), + ) + .await?; + + let result: Vec> = ids.iter().map(|id| assets.get(id).cloned()).collect(); + Ok(result) + } + async fn get_assets_by_owner( self: &DasApi, payload: GetAssetsByOwner, @@ -140,21 +259,23 @@ impl ApiContract for DasApi { page, before, after, + display_options, + cursor, } = payload; let before: Option = before.filter(|before| !before.is_empty()); let after: Option = after.filter(|after| !after.is_empty()); let owner_address = validate_pubkey(owner_address.clone())?; let owner_address_bytes = owner_address.to_bytes().to_vec(); let sort_by = sort_by.unwrap_or_default(); - self.validate_pagination(&limit, &page, &before, &after)?; + let display_options = display_options.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; get_assets_by_owner( &self.db_connection, owner_address_bytes, sort_by, - limit.map(|x| x as u64).unwrap_or(1000), - page.map(|x| x as u64), - before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), - after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), + &page_options, + &display_options, ) .await .map_err(Into::into) @@ -172,20 +293,22 @@ impl ApiContract for DasApi { page, before, after, + display_options, + cursor, } = payload; let before: Option = before.filter(|before| !before.is_empty()); let after: Option = after.filter(|after| !after.is_empty()); let sort_by = sort_by.unwrap_or_default(); - self.validate_pagination(&limit, &page, &before, &after)?; + let display_options = display_options.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; get_assets_by_group( &self.db_connection, group_key, group_value, sort_by, - limit.map(|x| x as u64).unwrap_or(1000), - page.map(|x| x as u64), - before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), - after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), + &page_options, + &display_options, ) .await .map_err(Into::into) @@ -203,22 +326,24 @@ impl ApiContract for DasApi { page, before, after, + display_options, + cursor, } = payload; let creator_address = validate_pubkey(creator_address.clone())?; let creator_address_bytes = creator_address.to_bytes().to_vec(); - self.validate_pagination(&limit, &page, &before, &after)?; let sort_by = sort_by.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; let only_verified = only_verified.unwrap_or_default(); + let display_options = display_options.unwrap_or_default(); get_assets_by_creator( &self.db_connection, creator_address_bytes, only_verified, sort_by, - limit.map(|x| x as u64).unwrap_or(1000), - page.map(|x| x as u64), - before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), - after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), + &page_options, + &display_options, ) .await .map_err(Into::into) @@ -235,20 +360,22 @@ impl ApiContract for DasApi { page, before, after, + display_options, + cursor, } = payload; let sort_by = sort_by.unwrap_or_default(); let authority_address = validate_pubkey(authority_address.clone())?; let authority_address_bytes = authority_address.to_bytes().to_vec(); + let display_options = display_options.unwrap_or_default(); - self.validate_pagination(&limit, &page, &before, &after)?; + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; get_assets_by_authority( &self.db_connection, authority_address_bytes, sort_by, - limit.map(|x| x as u64).unwrap_or(1000), - page.map(|x| x as u64), - before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), - after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), + &page_options, + &display_options, ) .await .map_err(Into::into) @@ -282,9 +409,13 @@ impl ApiContract for DasApi { before, after, json_uri, + display_options, + cursor, + name, } = payload; + + // Deserialize search assets query - self.validate_pagination(&limit, &page, &before, &after)?; let spec: Option<(SpecificationVersions, SpecificationAssetClass)> = interface.map(|x| x.into()); let specification_version = spec.clone().map(|x| x.0); @@ -294,6 +425,7 @@ impl ApiContract for DasApi { SearchConditionType::All => ConditionType::All, }); let owner_address = validate_opt_pubkey(&owner_address)?; + let name = validate_search_with_name(&name, &owner_address)?; let creator_address = validate_opt_pubkey(&creator_address)?; let delegate = validate_opt_pubkey(&delegate)?; @@ -332,17 +464,19 @@ impl ApiContract for DasApi { royalty_amount, burnt, json_uri, + name, }; + let display_options = display_options.unwrap_or_default(); let sort_by = sort_by.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; // Execute query search_assets( &self.db_connection, saq, sort_by, - limit.map(|x| x as u64).unwrap_or(1000), - page.map(|x| x as u64), - before.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), - after.map(|x| bs58::decode(x).into_vec().unwrap_or_default()), + &page_options, + &display_options, ) .await .map_err(Into::into) diff --git a/das_api/src/api/mod.rs b/das_api/src/api/mod.rs index 788c2adf2..3c8ad5f82 100644 --- a/das_api/src/api/mod.rs +++ b/das_api/src/api/mod.rs @@ -1,5 +1,6 @@ use crate::DasApiError; use async_trait::async_trait; +use digital_asset_types::rpc::display_options::DisplayOptions; use digital_asset_types::rpc::filter::SearchConditionType; use digital_asset_types::rpc::response::AssetList; use digital_asset_types::rpc::{filter::AssetSorting, response::GetGroupingResponse}; @@ -7,6 +8,7 @@ use digital_asset_types::rpc::{Asset, AssetProof, Interface, OwnershipModel, Roy use open_rpc_derive::{document_rpc, rpc}; use open_rpc_schema::schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; mod api_impl; pub use api_impl::*; @@ -21,6 +23,10 @@ pub struct GetAssetsByGroup { pub page: Option, pub before: Option, pub after: Option, + #[serde(default)] + pub display_options: Option, + #[serde(default)] + pub cursor: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -32,12 +38,26 @@ pub struct GetAssetsByOwner { pub page: Option, pub before: Option, pub after: Option, + #[serde(default)] + pub display_options: Option, + #[serde(default)] + pub cursor: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GetAsset { pub id: String, + #[serde(default)] + pub display_options: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetAssetBatch { + pub ids: Vec, + #[serde(default)] + pub display_options: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -46,6 +66,12 @@ pub struct GetAssetProof { pub id: String, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetAssetProofBatch { + pub ids: Vec, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GetAssetsByCreator { @@ -56,6 +82,10 @@ pub struct GetAssetsByCreator { pub page: Option, pub before: Option, pub after: Option, + #[serde(default)] + pub display_options: Option, + #[serde(default)] + pub cursor: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -87,6 +117,12 @@ pub struct SearchAssets { pub after: Option, #[serde(default)] pub json_uri: Option, + #[serde(default)] + pub display_options: Option, + #[serde(default)] + pub cursor: Option, + #[serde(default)] + pub name: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -98,6 +134,10 @@ pub struct GetAssetsByAuthority { pub page: Option, pub before: Option, pub after: Option, + #[serde(default)] + pub display_options: Option, + #[serde(default)] + pub cursor: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] @@ -117,12 +157,30 @@ pub trait ApiContract: Send + Sync + 'static { summary = "Get a merkle proof for a compressed asset by its ID" )] async fn get_asset_proof(&self, payload: GetAssetProof) -> Result; + #[rpc( + name = "getAssetProofBatch", + params = "named", + summary = "Get merkle proofs for compressed assets by their IDs" + )] + async fn get_asset_proof_batch( + &self, + payload: GetAssetProofBatch, + ) -> Result>, DasApiError>; #[rpc( name = "getAsset", params = "named", summary = "Get an asset by its ID" )] async fn get_asset(&self, payload: GetAsset) -> Result; + #[rpc( + name = "getAssetBatch", + params = "named", + summary = "Get assets by their IDs" + )] + async fn get_asset_batch( + &self, + payload: GetAssetBatch, + ) -> Result>, DasApiError>; #[rpc( name = "getAssetsByOwner", params = "named", diff --git a/das_api/src/builder.rs b/das_api/src/builder.rs index 342d2fba3..e5ff9bb48 100644 --- a/das_api/src/builder.rs +++ b/das_api/src/builder.rs @@ -22,12 +22,33 @@ impl RpcApiBuilder { })?; module.register_alias("getAssetProof", "get_asset_proof")?; + module.register_async_method( + "get_asset_proof_batch", + |rpc_params, rpc_context| async move { + let payload = rpc_params.parse::()?; + rpc_context + .get_asset_proof_batch(payload) + .await + .map_err(Into::into) + }, + )?; + module.register_alias("getAssetProofBatch", "get_asset_proof_batch")?; + module.register_async_method("get_asset", |rpc_params, rpc_context| async move { let payload = rpc_params.parse::()?; rpc_context.get_asset(payload).await.map_err(Into::into) })?; module.register_alias("getAsset", "get_asset")?; + module.register_async_method("get_asset_batch", |rpc_params, rpc_context| async move { + let payload = rpc_params.parse::()?; + rpc_context + .get_asset_batch(payload) + .await + .map_err(Into::into) + })?; + module.register_alias("getAssetBatch", "get_asset_batch")?; + module.register_async_method( "get_assets_by_owner", |rpc_params, rpc_context| async move { diff --git a/das_api/src/error.rs b/das_api/src/error.rs index 3640da765..4b009ceac 100644 --- a/das_api/src/error.rs +++ b/das_api/src/error.rs @@ -22,6 +22,14 @@ pub enum DasApiError { PaginationEmptyError, #[error("Deserialization error: {0}")] DeserializationError(#[from] serde_json::Error), + #[error("Batch Size Error. Batch size should not be greater than 1000.")] + BatchSizeExceededError, + #[error("Pagination Error. Limit should not be greater than 1000.")] + PaginationExceededError, + #[error("Cursor Validation Err: {0} is invalid")] + CursorValidationError(String), + #[error("Pagination Sorting Error. Only sorting based on id is support for this pagination")] + PaginationSortingValidationError, } impl Into for DasApiError { diff --git a/das_api/src/validation.rs b/das_api/src/validation.rs index b3377a7b4..394463208 100644 --- a/das_api/src/validation.rs +++ b/das_api/src/validation.rs @@ -6,6 +6,23 @@ pub fn validate_pubkey(str_pubkey: String) -> Result { Pubkey::from_str(&str_pubkey).map_err(|_| DasApiError::PubkeyValidationError(str_pubkey)) } +pub fn validate_search_with_name( + name: &Option, + owner: &Option>, +) -> Result>, DasApiError> { + let opt_name = if let Some(n) = name { + if owner.is_none() { + return Err(DasApiError::ValidationError( + "Owner address must be provided in order to search assets by name".to_owned(), + )); + } + Some(n.clone().into_bytes()) + } else { + None + }; + Ok(opt_name) +} + pub fn validate_opt_pubkey(pubkey: &Option) -> Result>, DasApiError> { let opt_bytes = if let Some(pubkey) = pubkey { let pubkey = Pubkey::from_str(pubkey) diff --git a/digital_asset_types/src/dao/mod.rs b/digital_asset_types/src/dao/mod.rs index e30c92755..2622c3659 100644 --- a/digital_asset_types/src/dao/mod.rs +++ b/digital_asset_types/src/dao/mod.rs @@ -2,23 +2,37 @@ mod full_asset; mod generated; pub mod scopes; -pub use full_asset::*; -pub use generated::*; - use self::sea_orm_active_enums::{ OwnerType, RoyaltyTargetType, SpecificationAssetClass, SpecificationVersions, }; +pub use full_asset::*; +pub use generated::*; use sea_orm::{ entity::*, sea_query::Expr, - sea_query::{ConditionType, IntoCondition}, + sea_query::{ConditionType, IntoCondition, SimpleExpr}, Condition, DbErr, RelationDef, }; +use serde::{Deserialize, Serialize}; pub struct GroupingSize { pub size: u64, } +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct PageOptions { + pub limit: u64, + pub page: Option, + pub before: Option>, + pub after: Option>, + pub cursor: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)] +pub struct Cursor { + pub id: Option>, +} + pub enum Pagination { Keyset { before: Option>, @@ -27,6 +41,7 @@ pub enum Pagination { Page { page: u64, }, + Cursor(Cursor), } #[derive(Debug, Clone, PartialEq)] @@ -54,6 +69,7 @@ pub struct SearchAssetsQuery { pub royalty_amount: Option, pub burnt: Option, pub json_uri: Option, + pub name: Option>, } impl SearchAssetsQuery { @@ -115,6 +131,9 @@ impl SearchAssetsQuery { if self.json_uri.is_some() { num_conditions += 1; } + if self.name.is_some() { + num_conditions += 1; + } num_conditions } @@ -245,6 +264,28 @@ impl SearchAssetsQuery { joins.push(rel); } + if let Some(n) = self.name.to_owned() { + let name_as_str = std::str::from_utf8(&n).map_err(|_| { + DbErr::Custom( + "Could not convert raw name bytes into string for comparison".to_owned(), + ) + })?; + + let name_expr = + SimpleExpr::Custom(format!("chain_data->>'name' LIKE '%{}%'", name_as_str).into()); + + conditions = conditions.add(name_expr); + let rel = asset_data::Relation::Asset + .def() + .rev() + .on_condition(|left, right| { + Expr::tbl(right, asset_data::Column::Id) + .eq(Expr::tbl(left, asset::Column::AssetData)) + .into_condition() + }); + joins.push(rel); + } + Ok(( match self.negate { None | Some(false) => conditions, diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs index cd9db02ba..8be2ff072 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -1,29 +1,32 @@ -use crate::{ - dao::{ - asset::{self, Entity}, - asset_authority, asset_creators, asset_data, asset_grouping, FullAsset, - GroupingSize, Pagination, - }, - dapi::common::safe_select, - rpc::{response::AssetList}, +use crate::dao::{ + asset::{self}, + asset_authority, asset_creators, asset_data, asset_grouping, Cursor, FullAsset, GroupingSize, + Pagination, }; use indexmap::IndexMap; use sea_orm::{entity::*, query::*, ConnectionTrait, DbErr, Order}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; -pub fn paginate<'db, T>(pagination: &Pagination, limit: u64, stmt: T) -> T +pub fn paginate<'db, T, C>( + pagination: &Pagination, + limit: u64, + stmt: T, + sort_direction: Order, + column: C, +) -> T where T: QueryFilter + QuerySelect, + C: ColumnTrait, { let mut stmt = stmt; match pagination { Pagination::Keyset { before, after } => { if let Some(b) = before { - stmt = stmt.filter(asset::Column::Id.lt(b.clone())); + stmt = stmt.filter(column.lt(b.clone())); } if let Some(a) = after { - stmt = stmt.filter(asset::Column::Id.gt(a.clone())); + stmt = stmt.filter(column.gt(a.clone())); } } Pagination::Page { page } => { @@ -31,6 +34,15 @@ where stmt = stmt.offset((page - 1) * limit) } } + Pagination::Cursor(cursor) => { + if *cursor != Cursor::default() { + if sort_direction == sea_orm::Order::Asc { + stmt = stmt.filter(column.gt(cursor.id.clone())); + } else { + stmt = stmt.filter(column.lt(cursor.id.clone())); + } + } + } } stmt.limit(limit) } @@ -43,6 +55,7 @@ pub async fn get_by_creator( sort_direction: Order, pagination: &Pagination, limit: u64, + show_unverified_collections: bool, ) -> Result, DbErr> { let mut condition = Condition::all() .add(asset_creators::Column::Creator.eq(creator)) @@ -58,6 +71,7 @@ pub async fn get_by_creator( sort_direction, pagination, limit, + show_unverified_collections, ) .await } @@ -91,15 +105,20 @@ pub async fn get_by_grouping( sort_direction: Order, pagination: &Pagination, limit: u64, + show_unverified_collections: bool, ) -> Result, DbErr> { - let condition = asset_grouping::Column::GroupKey + let mut condition = asset_grouping::Column::GroupKey .eq(group_key) - .and(asset_grouping::Column::GroupValue.eq(group_value)) - .and( + .and(asset_grouping::Column::GroupValue.eq(group_value)); + + if !show_unverified_collections { + condition = condition.and( asset_grouping::Column::Verified .eq(true) .or(asset_grouping::Column::Verified.is_null()), ); + } + get_by_related_condition( conn, Condition::all() @@ -110,6 +129,7 @@ pub async fn get_by_grouping( sort_direction, pagination, limit, + show_unverified_collections, ) .await } @@ -121,6 +141,7 @@ pub async fn get_assets_by_owner( sort_direction: Order, pagination: &Pagination, limit: u64, + show_unverified_collections: bool, ) -> Result, DbErr> { let cond = Condition::all() .add(asset::Column::Owner.eq(owner)) @@ -133,6 +154,30 @@ pub async fn get_assets_by_owner( sort_direction, pagination, limit, + show_unverified_collections, + ) + .await +} + +pub async fn get_asset_batch( + conn: &impl ConnectionTrait, + asset_ids: Vec>, + pagination: &Pagination, + limit: u64, +) -> Result, DbErr> { + let cond = Condition::all() + .add(asset::Column::Id.is_in(asset_ids)) + .add(asset::Column::Supply.gt(0)); + get_assets_by_condition( + conn, + cond, + vec![], + // Default values provided. The args below are not used for batch requests + None, + Order::Asc, + pagination, + limit, + false, ) .await } @@ -144,6 +189,7 @@ pub async fn get_by_authority( sort_direction: Order, pagination: &Pagination, limit: u64, + show_unverified_collections: bool, ) -> Result, DbErr> { let cond = Condition::all() .add(asset_authority::Column::Authority.eq(authority)) @@ -156,6 +202,7 @@ pub async fn get_by_authority( sort_direction, pagination, limit, + show_unverified_collections, ) .await } @@ -168,6 +215,7 @@ async fn get_by_related_condition( sort_direction: Order, pagination: &Pagination, limit: u64, + show_unverified_collections: bool, ) -> Result, DbErr> where E: RelationTrait, @@ -179,16 +227,19 @@ where if let Some(col) = sort_by { stmt = stmt .order_by(col, sort_direction.clone()) - .order_by(asset::Column::Id, sort_direction); + .order_by(asset::Column::Id, sort_direction.clone()); } - let assets = paginate(pagination, limit, stmt).all(conn).await?; - get_related_for_assets(conn, assets).await + let assets = paginate(pagination, limit, stmt, sort_direction, asset::Column::Id) + .all(conn) + .await?; + get_related_for_assets(conn, assets, show_unverified_collections).await } pub async fn get_related_for_assets( conn: &impl ConnectionTrait, assets: Vec, + show_unverified_collections: bool, ) -> Result, DbErr> { let asset_ids = assets.iter().map(|a| a.id.clone()).collect::>(); @@ -244,16 +295,20 @@ pub async fn get_related_for_assets( } } + let cond = if show_unverified_collections { + Condition::all() + } else { + Condition::any() + .add(asset_grouping::Column::Verified.eq(true)) + // Older versions of the indexer did not have the verified flag. A group would be present if and only if it was verified. + // Therefore if verified is null, we can assume that the group is verified. + .add(asset_grouping::Column::Verified.is_null()) + }; + let grouping = asset_grouping::Entity::find() .filter(asset_grouping::Column::AssetId.is_in(ids.clone())) .filter(asset_grouping::Column::GroupValue.is_not_null()) - .filter( - Condition::any() - .add(asset_grouping::Column::Verified.eq(true)) - // Older versions of the indexer did not have the verified flag. A group would be present if and only if it was verified. - // Therefore if verified is null, we can assume that the group is verified. - .add(asset_grouping::Column::Verified.is_null()), - ) + .filter(cond) .order_by_asc(asset_grouping::Column::AssetId) .all(conn) .await?; @@ -274,6 +329,7 @@ pub async fn get_assets_by_condition( sort_direction: Order, pagination: &Pagination, limit: u64, + show_unverified_collections: bool, ) -> Result, DbErr> { let mut stmt = asset::Entity::find(); for def in joins { @@ -283,11 +339,13 @@ pub async fn get_assets_by_condition( if let Some(col) = sort_by { stmt = stmt .order_by(col, sort_direction.clone()) - .order_by(asset::Column::Id, sort_direction); + .order_by(asset::Column::Id, sort_direction.clone()); } - let assets = paginate(pagination, limit, stmt).all(conn).await?; - let full_assets = get_related_for_assets(conn, assets).await?; + let assets = paginate(pagination, limit, stmt, sort_direction, asset::Column::Id) + .all(conn) + .await?; + let full_assets = get_related_for_assets(conn, assets, show_unverified_collections).await?; Ok(full_assets) } diff --git a/digital_asset_types/src/dapi/assets_by_authority.rs b/digital_asset_types/src/dapi/assets_by_authority.rs index 525881146..774b23b3e 100644 --- a/digital_asset_types/src/dapi/assets_by_authority.rs +++ b/digital_asset_types/src/dapi/assets_by_authority.rs @@ -1,4 +1,6 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; use sea_orm::DatabaseConnection; @@ -10,12 +12,10 @@ pub async fn get_assets_by_authority( db: &DatabaseConnection, authority: Vec, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, + page_options: &PageOptions, + display_options: &DisplayOptions, ) -> Result { - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; let (sort_direction, sort_column) = create_sorting(sorting); let assets = scopes::asset::get_by_authority( db, @@ -23,8 +23,14 @@ pub async fn get_assets_by_authority( sort_column, sort_direction, &pagination, - limit, + page_options.limit, + display_options.show_unverified_collections, ) .await?; - Ok(build_asset_response(assets, limit, &pagination)) + Ok(build_asset_response( + assets, + page_options.limit, + &pagination, + display_options, + )) } diff --git a/digital_asset_types/src/dapi/assets_by_creator.rs b/digital_asset_types/src/dapi/assets_by_creator.rs index e69a12f92..6df93cf26 100644 --- a/digital_asset_types/src/dapi/assets_by_creator.rs +++ b/digital_asset_types/src/dapi/assets_by_creator.rs @@ -1,4 +1,6 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; use sea_orm::DatabaseConnection; @@ -11,12 +13,10 @@ pub async fn get_assets_by_creator( creator: Vec, only_verified: bool, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, + page_options: &PageOptions, + display_options: &DisplayOptions, ) -> Result { - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; let (sort_direction, sort_column) = create_sorting(sorting); let assets = scopes::asset::get_by_creator( db, @@ -25,8 +25,14 @@ pub async fn get_assets_by_creator( sort_column, sort_direction, &pagination, - limit, + page_options.limit, + display_options.show_unverified_collections, ) .await?; - Ok(build_asset_response(assets, limit, &pagination)) + Ok(build_asset_response( + assets, + page_options.limit, + &pagination, + display_options, + )) } diff --git a/digital_asset_types/src/dapi/assets_by_group.rs b/digital_asset_types/src/dapi/assets_by_group.rs index 1e655b6ab..4a9472799 100644 --- a/digital_asset_types/src/dapi/assets_by_group.rs +++ b/digital_asset_types/src/dapi/assets_by_group.rs @@ -1,4 +1,6 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; use sea_orm::DatabaseConnection; @@ -10,12 +12,10 @@ pub async fn get_assets_by_group( group_key: String, group_value: String, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, + page_options: &PageOptions, + display_options: &DisplayOptions, ) -> Result { - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; let (sort_direction, sort_column) = create_sorting(sorting); let assets = scopes::asset::get_by_grouping( db, @@ -24,8 +24,14 @@ pub async fn get_assets_by_group( sort_column, sort_direction, &pagination, - limit, + page_options.limit, + display_options.show_unverified_collections, ) .await?; - Ok(build_asset_response(assets, limit, &pagination)) + Ok(build_asset_response( + assets, + page_options.limit, + &pagination, + display_options, + )) } diff --git a/digital_asset_types/src/dapi/assets_by_owner.rs b/digital_asset_types/src/dapi/assets_by_owner.rs index c2d9cce0e..9e7b3bc89 100644 --- a/digital_asset_types/src/dapi/assets_by_owner.rs +++ b/digital_asset_types/src/dapi/assets_by_owner.rs @@ -1,4 +1,6 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; use sea_orm::DatabaseConnection; @@ -10,12 +12,10 @@ pub async fn get_assets_by_owner( db: &DatabaseConnection, owner_address: Vec, sort_by: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, + page_options: &PageOptions, + display_options: &DisplayOptions, ) -> Result { - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; let (sort_direction, sort_column) = create_sorting(sort_by); let assets = scopes::asset::get_assets_by_owner( db, @@ -23,8 +23,14 @@ pub async fn get_assets_by_owner( sort_column, sort_direction, &pagination, - limit, + page_options.limit, + display_options.show_unverified_collections, ) .await?; - Ok(build_asset_response(assets, limit, &pagination)) + Ok(build_asset_response( + assets, + page_options.limit, + &pagination, + display_options, + )) } diff --git a/digital_asset_types/src/dapi/change_logs.rs b/digital_asset_types/src/dapi/change_logs.rs index a710c0a5a..11e1aa5ca 100644 --- a/digital_asset_types/src/dapi/change_logs.rs +++ b/digital_asset_types/src/dapi/change_logs.rs @@ -1,6 +1,6 @@ -use log::debug; use sea_orm::sea_query::Expr; use sea_orm::{DatabaseConnection, DbBackend}; +use std::collections::HashMap; use { crate::dao::asset, crate::dao::cl_items, @@ -15,6 +15,22 @@ struct SimpleChangeLog { level: i64, node_idx: i64, seq: i64, + tree: Vec, +} + +#[derive(FromQueryResult, Debug, Default, Clone, Eq, PartialEq)] +struct LeafInfo { + id: Vec, + tree_id: Vec, + leaf_idx: i64, + node_idx: i64, + hash: Vec, +} + +#[derive(Hash, Debug, Default, Clone, Eq, PartialEq)] +struct Leaf { + tree_id: Vec, + leaf_idx: i64, } pub async fn get_proof_for_asset( @@ -42,9 +58,6 @@ pub async fn get_proof_for_asset( } let leaf = leaf.unwrap(); let req_indexes = get_required_nodes_for_proof(leaf.node_idx); - let expected_proof_size = req_indexes.len(); - let mut final_node_list: Vec = - vec![SimpleChangeLog::default(); expected_proof_size]; let mut query = cl_items::Entity::find() .select_only() .column(cl_items::Column::NodeIdx) @@ -61,48 +74,169 @@ pub async fn get_proof_for_asset( query.sql = query .sql .replace("SELECT", "SELECT DISTINCT ON (cl_items.node_idx)"); + let required_nodes: Vec = db.query_all(query).await.map(|qr| { + qr.iter() + .map(|q| SimpleChangeLog::from_query_result(q, "").unwrap()) + .collect() + })?; + let asset_proof = build_asset_proof( + leaf.tree, + leaf.node_idx, + leaf.hash, + &req_indexes, + &required_nodes, + ); + Ok(asset_proof) +} + +pub async fn get_asset_proof_batch( + db: &DatabaseConnection, + asset_ids: Vec>, +) -> Result, DbErr> { + // get the leaves (JOIN with `asset` table to get the asset ids) + let q = asset::Entity::find() + .join( + JoinType::InnerJoin, + asset::Entity::belongs_to(cl_items::Entity) + .from(asset::Column::Nonce) + .to(cl_items::Column::LeafIdx) + .into(), + ) + .select_only() + .column(asset::Column::Id) + .column(asset::Column::TreeId) + .column(cl_items::Column::LeafIdx) + .column(cl_items::Column::NodeIdx) + .column(cl_items::Column::Hash) + .filter(Expr::cust("asset.tree_id = cl_items.tree")) + // filter by user provided asset ids + .filter(asset::Column::Id.is_in(asset_ids.clone())) + .build(DbBackend::Postgres); + let leaves: Vec = db.query_all(q).await.map(|qr| { + qr.iter() + .map(|q| LeafInfo::from_query_result(q, "").unwrap()) + .collect() + })?; + + let mut asset_map: HashMap = HashMap::new(); + for l in &leaves { + let key = Leaf { + tree_id: l.tree_id.clone(), + leaf_idx: l.leaf_idx, + }; + asset_map.insert(key, l.clone()); + } + + // map: (tree_id, leaf_idx) -> [...req_indexes] + let mut tree_indexes: HashMap> = HashMap::new(); + for leaf in &leaves { + let key = Leaf { + tree_id: leaf.tree_id.clone(), + leaf_idx: leaf.leaf_idx, + }; + let req_indexes = get_required_nodes_for_proof(leaf.node_idx); + tree_indexes.insert(key, req_indexes); + } + + // get the required nodes for all assets + // SELECT * FROM cl_items WHERE (tree = ? AND node_idx IN (?)) OR (tree = ? AND node_idx IN (?)) OR ... + let mut condition = Condition::any(); + for (leaf, req_indexes) in &tree_indexes { + let cond = Condition::all() + .add(cl_items::Column::Tree.eq(leaf.tree_id.clone())) + .add(cl_items::Column::NodeIdx.is_in(req_indexes.clone())); + condition = condition.add(cond); + } + let query = cl_items::Entity::find() + .select_only() + .column(cl_items::Column::Tree) + .column(cl_items::Column::NodeIdx) + .column(cl_items::Column::Level) + .column(cl_items::Column::Seq) + .column(cl_items::Column::Hash) + .filter(condition) + .build(DbBackend::Postgres); let nodes: Vec = db.query_all(query).await.map(|qr| { qr.iter() .map(|q| SimpleChangeLog::from_query_result(q, "").unwrap()) .collect() })?; - for node in nodes.iter() { + + // map: (tree, node_idx) -> SimpleChangeLog + let mut node_map: HashMap<(Vec, i64), SimpleChangeLog> = HashMap::new(); + for node in nodes { + let key = (node.tree.clone(), node.node_idx); + node_map.insert(key, node); + } + + // construct the proofs + let mut asset_proofs: HashMap = HashMap::new(); + for (leaf, req_indexes) in &tree_indexes { + let required_nodes: Vec = req_indexes + .iter() + .filter_map(|n| { + let key = (leaf.tree_id.clone(), *n); + node_map.get(&key).cloned() + }) + .collect(); + + let leaf_info = asset_map.get(&leaf).unwrap(); + let asset_proof = build_asset_proof( + leaf_info.tree_id.clone(), + leaf_info.node_idx, + leaf_info.hash.clone(), + &req_indexes, + &required_nodes, + ); + + let asset_id = bs58::encode(leaf_info.id.to_owned()).into_string(); + asset_proofs.insert(asset_id, asset_proof); + } + + Ok(asset_proofs) +} + +fn build_asset_proof( + tree_id: Vec, + leaf_node_idx: i64, + leaf_hash: Vec, + req_indexes: &Vec, + required_nodes: &Vec, +) -> AssetProof { + let mut final_node_list = vec![SimpleChangeLog::default(); req_indexes.len()]; + for node in required_nodes.iter() { if node.level < final_node_list.len().try_into().unwrap() { final_node_list[node.level as usize] = node.to_owned(); } } - for (i, (n, nin)) in final_node_list.iter_mut().zip(req_indexes).enumerate() { + for (i, (n, nin)) in final_node_list + .iter_mut() + .zip(req_indexes.clone()) + .enumerate() + { if *n == SimpleChangeLog::default() { - *n = make_empty_node(i as i64, nin); + *n = make_empty_node(i as i64, nin, tree_id.clone()); } } - for n in final_node_list.iter() { - debug!( - "level {} index {} seq {} hash {}", - n.level, - n.node_idx, - n.seq, - bs58::encode(&n.hash).into_string() - ); - } - Ok(AssetProof { + AssetProof { root: bs58::encode(final_node_list.pop().unwrap().hash).into_string(), - leaf: bs58::encode(&leaf.hash).into_string(), + leaf: bs58::encode(leaf_hash).into_string(), proof: final_node_list .iter() .map(|model| bs58::encode(&model.hash).into_string()) .collect(), - node_index: leaf.node_idx, - tree_id: bs58::encode(&leaf.tree).into_string(), - }) + node_index: leaf_node_idx, + tree_id: bs58::encode(tree_id).into_string(), + } } -fn make_empty_node(lvl: i64, node_index: i64) -> SimpleChangeLog { +fn make_empty_node(lvl: i64, node_index: i64, tree: Vec) -> SimpleChangeLog { SimpleChangeLog { node_idx: node_index, level: lvl, hash: empty_node(lvl as u32).to_vec(), seq: 0, + tree, } } diff --git a/digital_asset_types/src/dapi/common/asset.rs b/digital_asset_types/src/dapi/common/asset.rs index 007e927b6..68be6482a 100644 --- a/digital_asset_types/src/dapi/common/asset.rs +++ b/digital_asset_types/src/dapi/common/asset.rs @@ -1,7 +1,9 @@ use crate::dao::sea_orm_active_enums::SpecificationVersions; use crate::dao::FullAsset; +use crate::dao::PageOptions; use crate::dao::Pagination; use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping}; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::{AssetSortBy, AssetSortDirection, AssetSorting}; use crate::rpc::response::{AssetError, AssetList}; use crate::rpc::{ @@ -48,17 +50,27 @@ pub fn build_asset_response( assets: Vec, limit: u64, pagination: &Pagination, + display_options: &DisplayOptions, ) -> AssetList { let total = assets.len() as u32; - let (page, before, after) = match pagination { + let (page, before, after, cursor) = 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) + (None, bef, aft, None) + } + Pagination::Page { page } => (Some(*page), None, None, None), + Pagination::Cursor(_) => { + if let Some(last_asset) = assets.last() { + let cursor_str = bs58::encode(&last_asset.asset.id.clone()).into_string(); + (None, None, None, Some(cursor_str)) + } else { + (None, None, None, None) + } } - Pagination::Page { page } => (Some(*page), None, None), }; - let (items, errors) = asset_list_to_rpc(assets); + + let (items, errors) = asset_list_to_rpc(assets, display_options); AssetList { total, limit: limit as u32, @@ -67,11 +79,13 @@ pub fn build_asset_response( after, items, errors, + cursor, } } pub fn create_sorting(sorting: AssetSorting) -> (sea_orm::query::Order, Option) { let sort_column = match sorting.sort_by { + AssetSortBy::Id => Some(asset::Column::Id), AssetSortBy::Created => Some(asset::Column::CreatedAt), AssetSortBy::Updated => Some(asset::Column::SlotUpdated), AssetSortBy::RecentAction => Some(asset::Column::SlotUpdated), @@ -84,18 +98,22 @@ pub fn create_sorting(sorting: AssetSorting) -> (sea_orm::query::Order, Option>, - after: Option>, - page: Option, -) -> Result { - match (&before, &after, &page) { - (_, _, None) => Ok(Pagination::Keyset { - before: before.map(|x| x.into()), - after: after.map(|x| x.into()), - }), - (None, None, Some(p)) => Ok(Pagination::Page { page: *p }), - _ => Err(DbErr::Custom("Invalid Pagination".to_string())), +pub fn create_pagination(page_options: &PageOptions) -> Result { + if let Some(cursor) = &page_options.cursor { + Ok(Pagination::Cursor(cursor.clone())) + } else { + match ( + page_options.before.as_ref(), + page_options.after.as_ref(), + page_options.page, + ) { + (_, _, None) => Ok(Pagination::Keyset { + before: page_options.before.clone(), + after: page_options.after.clone(), + }), + (None, None, Some(p)) => Ok(Pagination::Page { page: p }), + _ => Err(DbErr::Custom("Invalid Pagination".to_string())), + } } } @@ -150,6 +168,10 @@ pub fn v1_content_from_json(asset_data: &asset_data::Model) -> Result Result Ordering::Equal, }); - Ok(Content { schema: "https://schema.metaplex.com/nft1.0.json".to_string(), json_uri, @@ -231,10 +252,7 @@ pub fn v1_content_from_json(asset_data: &asset_data::Model) -> Result Result { +pub fn get_content(asset: &asset::Model, data: &asset_data::Model) -> Result { match asset.specification_version { Some(SpecificationVersions::V1) | Some(SpecificationVersions::V0) => { v1_content_from_json(data) @@ -265,20 +283,27 @@ pub fn to_creators(creators: Vec) -> Vec { .collect() } -pub fn to_grouping(groups: Vec) -> Result, DbErr> { - fn find_group(model: &asset_grouping::Model) -> Result { - Ok(Group { - group_key: model.group_key.clone(), - group_value: Some( - model - .group_value - .clone() - .ok_or(DbErr::Custom("Group value not found".to_string()))?, - ), +pub fn to_grouping( + groups: Vec, + display_options: &DisplayOptions, +) -> Result, DbErr> { + let result: Vec = groups + .iter() + .filter_map(|model| { + let verified = match display_options.show_unverified_collections { + // Null verified indicates legacy data, meaning it is verified. + true => Some(model.verified.unwrap_or(true)), + false => None, + }; + // Filter out items where group_value is None. + model.group_value.clone().map(|group_value| Group { + group_key: model.group_key.clone(), + group_value: Some(group_value), + verified, + }) }) - } - - groups.iter().map(find_group).collect() + .collect(); + Ok(result) } pub fn get_interface(asset: &asset::Model) -> Result { @@ -297,9 +322,7 @@ pub fn get_interface(asset: &asset::Model) -> Result { } //TODO -> impl custom error type -pub fn asset_to_rpc( - asset: FullAsset -) -> Result { +pub fn asset_to_rpc(asset: FullAsset, display_options: &DisplayOptions) -> Result { let FullAsset { asset, data, @@ -309,7 +332,7 @@ pub fn asset_to_rpc( } = asset; let rpc_authorities = to_authority(authorities); let rpc_creators = to_creators(creators); - let rpc_groups = to_grouping(groups)?; + let rpc_groups = to_grouping(groups, display_options)?; let interface = get_interface(&asset)?; let content = get_content(&asset, &data)?; let mut chain_data_selector_fn = jsonpath_lib::selector(&data.chain_data); @@ -390,13 +413,14 @@ pub fn asset_to_rpc( } pub fn asset_list_to_rpc( - asset_list: Vec + asset_list: Vec, + display_options: &DisplayOptions, ) -> (Vec, Vec) { asset_list .into_iter() .fold((vec![], vec![]), |(mut assets, mut errors), asset| { let id = bs58::encode(asset.asset.id.clone()).into_string(); - match asset_to_rpc(asset) { + match asset_to_rpc(asset, display_options) { Ok(rpc_asset) => assets.push(rpc_asset), Err(e) => errors.push(AssetError { id, diff --git a/digital_asset_types/src/dapi/get_asset.rs b/digital_asset_types/src/dapi/get_asset.rs index 110d9cb62..6056d56eb 100644 --- a/digital_asset_types/src/dapi/get_asset.rs +++ b/digital_asset_types/src/dapi/get_asset.rs @@ -1,10 +1,33 @@ +use super::common::{asset_to_rpc, build_asset_response}; +use crate::{ + dao::{scopes, Pagination}, + rpc::{display_options::DisplayOptions, Asset}, +}; use sea_orm::{DatabaseConnection, DbErr}; +use std::collections::HashMap; -use crate::{dao::scopes, rpc::Asset}; - -use super::common::asset_to_rpc; - -pub async fn get_asset(db: &DatabaseConnection, id: Vec) -> Result { +pub async fn get_asset( + db: &DatabaseConnection, + id: Vec, + display_options: &DisplayOptions, +) -> Result { let asset = scopes::asset::get_by_id(db, id, false).await?; - asset_to_rpc(asset) + asset_to_rpc(asset, display_options) +} + +pub async fn get_asset_batch( + db: &DatabaseConnection, + ids: Vec>, + limit: u64, + display_options: &DisplayOptions, +) -> Result, DbErr> { + let pagination = Pagination::Page { page: 1 }; + let assets = scopes::asset::get_asset_batch(db, ids, &pagination, limit).await?; + let asset_list = build_asset_response(assets, limit, &pagination, display_options); + let asset_map = asset_list + .items + .into_iter() + .map(|asset| (asset.id.clone(), asset)) + .collect(); + Ok(asset_map) } diff --git a/digital_asset_types/src/dapi/search_assets.rs b/digital_asset_types/src/dapi/search_assets.rs index d6998ea76..cd467ab8b 100644 --- a/digital_asset_types/src/dapi/search_assets.rs +++ b/digital_asset_types/src/dapi/search_assets.rs @@ -1,7 +1,7 @@ use super::common::{build_asset_response, create_pagination, create_sorting}; use crate::{ - dao::{scopes, SearchAssetsQuery}, - rpc::{filter::AssetSorting, response::AssetList}, + dao::{scopes, PageOptions, SearchAssetsQuery}, + rpc::{display_options::DisplayOptions, filter::AssetSorting, response::AssetList}, }; use sea_orm::{DatabaseConnection, DbErr}; @@ -9,12 +9,10 @@ pub async fn search_assets( db: &DatabaseConnection, search_assets_query: SearchAssetsQuery, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, + page_options: &PageOptions, + display_options: &DisplayOptions, ) -> Result { - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; let (sort_direction, sort_column) = create_sorting(sorting); let (condition, joins) = search_assets_query.conditions()?; let assets = scopes::asset::get_assets_by_condition( @@ -24,8 +22,14 @@ pub async fn search_assets( sort_column, sort_direction, &pagination, - limit, + page_options.limit, + display_options.show_unverified_collections, ) .await?; - Ok(build_asset_response(assets, limit, &pagination)) + Ok(build_asset_response( + assets, + page_options.limit, + &pagination, + display_options, + )) } diff --git a/digital_asset_types/src/rpc/asset.rs b/digital_asset_types/src/rpc/asset.rs index ea365e4b1..cfbb02d40 100644 --- a/digital_asset_types/src/rpc/asset.rs +++ b/digital_asset_types/src/rpc/asset.rs @@ -200,6 +200,8 @@ pub type GroupValue = String; pub struct Group { pub group_key: String, pub group_value: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub verified: Option, } #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] diff --git a/digital_asset_types/src/rpc/display_options.rs b/digital_asset_types/src/rpc/display_options.rs new file mode 100644 index 000000000..ac3efc6b6 --- /dev/null +++ b/digital_asset_types/src/rpc/display_options.rs @@ -0,0 +1,9 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema, Default)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct DisplayOptions { + #[serde(default)] + pub show_unverified_collections: bool, +} diff --git a/digital_asset_types/src/rpc/filter.rs b/digital_asset_types/src/rpc/filter.rs index f6aa010f8..0de5574b7 100644 --- a/digital_asset_types/src/rpc/filter.rs +++ b/digital_asset_types/src/rpc/filter.rs @@ -19,8 +19,9 @@ impl Default for AssetSorting { } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)] - pub enum AssetSortBy { + #[serde(rename = "id")] + Id, #[serde(rename = "created")] Created, #[serde(rename = "updated")] diff --git a/digital_asset_types/src/rpc/mod.rs b/digital_asset_types/src/rpc/mod.rs index 92325d382..a27a904b5 100644 --- a/digital_asset_types/src/rpc/mod.rs +++ b/digital_asset_types/src/rpc/mod.rs @@ -1,5 +1,6 @@ mod asset; +pub mod display_options; pub mod filter; pub mod response; diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs index 855061438..3945f6955 100644 --- a/digital_asset_types/src/rpc/response.rs +++ b/digital_asset_types/src/rpc/response.rs @@ -30,6 +30,8 @@ pub struct AssetList { pub before: Option, #[serde(skip_serializing_if = "Option::is_none")] pub after: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, pub items: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] pub errors: Vec, From 4e044d43dea0308711b0372939b78bb5008f2de3 Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Mon, 27 Nov 2023 12:51:46 -0500 Subject: [PATCH 2/3] apply helius pr#137 --- das_api/src/api/api_impl.rs | 36 +++++++++ das_api/src/api/mod.rs | 23 +++++- das_api/src/builder.rs | 6 ++ .../src/dao/generated/asset_data.rs | 10 +-- .../src/dao/generated/asset_grouping.rs | 6 +- .../src/dao/generated/cl_audits.rs | 9 ++- .../src/dao/generated/sea_orm_active_enums.rs | 68 ++++++++-------- digital_asset_types/src/dao/scopes/asset.rs | 81 +++++++++++++++++++ digital_asset_types/src/dapi/common/asset.rs | 25 ++++++ digital_asset_types/src/dapi/mod.rs | 2 + .../src/dapi/signatures_for_asset.rs | 35 ++++++++ digital_asset_types/src/rpc/response.rs | 14 ++++ digital_asset_types/tests/common.rs | 6 +- digital_asset_types/tests/json_parsing.rs | 4 +- migration/src/lib.rs | 4 + ...01_120101_add_instruction_into_cl_audit.rs | 32 ++++++++ .../m20231101_120101_cl_audit_table_index.rs | 54 +++++++++++++ .../program_transformers/bubblegum/burn.rs | 3 +- .../bubblegum/cancel_redeem.rs | 3 +- .../bubblegum/collection_verification.rs | 3 +- .../bubblegum/creator_verification.rs | 3 +- .../src/program_transformers/bubblegum/db.rs | 31 +++---- .../bubblegum/delegate.rs | 3 +- .../program_transformers/bubblegum/mint_v1.rs | 7 +- .../src/program_transformers/bubblegum/mod.rs | 18 ++--- .../program_transformers/bubblegum/redeem.rs | 3 +- .../bubblegum/transfer.rs | 3 +- .../token_metadata/v1_asset.rs | 6 +- 28 files changed, 411 insertions(+), 87 deletions(-) create mode 100644 digital_asset_types/src/dapi/signatures_for_asset.rs create mode 100644 migration/src/m20231101_120101_add_instruction_into_cl_audit.rs create mode 100644 migration/src/m20231101_120101_cl_audit_table_index.rs diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs index e7ffa95bb..fc91ac61e 100644 --- a/das_api/src/api/api_impl.rs +++ b/das_api/src/api/api_impl.rs @@ -7,6 +7,7 @@ use digital_asset_types::{ Cursor, PageOptions, SearchAssetsQuery, }, dapi::{ +<<<<<<< HEAD get_asset, get_asset_batch, get_asset_proof_batch, get_assets_by_authority, get_assets_by_creator, get_assets_by_group, get_assets_by_owner, get_proof_for_asset, search_assets, @@ -14,6 +15,10 @@ use digital_asset_types::{ rpc::{ filter::{AssetSortBy, SearchConditionType}, response::GetGroupingResponse, +======= + get_asset, get_assets_by_authority, get_assets_by_creator, get_assets_by_group, + get_assets_by_owner, get_proof_for_asset, search_assets, get_signatures_for_asset +>>>>>>> helius-nikhil/get-sigs-for-asset }, rpc::{OwnershipModel, RoyaltyModel}, }; @@ -32,6 +37,7 @@ use { sea_orm::{DatabaseConnection, DbErr, SqlxPostgresConnector}, sqlx::postgres::PgPoolOptions, }; + use digital_asset_types::rpc::response::TransactionSignatureList; pub struct DasApi { db_connection: DatabaseConnection, @@ -497,4 +503,34 @@ impl ApiContract for DasApi { group_size: gs.size, }) } + + async fn get_signatures_for_asset( + self: &DasApi, + payload: GetSignaturesForAsset, + ) -> Result { + let GetSignaturesForAsset { + id, + limit, + page, + before, + after, + tree, + leaf_index, + } = 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 before = validate_opt_pubkey(&before)?; + let after = validate_opt_pubkey(&after)?; + get_signatures_for_asset(&self.db_connection, id, tree, leaf_index, limit.unwrap_or(100), page, before, after) + .await + .map_err(Into::into) + } } diff --git a/das_api/src/api/mod.rs b/das_api/src/api/mod.rs index 3c8ad5f82..1c872801d 100644 --- a/das_api/src/api/mod.rs +++ b/das_api/src/api/mod.rs @@ -2,7 +2,7 @@ use crate::DasApiError; use async_trait::async_trait; use digital_asset_types::rpc::display_options::DisplayOptions; use digital_asset_types::rpc::filter::SearchConditionType; -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}; @@ -147,6 +147,18 @@ pub struct GetGrouping { pub group_value: String, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct GetSignaturesForAsset { + pub id: Option, + pub limit: Option, + pub page: Option, + pub before: Option, + pub after: Option, + pub tree: Option, + pub leaf_index: Option, +} + #[document_rpc] #[async_trait] pub trait ApiContract: Send + Sync + 'static { @@ -229,4 +241,13 @@ pub trait ApiContract: Send + Sync + 'static { summary = "Get a list of assets grouped by a specific authority" )] async fn get_grouping(&self, payload: GetGrouping) -> Result; + #[rpc( + name = "getSignaturesForAsset", + params = "named", + summary = "Get transaction signatures for an asset" + )] + async fn get_signatures_for_asset( + &self, + payload: GetSignaturesForAsset, + ) -> Result; } diff --git a/das_api/src/builder.rs b/das_api/src/builder.rs index e5ff9bb48..fcf141ea4 100644 --- a/das_api/src/builder.rs +++ b/das_api/src/builder.rs @@ -101,6 +101,12 @@ impl RpcApiBuilder { rpc_context.search_assets(payload).await.map_err(Into::into) })?; module.register_alias("searchAssets", "search_assets")?; + + module.register_async_method("get_signatures_for_asset", |rpc_params, rpc_context| async move { + let payload = rpc_params.parse::()?; + rpc_context.get_signatures_for_asset(payload).await.map_err(Into::into) + })?; + module.register_alias("getSignaturesForAsset", "get_signatures_for_asset")?; module.register_async_method("schema", |_, rpc_context| async move { Ok(rpc_context.schema()) diff --git a/digital_asset_types/src/dao/generated/asset_data.rs b/digital_asset_types/src/dao/generated/asset_data.rs index 374ed854a..c8177b628 100644 --- a/digital_asset_types/src/dao/generated/asset_data.rs +++ b/digital_asset_types/src/dao/generated/asset_data.rs @@ -24,8 +24,8 @@ pub struct Model { pub metadata: Json, pub slot_updated: i64, pub reindex: Option, - pub raw_name: Vec, - pub raw_symbol: Vec, + pub raw_name: Option>, + pub raw_symbol: Option>, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] @@ -70,9 +70,9 @@ impl ColumnTrait for Column { Self::MetadataMutability => Mutability::db_type(), Self::Metadata => ColumnType::JsonBinary.def(), Self::SlotUpdated => ColumnType::BigInteger.def(), - Self::Reindex => ColumnType::Boolean.def(), - Self::RawName => ColumnType::Binary.def(), - Self::RawSymbol => ColumnType::Binary.def(), + Self::Reindex => ColumnType::Boolean.def().null(), + Self::RawName => ColumnType::Binary.def().null(), + Self::RawSymbol => ColumnType::Binary.def().null(), } } } diff --git a/digital_asset_types/src/dao/generated/asset_grouping.rs b/digital_asset_types/src/dao/generated/asset_grouping.rs index aae51d6d8..5d5c0e749 100644 --- a/digital_asset_types/src/dao/generated/asset_grouping.rs +++ b/digital_asset_types/src/dao/generated/asset_grouping.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 +//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -12,7 +12,7 @@ impl EntityName for Entity { } } -#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] pub struct Model { pub id: i64, pub asset_id: Vec, @@ -20,7 +20,7 @@ pub struct Model { pub group_value: Option, pub seq: Option, pub slot_updated: Option, - pub verified: Option, + pub verified: bool, pub group_info_seq: Option, } diff --git a/digital_asset_types/src/dao/generated/cl_audits.rs b/digital_asset_types/src/dao/generated/cl_audits.rs index a07714202..ac22cc902 100644 --- a/digital_asset_types/src/dao/generated/cl_audits.rs +++ b/digital_asset_types/src/dao/generated/cl_audits.rs @@ -1,9 +1,8 @@ //! SeaORM Entity. Generated by sea-orm-codegen 0.9.3 + use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; -use std::convert::From; - #[derive(Copy, Clone, Default, Debug, DeriveEntity)] pub struct Entity; @@ -22,8 +21,9 @@ pub struct Model { pub seq: i64, pub level: i64, pub hash: Vec, - pub created_at: Option, + pub created_at: DateTime, pub tx: String, + pub instruction: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] @@ -37,6 +37,8 @@ pub enum Column { Hash, CreatedAt, Tx, + #[sea_orm(column_name = "Instruction")] + Instruction, } #[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] @@ -67,6 +69,7 @@ impl ColumnTrait for Column { Self::Hash => ColumnType::Binary.def(), Self::CreatedAt => ColumnType::DateTime.def(), Self::Tx => ColumnType::String(None).def(), + Self::Instruction => ColumnType::String(None).def().null(), } } } diff --git a/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs b/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs index 2be0283e7..5830d0cc2 100644 --- a/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs +++ b/digital_asset_types/src/dao/generated/sea_orm_active_enums.rs @@ -4,8 +4,8 @@ use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "mutability")] -pub enum Mutability { +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "chain_mutability")] +pub enum ChainMutability { #[sea_orm(string_value = "immutable")] Immutable, #[sea_orm(string_value = "mutable")] @@ -14,20 +14,12 @@ pub enum Mutability { Unknown, } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm( - rs_type = "String", - db_type = "Enum", - enum_name = "v1_account_attachments" -)] -pub enum V1AccountAttachments { - #[sea_orm(string_value = "edition")] - Edition, - #[sea_orm(string_value = "edition_marker")] - EditionMarker, - #[sea_orm(string_value = "master_edition_v1")] - MasterEditionV1, - #[sea_orm(string_value = "master_edition_v2")] - MasterEditionV2, +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "mutability")] +pub enum Mutability { + #[sea_orm(string_value = "immutable")] + Immutable, + #[sea_orm(string_value = "mutable")] + Mutable, #[sea_orm(string_value = "unknown")] Unknown, } @@ -60,6 +52,22 @@ pub enum RoyaltyTargetType { Unknown, } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm( + rs_type = "String", + db_type = "Enum", + enum_name = "specification_versions" +)] +pub enum SpecificationVersions { + #[sea_orm(string_value = "unknown")] + Unknown, + #[sea_orm(string_value = "v0")] + V0, + #[sea_orm(string_value = "v1")] + V1, + #[sea_orm(string_value = "v2")] + V2, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm( rs_type = "String", db_type = "Enum", @@ -88,30 +96,22 @@ pub enum SpecificationAssetClass { Unknown, } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] -#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "chain_mutability")] -pub enum ChainMutability { - #[sea_orm(string_value = "immutable")] - Immutable, - #[sea_orm(string_value = "mutable")] - Mutable, - #[sea_orm(string_value = "unknown")] - Unknown, -} -#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm( rs_type = "String", db_type = "Enum", - enum_name = "specification_versions" + enum_name = "v1_account_attachments" )] -pub enum SpecificationVersions { +pub enum V1AccountAttachments { + #[sea_orm(string_value = "edition")] + Edition, + #[sea_orm(string_value = "edition_marker")] + EditionMarker, + #[sea_orm(string_value = "master_edition_v1")] + MasterEditionV1, + #[sea_orm(string_value = "master_edition_v2")] + MasterEditionV2, #[sea_orm(string_value = "unknown")] Unknown, - #[sea_orm(string_value = "v0")] - V0, - #[sea_orm(string_value = "v1")] - V1, - #[sea_orm(string_value = "v2")] - V2, } #[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "owner_type")] diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs index 8be2ff072..37067dfb4 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -1,7 +1,18 @@ +<<<<<<< HEAD use crate::dao::{ asset::{self}, asset_authority, asset_creators, asset_data, asset_grouping, Cursor, FullAsset, GroupingSize, Pagination, +======= +use crate::{ + dao::{ + asset::{self, Entity}, + asset_authority, asset_creators, asset_data, asset_grouping, FullAsset, + GroupingSize, Pagination, cl_audits, + }, + dapi::common::safe_select, + rpc::{response::AssetList}, +>>>>>>> helius-nikhil/get-sigs-for-asset }; use indexmap::IndexMap; @@ -397,3 +408,73 @@ pub async fn get_by_id( groups: grouping, }) } + +pub async fn get_signatures_for_asset( + conn: &impl ConnectionTrait, + asset_id: Option>, + tree_id: Option>, + leaf_idx: Option, + pagination: &Pagination, + limit: u64, +) -> Result)>, 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_id = asset + .nonce + .ok_or(DbErr::RecordNotFound("Leaf ID does not exist".to_string()))?; + let transactions = fetch_transactions(conn, tree, leaf_id, pagination, limit).await?; + Ok(transactions) + } else { + Ok(Vec::new()) + } +} + +pub async fn fetch_transactions( + conn: &impl ConnectionTrait, + tree: Vec, + leaf_id: i64, + pagination: &Pagination, + limit: u64, +) -> Result)>, DbErr> { + let mut stmt = cl_audits::Entity::find().filter(cl_audits::Column::Tree.eq(tree)); + stmt = stmt.filter(cl_audits::Column::LeafIdx.eq(leaf_id)); + stmt = stmt.order_by(cl_audits::Column::CreatedAt, sea_orm::Order::Desc); + + stmt = paginate( + pagination, + limit, + stmt + ); + let transactions = stmt.all(conn).await?; + let transaction_list: Vec<(String, Option)> = transactions + .into_iter() + .map(|transaction| (transaction.tx, transaction.instruction)) + .collect(); + + Ok(transaction_list) +} \ No newline at end of file diff --git a/digital_asset_types/src/dapi/common/asset.rs b/digital_asset_types/src/dapi/common/asset.rs index 68be6482a..5c960638e 100644 --- a/digital_asset_types/src/dapi/common/asset.rs +++ b/digital_asset_types/src/dapi/common/asset.rs @@ -5,6 +5,7 @@ use crate::dao::Pagination; use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping}; use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::{AssetSortBy, AssetSortDirection, AssetSorting}; +use crate::rpc::response::TransactionSignatureList; use crate::rpc::response::{AssetError, AssetList}; use crate::rpc::{ Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface, @@ -83,6 +84,30 @@ pub fn build_asset_response( } } +pub fn build_transaction_signatures_response( + items: Vec<(String, Option)>, + 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), + }; + 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) { let sort_column = match sorting.sort_by { AssetSortBy::Id => Some(asset::Column::Id), diff --git a/digital_asset_types/src/dapi/mod.rs b/digital_asset_types/src/dapi/mod.rs index efe5deee3..56591cfa4 100644 --- a/digital_asset_types/src/dapi/mod.rs +++ b/digital_asset_types/src/dapi/mod.rs @@ -6,6 +6,7 @@ mod change_logs; pub mod common; mod get_asset; mod search_assets; +mod signatures_for_asset; pub use assets_by_authority::*; pub use assets_by_creator::*; pub use assets_by_group::*; @@ -13,3 +14,4 @@ pub use assets_by_owner::*; pub use change_logs::*; pub use get_asset::*; pub use search_assets::*; +pub use signatures_for_asset::*; \ No newline at end of file diff --git a/digital_asset_types/src/dapi/signatures_for_asset.rs b/digital_asset_types/src/dapi/signatures_for_asset.rs new file mode 100644 index 000000000..783dd021c --- /dev/null +++ b/digital_asset_types/src/dapi/signatures_for_asset.rs @@ -0,0 +1,35 @@ +use crate::dao::scopes; +use crate::rpc::filter::AssetSorting; +use crate::rpc::response::TransactionSignatureList; +use sea_orm::DatabaseConnection; +use sea_orm::DbErr; +use super::common::build_transaction_signatures_response; +use super::common::{build_asset_response, create_pagination, create_sorting}; + + +pub async fn get_signatures_for_asset( + db: &DatabaseConnection, + asset_id: Option>, + tree: Option>, + leaf_idx: Option, + limit: u64, + page: Option, + before: Option>, + after: Option>, +) -> Result { + let pagination = create_pagination(before, after, page)?; + let transactions = scopes::asset::get_signatures_for_asset( + db, + asset_id, + tree, + leaf_idx, + &pagination, + limit + ) + .await?; + Ok(build_transaction_signatures_response( + transactions, + limit, + &pagination, + )) +} diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs index 3945f6955..0962305ab 100644 --- a/digital_asset_types/src/rpc/response.rs +++ b/digital_asset_types/src/rpc/response.rs @@ -36,3 +36,17 @@ pub struct AssetList { #[serde(skip_serializing_if = "Vec::is_empty")] pub errors: Vec, } + +#[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub before: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub after: Option, + pub items: Vec<(String, Option)>, +} diff --git a/digital_asset_types/tests/common.rs b/digital_asset_types/tests/common.rs index bbe3cf509..d4df10742 100644 --- a/digital_asset_types/tests/common.rs +++ b/digital_asset_types/tests/common.rs @@ -83,8 +83,8 @@ pub fn create_asset_data( metadata: JsonValue::String("processing".to_string()), slot_updated: 0, reindex: None, - raw_name: metadata.name.into_bytes().to_vec().clone(), - raw_symbol: metadata.symbol.into_bytes().to_vec().clone(), + raw_name: Some(metadata.name.into_bytes().to_vec().clone()), + raw_symbol: Some(metadata.symbol.into_bytes().to_vec().clone()), }, ) } @@ -231,7 +231,7 @@ pub fn create_asset_grouping( id: row_num, group_key: "collection".to_string(), slot_updated: Some(0), - verified: Some(false), + verified: false, group_info_seq: Some(0), }, ) diff --git a/digital_asset_types/tests/json_parsing.rs b/digital_asset_types/tests/json_parsing.rs index 765f14bc6..fcb133052 100644 --- a/digital_asset_types/tests/json_parsing.rs +++ b/digital_asset_types/tests/json_parsing.rs @@ -34,8 +34,8 @@ pub async fn parse_onchain_json(json: serde_json::Value) -> Content { metadata: json, slot_updated: 0, reindex: None, - raw_name: String::from("Handalf").into_bytes().to_vec(), - raw_symbol: String::from("").into_bytes().to_vec(), + raw_name: Some(String::from("Handalf").into_bytes().to_vec()), + raw_symbol: Some(String::from("").into_bytes().to_vec()), }; v1_content_from_json(&asset_data).unwrap() diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 7e38ac93d..4ae2cfe66 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -30,6 +30,8 @@ mod m20230724_120101_add_group_info_seq; mod m20230726_013107_remove_not_null_constraint_from_group_value; mod m20230918_182123_add_raw_name_symbol; mod m20230919_072154_cl_audits; +mod m20231101_120101_add_instruction_into_cl_audit; +mod m20231101_120101_cl_audit_table_index; pub struct Migrator; @@ -67,6 +69,8 @@ impl MigratorTrait for Migrator { Box::new(m20230726_013107_remove_not_null_constraint_from_group_value::Migration), Box::new(m20230918_182123_add_raw_name_symbol::Migration), Box::new(m20230919_072154_cl_audits::Migration), + Box::new(m20231101_120101_add_instruction_into_cl_audit::Migration), + Box::new(m20231101_120101_cl_audit_table_index::Migration), ] } } diff --git a/migration/src/m20231101_120101_add_instruction_into_cl_audit.rs b/migration/src/m20231101_120101_add_instruction_into_cl_audit.rs new file mode 100644 index 000000000..37820db5d --- /dev/null +++ b/migration/src/m20231101_120101_add_instruction_into_cl_audit.rs @@ -0,0 +1,32 @@ +use digital_asset_types::dao::cl_audits; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + sea_query::Table::alter() + .table(cl_audits::Entity) + .add_column(ColumnDef::new(Alias::new("Instruction")).string()) + .to_owned(), + ) + .await?; + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + sea_query::Table::alter() + .table(cl_audits::Entity) + .drop_column(Alias::new("Instruction")) + .to_owned(), + ) + .await?; + Ok(()) + } +} \ No newline at end of file diff --git a/migration/src/m20231101_120101_cl_audit_table_index.rs b/migration/src/m20231101_120101_cl_audit_table_index.rs new file mode 100644 index 000000000..fb93bda79 --- /dev/null +++ b/migration/src/m20231101_120101_cl_audit_table_index.rs @@ -0,0 +1,54 @@ +use digital_asset_types::dao::cl_audits; +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_index( + Index::create() + .name("idx_cl_audits_tree") + .col(cl_audits::Column::Tree) + .table(cl_audits::Entity) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .name("idx_cl_audits_leaf_id") + .col(cl_audits::Column::LeafIdx) + .table(cl_audits::Entity) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_index( + Index::drop() + .name("idx_cl_audits_tree") + .table(cl_audits::Entity) + .to_owned(), + ) + .await?; + + manager + .drop_index( + Index::drop() + .name("idx_cl_audits_leaf_id") + .table(cl_audits::Entity) + .to_owned(), + ) + .await?; + + Ok(()) + } +} diff --git a/nft_ingester/src/program_transformers/bubblegum/burn.rs b/nft_ingester/src/program_transformers/bubblegum/burn.rs index 70ddcfcea..8f0dffa9a 100644 --- a/nft_ingester/src/program_transformers/bubblegum/burn.rs +++ b/nft_ingester/src/program_transformers/bubblegum/burn.rs @@ -18,12 +18,13 @@ pub async fn burn<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, { if let Some(cl) = &parsing_result.tree_update { - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; let leaf_index = cl.index; let (asset_id, _) = Pubkey::find_program_address( &[ diff --git a/nft_ingester/src/program_transformers/bubblegum/cancel_redeem.rs b/nft_ingester/src/program_transformers/bubblegum/cancel_redeem.rs index 1b8f5842a..b10ef0c46 100644 --- a/nft_ingester/src/program_transformers/bubblegum/cancel_redeem.rs +++ b/nft_ingester/src/program_transformers/bubblegum/cancel_redeem.rs @@ -16,12 +16,13 @@ pub async fn cancel_redeem<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, { if let (Some(le), Some(cl)) = (&parsing_result.leaf_update, &parsing_result.tree_update) { - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; #[allow(unreachable_patterns)] return match le.schema { LeafSchema::V1 { diff --git a/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs b/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs index 7517f1544..072f10177 100644 --- a/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs +++ b/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs @@ -14,6 +14,7 @@ pub async fn process<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, @@ -37,7 +38,7 @@ where "Handling collection verification event for {} (verify: {}): {}", collection, verify, bundle.txn_id ); - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; let id_bytes = match le.schema { LeafSchema::V1 { id, .. } => id.to_bytes().to_vec(), }; diff --git a/nft_ingester/src/program_transformers/bubblegum/creator_verification.rs b/nft_ingester/src/program_transformers/bubblegum/creator_verification.rs index 134fe89ca..fb7962398 100644 --- a/nft_ingester/src/program_transformers/bubblegum/creator_verification.rs +++ b/nft_ingester/src/program_transformers/bubblegum/creator_verification.rs @@ -18,6 +18,7 @@ pub async fn process<'c, T>( txn: &'c T, value: bool, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, @@ -41,7 +42,7 @@ where "Handling creator verification event for creator {} (verify: {}): {}", creator, verify, bundle.txn_id ); - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; let asset_id_bytes = match le.schema { LeafSchema::V1 { diff --git a/nft_ingester/src/program_transformers/bubblegum/db.rs b/nft_ingester/src/program_transformers/bubblegum/db.rs index 7e930abdc..0d6be6dfa 100644 --- a/nft_ingester/src/program_transformers/bubblegum/db.rs +++ b/nft_ingester/src/program_transformers/bubblegum/db.rs @@ -2,7 +2,7 @@ use crate::error::IngesterError; use digital_asset_types::dao::{ asset, asset_creators, asset_grouping, backfill_items, cl_audits, cl_items, }; -use log::{debug, info}; +use log::{debug, info, error}; use mpl_bubblegum::types::Collection; use sea_orm::{ query::*, sea_query::OnConflict, ActiveValue::Set, ColumnTrait, DbBackend, EntityTrait, @@ -17,11 +17,12 @@ pub async fn save_changelog_event<'c, T>( txn_id: &str, txn: &T, cl_audits: bool, + instruction: &str, ) -> Result where T: ConnectionTrait + TransactionTrait, { - insert_change_log(change_log_event, slot, txn_id, txn, cl_audits).await?; + insert_change_log(change_log_event, slot, txn_id, txn, cl_audits, instruction).await?; Ok(change_log_event.seq) } @@ -35,6 +36,7 @@ pub async fn insert_change_log<'c, T>( txn_id: &str, txn: &T, cl_audits: bool, + instruction: &str, ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, @@ -44,13 +46,14 @@ where let tree_id = change_log_event.id.as_ref(); for p in change_log_event.path.iter() { let node_idx = p.index as i64; - debug!( - "seq {}, index {} level {}, node {:?}, txn: {:?}", + info!( + "seq {}, index {} level {}, node {}, txn {}, instruction {}", change_log_event.seq, p.index, i, bs58::encode(p.node).into_string(), txn_id, + instruction ); let leaf_idx = if i == 0 { Some(node_idx_to_leaf_idx(node_idx, depth as u32)) @@ -68,13 +71,9 @@ where ..Default::default() }; - let mut audit_item: Option = if (cl_audits) { - let mut ai: cl_audits::ActiveModel = item.clone().into(); - ai.tx = Set(txn_id.to_string()); - Some(ai) - } else { - None - }; + let mut audit_item: cl_audits::ActiveModel = item.clone().into(); + audit_item.tx = Set(txn_id.to_string()); + audit_item.instruction = Set(Some(instruction.to_string())); i += 1; let mut query = cl_items::Entity::insert(item) @@ -95,8 +94,12 @@ where .map_err(|db_err| IngesterError::StorageWriteError(db_err.to_string()))?; // Insert the audit item after the insert into cl_items have been completed - if let Some(audit_item) = audit_item { - cl_audits::Entity::insert(audit_item).exec(txn).await?; + let query = cl_audits::Entity::insert(audit_item).build(DbBackend::Postgres); + match txn.execute(query).await { + Ok(_) => {} + Err(e) => { + error!("Error while inserting into cl_audits: {:?}", e); + } } } @@ -408,7 +411,7 @@ where asset_id: Set(asset_id), group_key: Set("collection".to_string()), group_value: Set(group_value), - verified: Set(Some(verified)), + verified: Set(verified), slot_updated: Set(Some(slot_updated)), group_info_seq: Set(Some(seq)), ..Default::default() diff --git a/nft_ingester/src/program_transformers/bubblegum/delegate.rs b/nft_ingester/src/program_transformers/bubblegum/delegate.rs index 88896de64..6988f6d03 100644 --- a/nft_ingester/src/program_transformers/bubblegum/delegate.rs +++ b/nft_ingester/src/program_transformers/bubblegum/delegate.rs @@ -16,12 +16,13 @@ pub async fn delegate<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, { if let (Some(le), Some(cl)) = (&parsing_result.leaf_update, &parsing_result.tree_update) { - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; return match le.schema { LeafSchema::V1 { id, diff --git a/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs b/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs index 323c1e35c..ffcc2851a 100644 --- a/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs +++ b/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs @@ -40,6 +40,7 @@ pub async fn mint_v1<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result, IngesterError> where T: ConnectionTrait + TransactionTrait, @@ -49,7 +50,7 @@ where &parsing_result.tree_update, &parsing_result.payload, ) { - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; let metadata = args; #[allow(unreachable_patterns)] return match le.schema { @@ -95,8 +96,8 @@ where metadata_mutability: Set(Mutability::Mutable), slot_updated: Set(slot_i), reindex: Set(Some(true)), - raw_name: Set(name.to_vec()), - raw_symbol: Set(symbol.to_vec()), + raw_name: Set(Some(name.to_vec())), + raw_symbol: Set(Some(symbol.to_vec())), ..Default::default() }; diff --git a/nft_ingester/src/program_transformers/bubblegum/mod.rs b/nft_ingester/src/program_transformers/bubblegum/mod.rs index bcc102c0b..4e735576a 100644 --- a/nft_ingester/src/program_transformers/bubblegum/mod.rs +++ b/nft_ingester/src/program_transformers/bubblegum/mod.rs @@ -59,40 +59,40 @@ where match ix_type { InstructionName::Transfer => { - transfer::transfer(parsing_result, bundle, txn, cl_audits).await?; + transfer::transfer(parsing_result, bundle, txn, cl_audits, ix_str).await?; } InstructionName::Burn => { - burn::burn(parsing_result, bundle, txn, cl_audits).await?; + burn::burn(parsing_result, bundle, txn, cl_audits, ix_str).await?; } InstructionName::Delegate => { - delegate::delegate(parsing_result, bundle, txn, cl_audits).await?; + delegate::delegate(parsing_result, bundle, txn, cl_audits, ix_str).await?; } InstructionName::MintV1 | InstructionName::MintToCollectionV1 => { - let task = mint_v1::mint_v1(parsing_result, bundle, txn, cl_audits).await?; + let task = mint_v1::mint_v1(parsing_result, bundle, txn, cl_audits, ix_str).await?; if let Some(t) = task { task_manager.send(t)?; } } InstructionName::Redeem => { - redeem::redeem(parsing_result, bundle, txn, cl_audits).await?; + redeem::redeem(parsing_result, bundle, txn, cl_audits, ix_str).await?; } InstructionName::CancelRedeem => { - cancel_redeem::cancel_redeem(parsing_result, bundle, txn, cl_audits).await?; + cancel_redeem::cancel_redeem(parsing_result, bundle, txn, cl_audits, ix_str).await?; } InstructionName::DecompressV1 => { decompress::decompress(parsing_result, bundle, txn).await?; } InstructionName::VerifyCreator => { - creator_verification::process(parsing_result, bundle, txn, true, cl_audits).await?; + creator_verification::process(parsing_result, bundle, txn, true, cl_audits, ix_str).await?; } InstructionName::UnverifyCreator => { - creator_verification::process(parsing_result, bundle, txn, false, cl_audits).await?; + creator_verification::process(parsing_result, bundle, txn, false, cl_audits, ix_str).await?; } InstructionName::VerifyCollection | InstructionName::UnverifyCollection | InstructionName::SetAndVerifyCollection => { - collection_verification::process(parsing_result, bundle, txn, cl_audits).await?; + collection_verification::process(parsing_result, bundle, txn, cl_audits, ix_str).await?; } _ => debug!("Bubblegum: Not Implemented Instruction"), } diff --git a/nft_ingester/src/program_transformers/bubblegum/redeem.rs b/nft_ingester/src/program_transformers/bubblegum/redeem.rs index b9b7f2c27..23e46ae65 100644 --- a/nft_ingester/src/program_transformers/bubblegum/redeem.rs +++ b/nft_ingester/src/program_transformers/bubblegum/redeem.rs @@ -15,12 +15,13 @@ pub async fn redeem<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, { if let Some(cl) = &parsing_result.tree_update { - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; let leaf_index = cl.index; let (asset_id, _) = Pubkey::find_program_address( &[ diff --git a/nft_ingester/src/program_transformers/bubblegum/transfer.rs b/nft_ingester/src/program_transformers/bubblegum/transfer.rs index 573f33a8f..83ed6777e 100644 --- a/nft_ingester/src/program_transformers/bubblegum/transfer.rs +++ b/nft_ingester/src/program_transformers/bubblegum/transfer.rs @@ -17,12 +17,13 @@ pub async fn transfer<'c, T>( bundle: &InstructionBundle<'c>, txn: &'c T, cl_audits: bool, + instruction: &str ) -> Result<(), IngesterError> where T: ConnectionTrait + TransactionTrait, { if let (Some(le), Some(cl)) = (&parsing_result.leaf_update, &parsing_result.tree_update) { - let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits, instruction).await?; #[allow(unreachable_patterns)] return match le.schema { LeafSchema::V1 { diff --git a/nft_ingester/src/program_transformers/token_metadata/v1_asset.rs b/nft_ingester/src/program_transformers/token_metadata/v1_asset.rs index 879581341..abdf77170 100644 --- a/nft_ingester/src/program_transformers/token_metadata/v1_asset.rs +++ b/nft_ingester/src/program_transformers/token_metadata/v1_asset.rs @@ -147,8 +147,8 @@ pub async fn save_v1_asset( slot_updated: Set(slot_i), reindex: Set(Some(true)), id: Set(id.to_vec()), - raw_name: Set(name.to_vec()), - raw_symbol: Set(symbol.to_vec()), + raw_name: Set(Some(name.to_vec())), + raw_symbol: Set(Some(symbol.to_vec())), }; let txn = conn.begin().await?; let mut query = asset_data::Entity::insert(asset_data_model) @@ -277,7 +277,7 @@ pub async fn save_v1_asset( asset_id: Set(id.to_vec()), group_key: Set("collection".to_string()), group_value: Set(Some(c.key.to_string())), - verified: Set(Some(c.verified)), + verified: Set(c.verified), seq: Set(None), slot_updated: Set(Some(slot_i)), ..Default::default() From aeee27ec5587e3318939c8683d5d4119c4b4e8ae Mon Sep 17 00:00:00 2001 From: Kirill Fomichev Date: Mon, 27 Nov 2023 12:56:25 -0500 Subject: [PATCH 3/3] fix conflicts helius pr#137 --- das_api/src/api/api_impl.rs | 20 +++++------ das_api/src/api/mod.rs | 7 ++-- digital_asset_types/src/dao/scopes/asset.rs | 34 +++++++++---------- digital_asset_types/src/dapi/common/asset.rs | 13 ++++--- .../src/dapi/signatures_for_asset.rs | 15 ++++---- digital_asset_types/src/rpc/response.rs | 2 ++ 6 files changed, 50 insertions(+), 41 deletions(-) diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs index fc91ac61e..8678c8421 100644 --- a/das_api/src/api/api_impl.rs +++ b/das_api/src/api/api_impl.rs @@ -7,20 +7,16 @@ use digital_asset_types::{ Cursor, PageOptions, SearchAssetsQuery, }, dapi::{ -<<<<<<< HEAD get_asset, get_asset_batch, get_asset_proof_batch, get_assets_by_authority, get_assets_by_creator, get_assets_by_group, get_assets_by_owner, get_proof_for_asset, - search_assets, + search_assets, get_signatures_for_asset }, rpc::{ filter::{AssetSortBy, SearchConditionType}, response::GetGroupingResponse, -======= - get_asset, get_assets_by_authority, get_assets_by_creator, get_assets_by_group, - get_assets_by_owner, get_proof_for_asset, search_assets, get_signatures_for_asset ->>>>>>> helius-nikhil/get-sigs-for-asset + OwnershipModel, RoyaltyModel }, - rpc::{OwnershipModel, RoyaltyModel}, + rpc::{}, }; use open_rpc_derive::document_rpc; use sea_orm::{sea_query::ConditionType, ConnectionTrait, DbBackend, Statement}; @@ -516,6 +512,8 @@ impl ApiContract for DasApi { after, tree, leaf_index, + sort_by, + cursor, } = payload; if !((id.is_some() && tree.is_none() && leaf_index.is_none()) @@ -527,9 +525,11 @@ impl ApiContract for DasApi { } let id = validate_opt_pubkey(&id)?; let tree = validate_opt_pubkey(&tree)?; - let before = validate_opt_pubkey(&before)?; - let after = validate_opt_pubkey(&after)?; - get_signatures_for_asset(&self.db_connection, id, tree, leaf_index, limit.unwrap_or(100), page, before, after) + let sort_by = sort_by.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; + + get_signatures_for_asset(&self.db_connection, id, tree, leaf_index, sort_by, &page_options) .await .map_err(Into::into) } diff --git a/das_api/src/api/mod.rs b/das_api/src/api/mod.rs index 1c872801d..14ba499fa 100644 --- a/das_api/src/api/mod.rs +++ b/das_api/src/api/mod.rs @@ -151,12 +151,15 @@ pub struct GetGrouping { #[serde(deny_unknown_fields, rename_all = "camelCase")] pub struct GetSignaturesForAsset { pub id: Option, - pub limit: Option, - pub page: Option, + pub limit: Option, + pub page: Option, pub before: Option, pub after: Option, pub tree: Option, pub leaf_index: Option, + pub sort_by: Option, + #[serde(default)] + pub cursor: Option, } #[document_rpc] diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs index 37067dfb4..8f089d191 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -1,19 +1,19 @@ -<<<<<<< HEAD -use crate::dao::{ - asset::{self}, - asset_authority, asset_creators, asset_data, asset_grouping, Cursor, FullAsset, GroupingSize, - Pagination, -======= +// <<<<<<< HEAD +// use crate::dao::{ +// asset::{self}, +// asset_authority, asset_creators, asset_data, asset_grouping, Cursor, FullAsset, GroupingSize, +// Pagination, +// ======= use crate::{ dao::{ asset::{self, Entity}, - asset_authority, asset_creators, asset_data, asset_grouping, FullAsset, - GroupingSize, Pagination, cl_audits, + asset_authority, asset_creators, asset_data, asset_grouping, cl_audits, Cursor, FullAsset, + GroupingSize, Pagination, }, dapi::common::safe_select, - rpc::{response::AssetList}, ->>>>>>> helius-nikhil/get-sigs-for-asset + rpc::response::AssetList, }; +// >>>>>>> helius-nikhil/get-sigs-for-asset use indexmap::IndexMap; use sea_orm::{entity::*, query::*, ConnectionTrait, DbErr, Order}; @@ -414,12 +414,13 @@ pub async fn get_signatures_for_asset( asset_id: Option>, tree_id: Option>, leaf_idx: Option, + sort_direction: Order, pagination: &Pagination, limit: u64, ) -> Result)>, 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?; + let transactions = fetch_transactions(conn, tree_id, leaf_idx, sort_direction, pagination, limit).await?; return Ok(transactions); } @@ -447,7 +448,7 @@ pub async fn get_signatures_for_asset( let leaf_id = asset .nonce .ok_or(DbErr::RecordNotFound("Leaf ID does not exist".to_string()))?; - let transactions = fetch_transactions(conn, tree, leaf_id, pagination, limit).await?; + let transactions = fetch_transactions(conn, tree, leaf_id, sort_direction, pagination, limit).await?; Ok(transactions) } else { Ok(Vec::new()) @@ -458,6 +459,7 @@ pub async fn fetch_transactions( conn: &impl ConnectionTrait, tree: Vec, leaf_id: i64, + sort_direction: Order, pagination: &Pagination, limit: u64, ) -> Result)>, DbErr> { @@ -465,11 +467,7 @@ pub async fn fetch_transactions( stmt = stmt.filter(cl_audits::Column::LeafIdx.eq(leaf_id)); stmt = stmt.order_by(cl_audits::Column::CreatedAt, sea_orm::Order::Desc); - stmt = paginate( - pagination, - limit, - stmt - ); + stmt = paginate(pagination, limit, stmt, sort_direction, cl_audits::Column::Id); let transactions = stmt.all(conn).await?; let transaction_list: Vec<(String, Option)> = transactions .into_iter() @@ -477,4 +475,4 @@ pub async fn fetch_transactions( .collect(); Ok(transaction_list) -} \ No newline at end of file +} diff --git a/digital_asset_types/src/dapi/common/asset.rs b/digital_asset_types/src/dapi/common/asset.rs index 5c960638e..007d793f9 100644 --- a/digital_asset_types/src/dapi/common/asset.rs +++ b/digital_asset_types/src/dapi/common/asset.rs @@ -90,13 +90,17 @@ pub fn build_transaction_signatures_response( pagination: &Pagination, ) -> TransactionSignatureList { let total = items.len() as u32; - let (page, before, after) = match pagination { + let (page, before, after, cursor) = 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) + (None, bef, aft, None) + } + Pagination::Page { page } => (Some(*page), None, None, None), + Pagination::Cursor(_) => { + // tmp: helius please fix it ;) + (None, None, None, None) } - Pagination::Page { page } => (Some(*page), None, None), }; TransactionSignatureList { total, @@ -104,6 +108,7 @@ pub fn build_transaction_signatures_response( page: page.map(|x| x as u32), before, after, + cursor, items, } } @@ -317,7 +322,7 @@ pub fn to_grouping( .filter_map(|model| { let verified = match display_options.show_unverified_collections { // Null verified indicates legacy data, meaning it is verified. - true => Some(model.verified.unwrap_or(true)), + true => Some(model.verified), false => None, }; // Filter out items where group_value is None. diff --git a/digital_asset_types/src/dapi/signatures_for_asset.rs b/digital_asset_types/src/dapi/signatures_for_asset.rs index 783dd021c..f34893800 100644 --- a/digital_asset_types/src/dapi/signatures_for_asset.rs +++ b/digital_asset_types/src/dapi/signatures_for_asset.rs @@ -1,4 +1,5 @@ use crate::dao::scopes; +use crate::dao::PageOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::TransactionSignatureList; use sea_orm::DatabaseConnection; @@ -12,24 +13,24 @@ pub async fn get_signatures_for_asset( asset_id: Option>, tree: Option>, leaf_idx: Option, - limit: u64, - page: Option, - before: Option>, - after: Option>, + sorting: AssetSorting, + page_options: &PageOptions, ) -> Result { - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; + let (sort_direction, sort_column) = create_sorting(sorting); let transactions = scopes::asset::get_signatures_for_asset( db, asset_id, tree, leaf_idx, + sort_direction, &pagination, - limit + page_options.limit ) .await?; Ok(build_transaction_signatures_response( transactions, - limit, + page_options.limit, &pagination, )) } diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs index 0962305ab..2fe0bc6ae 100644 --- a/digital_asset_types/src/rpc/response.rs +++ b/digital_asset_types/src/rpc/response.rs @@ -48,5 +48,7 @@ pub struct TransactionSignatureList { pub before: Option, #[serde(skip_serializing_if = "Option::is_none")] pub after: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: Option, pub items: Vec<(String, Option)>, }