diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs index a533617c..d2c1d755 100644 --- a/das_api/src/api/api_impl.rs +++ b/das_api/src/api/api_impl.rs @@ -4,16 +4,17 @@ 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, get_signatures_for_asset, search_assets, + get_asset, get_asset_batch, get_asset_proof, get_asset_proof_batch, + get_assets_by_authority, get_assets_by_creator, get_assets_by_group, get_assets_by_owner, + get_signatures_for_asset, search_assets, }, + feature_flag::FeatureFlags, rpc::{ filter::{AssetSortBy, SearchConditionType}, response::GetGroupingResponse, - transform::AssetTransform, }, rpc::{OwnershipModel, RoyaltyModel}, }; @@ -21,7 +22,7 @@ use open_rpc_derive::document_rpc; use sea_orm::{sea_query::ConditionType, ConnectionTrait, DbBackend, Statement}; use crate::{ - feature_flag::{get_feature_flags, FeatureFlags}, + feature_flag::get_feature_flags, validation::{validate_opt_pubkey, validate_search_with_name}, }; use open_rpc_schema::document::OpenrpcDocument; @@ -59,21 +60,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); } } @@ -83,20 +100,62 @@ 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); } + + let current_limit = limit.unwrap_or(1000); + let offset = (*page - 1) * current_limit; + if offset > 500_000 { + return Err(DasApiError::OffsetLimitExceededError); + } + 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) } fn validate_sorting_for_collection( @@ -162,7 +221,7 @@ impl ApiContract for DasApi { ) -> Result { let id = validate_pubkey(payload.id.clone())?; let id_bytes = id.to_bytes().to_vec(); - get_proof_for_asset(&self.db_connection, id_bytes) + get_asset_proof(&self.db_connection, id_bytes) .await .and_then(|p| { if p.proof.is_empty() { @@ -173,15 +232,81 @@ 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(); - let transform = AssetTransform { - cdn_prefix: self.cdn_prefix.clone(), - }; - get_asset(&self.db_connection, id_bytes, &transform, payload.raw_data) - .await - .map_err(Into::into) + let GetAsset { + id, + display_options, + } = payload; + let id_bytes = validate_pubkey(id.clone())?.to_bytes().to_vec(); + let mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); + get_asset( + &self.db_connection, + id_bytes, + &self.feature_flags, + &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 mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); + + 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( @@ -195,26 +320,25 @@ 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 transform = AssetTransform { - cdn_prefix: self.cdn_prefix.clone(), - }; + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; + let mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); 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()), - &transform, - self.feature_flags.enable_grand_total_query, + &page_options, + &self.feature_flags, + &display_options, ) .await .map_err(Into::into) @@ -232,26 +356,24 @@ impl ApiContract for DasApi { page, before, after, + display_options, + cursor, } = payload; self.validate_sorting_for_collection(&group_key, &group_value, &sort_by)?; - let sort_by = sort_by.unwrap_or_default(); let before: Option = before.filter(|before| !before.is_empty()); let after: Option = after.filter(|after| !after.is_empty()); - self.validate_pagination(&limit, &page, &before, &after)?; - let transform = AssetTransform { - cdn_prefix: self.cdn_prefix.clone(), - }; + let sort_by = sort_by.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; + let mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); 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()), - &transform, - self.feature_flags.enable_grand_total_query, + &page_options, + &self.feature_flags, + &display_options, ) .await .map_err(Into::into) @@ -269,27 +391,26 @@ 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 transform = AssetTransform { - cdn_prefix: self.cdn_prefix.clone(), - }; + let mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); 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()), - &transform, - self.feature_flags.enable_grand_total_query, + &page_options, + &self.feature_flags, + &display_options, ) .await .map_err(Into::into) @@ -306,24 +427,23 @@ 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(); - self.validate_pagination(&limit, &page, &before, &after)?; - let transform = AssetTransform { - cdn_prefix: self.cdn_prefix.clone(), - }; + let sort_by = sort_by.unwrap_or_default(); + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; + let mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); 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()), - &transform, - self.feature_flags.enable_grand_total_query, + &page_options, + &self.feature_flags, + &display_options, ) .await .map_err(Into::into) @@ -357,11 +477,11 @@ impl ApiContract for DasApi { before, after, json_uri, - show_collection_metadata, + 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); @@ -413,22 +533,18 @@ impl ApiContract for DasApi { name, }; let sort_by = sort_by.unwrap_or_default(); - let transform = AssetTransform { - cdn_prefix: self.cdn_prefix.clone(), - }; + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &Some(&sort_by))?; + let mut display_options = display_options.unwrap_or_default(); + display_options.cdn_prefix = self.cdn_prefix.clone(); // 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()), - &transform, - self.feature_flags.enable_grand_total_query, - self.feature_flags.enable_collection_metadata - && show_collection_metadata.unwrap_or(false), + &page_options, + &self.feature_flags, + &display_options, ) .await .map_err(Into::into) @@ -462,6 +578,7 @@ impl ApiContract for DasApi { after, tree, leaf_index, + cursor, } = payload; if !((id.is_some() && tree.is_none() && leaf_index.is_none()) @@ -474,19 +591,11 @@ impl ApiContract for DasApi { let id = validate_opt_pubkey(&id)?; let tree = validate_opt_pubkey(&tree)?; - self.validate_pagination(&limit, &page, &before, &after)?; + let page_options = + self.validate_pagination(&limit, &page, &before, &after, &cursor, &None)?; - get_signatures_for_asset( - &self.db_connection, - id, - tree, - leaf_index, - 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()), - ) - .await - .map_err(Into::into) + get_signatures_for_asset(&self.db_connection, id, tree, leaf_index, page_options) + .await + .map_err(Into::into) } } diff --git a/das_api/src/api/mod.rs b/das_api/src/api/mod.rs index 31372d60..44f96f9b 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, TransactionSignatureList}; 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,6 +38,10 @@ 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)] @@ -39,7 +49,15 @@ pub struct GetAssetsByOwner { pub struct GetAsset { pub id: String, #[serde(default)] - pub raw_data: Option, + 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)] @@ -48,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 { @@ -58,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)] @@ -90,7 +118,10 @@ pub struct SearchAssets { #[serde(default)] pub json_uri: Option, #[serde(default)] - pub show_collection_metadata: Option, + pub display_options: Option, + #[serde(default)] + pub cursor: Option, + #[serde(default)] pub name: Option, } @@ -104,6 +135,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)] @@ -123,6 +158,8 @@ pub struct GetSignaturesForAsset { pub after: Option, pub tree: Option, pub leaf_index: Option, + #[serde(default)] + pub cursor: Option, } #[document_rpc] @@ -135,12 +172,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 502be937..bb438d93 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 3640da76..8c7c6ddd 100644 --- a/das_api/src/error.rs +++ b/das_api/src/error.rs @@ -22,6 +22,16 @@ pub enum DasApiError { PaginationEmptyError, #[error("Deserialization error: {0}")] DeserializationError(#[from] serde_json::Error), + #[error("Paginating beyond 500000 items is temporarily disabled. Please contact Helius support for more details.")] + OffsetLimitExceededError, + #[error("Pagination Error. Limit should not be greater than 1000.")] + PaginationExceededError, + #[error("Batch Size Error. Batch size should not be greater than 1000.")] + BatchSizeExceededError, + #[error("Pagination Sorting Error. Only sorting based on id is support for this pagination")] + PaginationSortingValidationError, + #[error("Cursor Validation Err: {0} is invalid")] + CursorValidationError(String), } impl Into for DasApiError { diff --git a/das_api/src/feature_flag.rs b/das_api/src/feature_flag.rs index ba68f00f..a772c9f4 100644 --- a/das_api/src/feature_flag.rs +++ b/das_api/src/feature_flag.rs @@ -1,13 +1,10 @@ -use crate::config::Config; +use digital_asset_types::feature_flag::FeatureFlags; -pub struct FeatureFlags { - pub enable_grand_total_query: bool, - pub enable_collection_metadata: bool, -} +use crate::config::Config; pub fn get_feature_flags(config: &Config) -> FeatureFlags { FeatureFlags { - enable_grand_total_query: config.enable_grand_total_query.unwrap_or(false), - enable_collection_metadata: config.enable_collection_metadata.unwrap_or(false), + enable_grand_total_query: config.enable_grand_total_query.unwrap_or(true), + enable_collection_metadata: config.enable_collection_metadata.unwrap_or(true), } } diff --git a/digital_asset_types/src/dao/mod.rs b/digital_asset_types/src/dao/mod.rs index 2d36217e..9228ca3f 100644 --- a/digital_asset_types/src/dao/mod.rs +++ b/digital_asset_types/src/dao/mod.rs @@ -4,6 +4,7 @@ pub mod scopes; pub use full_asset::*; #[allow(ambiguous_glob_reexports)] pub use generated::*; +use serde::{Deserialize, Serialize}; use self::sea_orm_active_enums::{ OwnerType, RoyaltyTargetType, SpecificationAssetClass, SpecificationVersions, @@ -19,6 +20,20 @@ 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 +42,7 @@ pub enum Pagination { Page { page: u64, }, + Cursor(Cursor), } #[derive(Debug, Clone, PartialEq)] @@ -258,6 +274,7 @@ impl SearchAssetsQuery { 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() diff --git a/digital_asset_types/src/dao/scopes/asset.rs b/digital_asset_types/src/dao/scopes/asset.rs index 3d2c50fe..827735b6 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -1,11 +1,11 @@ use crate::{ dao::{ asset::{self, Entity}, - asset_authority, asset_creators, asset_data, asset_grouping, cl_audits, FullAsset, + asset_authority, asset_creators, asset_data, asset_grouping, cl_audits, Cursor, FullAsset, GroupingSize, Pagination, }, dapi::common::safe_select, - rpc::{response::AssetList, CollectionMetadata}, + rpc::{display_options::DisplayOptions, Asset, CollectionMetadata}, }; use indexmap::IndexMap; @@ -13,18 +13,25 @@ use sea_orm::{entity::*, query::*, ConnectionTrait, DbErr, Order}; use std::collections::{HashMap, HashSet}; use tokio::try_join; -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 } => { @@ -32,6 +39,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) } @@ -45,6 +61,7 @@ pub async fn get_by_creator( pagination: &Pagination, limit: u64, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> { let mut condition = Condition::all() .add(asset_creators::Column::Creator.eq(creator)) @@ -61,6 +78,7 @@ pub async fn get_by_creator( pagination, limit, enable_grand_total_query, + show_unverified_collections, ) .await } @@ -88,33 +106,34 @@ pub async fn get_grouping( pub async fn get_by_grouping( conn: &impl ConnectionTrait, - group_key: String, group_value: String, sort_by: Option, sort_direction: Order, pagination: &Pagination, limit: u64, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> { - let condition = asset_grouping::Column::GroupKey - .eq(group_key) - .and(asset_grouping::Column::GroupValue.eq(group_value)) - .and( + let mut expr = asset_grouping::Column::GroupValue.eq(group_value); + if !show_unverified_collections { + expr = expr.and( asset_grouping::Column::Verified .eq(true) .or(asset_grouping::Column::Verified.is_null()), ); - get_by_related_condition( + } + + let condition = Condition::all().add(expr); + get_assets_by_condition( conn, - Condition::all() - .add(condition) - .add(asset::Column::Supply.gt(0)), - asset::Relation::AssetGrouping, + condition, + vec![], sort_by, sort_direction, pagination, limit, enable_grand_total_query, + show_unverified_collections, ) .await } @@ -127,6 +146,7 @@ pub async fn get_assets_by_owner( pagination: &Pagination, limit: u64, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> { let cond = Condition::all() .add(asset::Column::Owner.eq(owner)) @@ -140,10 +160,36 @@ pub async fn get_assets_by_owner( pagination, limit, enable_grand_total_query, + 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)); + let (assets, _grand_total) = 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, + false, + ) + .await?; + Ok(assets) +} + pub async fn get_by_authority( conn: &impl ConnectionTrait, authority: Vec, @@ -152,43 +198,25 @@ pub async fn get_by_authority( pagination: &Pagination, limit: u64, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> { let cond = Condition::all() .add(asset_authority::Column::Authority.eq(authority)) .add(asset::Column::Supply.gt(0)); - get_by_related_condition( + get_assets_by_condition( conn, cond, - asset::Relation::AssetAuthority, + vec![], sort_by, sort_direction, pagination, limit, enable_grand_total_query, + show_unverified_collections, ) .await } -async fn get_by_related_condition_unsorted( - conn: &impl ConnectionTrait, - condition: Condition, - relation: E, - pagination: &Pagination, - limit: u64, - enable_grand_total_query: bool, -) -> Result<(Vec, Option), DbErr> -where - E: RelationTrait, -{ - let stmt = asset::Entity::find() - .filter(condition) - .join(JoinType::LeftJoin, relation.def()); - - let (assets, grand_total) = - get_full_response(conn, stmt, pagination, limit, enable_grand_total_query).await?; - Ok((assets, grand_total)) -} - async fn get_by_related_condition( conn: &impl ConnectionTrait, condition: Condition, @@ -198,6 +226,7 @@ async fn get_by_related_condition( pagination: &Pagination, limit: u64, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> where E: RelationTrait, @@ -209,17 +238,27 @@ 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, grand_total) = - get_full_response(conn, stmt, pagination, limit, enable_grand_total_query).await?; + let (assets, grand_total) = get_full_response( + conn, + stmt, + pagination, + limit, + sort_by, + sort_direction, + enable_grand_total_query, + show_unverified_collections, + ) + .await?; Ok((assets, grand_total)) } 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::>(); @@ -275,16 +314,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?; @@ -306,6 +349,7 @@ pub async fn get_assets_by_condition( pagination: &Pagination, limit: u64, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> { let mut stmt = asset::Entity::find(); for def in joins { @@ -315,11 +359,20 @@ 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, grand_total) = - get_full_response(conn, stmt, pagination, limit, enable_grand_total_query).await?; + let (assets, grand_total) = get_full_response( + conn, + stmt, + pagination, + limit, + sort_by, + sort_direction, + enable_grand_total_query, + show_unverified_collections, + ) + .await?; Ok((assets, grand_total)) } @@ -327,6 +380,7 @@ pub async fn get_by_id( conn: &impl ConnectionTrait, asset_id: Vec, include_no_supply: bool, + display_options: &DisplayOptions, ) -> Result { let mut asset_data = asset::Entity::find_by_id(asset_id.clone()).find_also_related(asset_data::Entity); @@ -350,16 +404,19 @@ pub async fn get_by_id( .order_by_asc(asset_creators::Column::Position) .all(conn) .await?; + let cond = if display_options.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: Vec = asset_grouping::Entity::find() .filter(asset_grouping::Column::AssetId.eq(asset.id.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?; @@ -378,16 +435,22 @@ pub async fn fetch_transactions( leaf_id: i64, pagination: &Pagination, limit: u64, -) -> Result>, DbErr> { +) -> 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); + stmt = paginate( + pagination, + limit, + stmt, + sea_orm::Order::Asc, + asset::Column::Id, + ); let transactions = stmt.all(conn).await?; - let transaction_list: Vec> = transactions + let transaction_list: Vec<(String, String)> = transactions .into_iter() - .map(|transaction| vec![transaction.tx, transaction.instruction]) + .map(|transaction| (transaction.tx, transaction.instruction)) .collect(); Ok(transaction_list) @@ -400,7 +463,7 @@ pub async fn get_signatures_for_asset( leaf_idx: Option, pagination: &Pagination, limit: u64, -) -> Result>, DbErr> { +) -> 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?; @@ -443,18 +506,24 @@ async fn get_full_response( stmt: Select, pagination: &Pagination, limit: u64, + _sort_by: Option, + sort_direction: Order, enable_grand_total_query: bool, + show_unverified_collections: bool, ) -> Result<(Vec, Option), DbErr> { if enable_grand_total_query { let grand_total_task = get_grand_total(conn, stmt.clone()); - let assets_task = paginate(pagination, limit, stmt).all(conn); + let assets_task = + paginate(pagination, limit, stmt, sort_direction, asset::Column::Id).all(conn); let (assets, grand_total) = try_join!(assets_task, grand_total_task)?; - let full_assets = get_related_for_assets(conn, assets).await?; + let full_assets = get_related_for_assets(conn, assets, show_unverified_collections).await?; return Ok((full_assets, grand_total)); } else { - 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, None)) } } @@ -469,12 +538,12 @@ async fn get_grand_total( pub async fn add_collection_metadata( conn: &impl ConnectionTrait, - mut asset_list: AssetList, -) -> Result { + assets: &mut Vec, +) -> Result<(), DbErr> { // compile a set of all the distinct group values (bs58 String) from the asset list let mut group_values: HashSet = HashSet::new(); - for item in &asset_list.items { - if let Some(groups) = &item.grouping { + for asset in assets.iter() { + if let Some(groups) = &asset.grouping { for group in groups { if let Some(group_value) = &group.group_value { group_values.insert(group_value.clone()); @@ -508,14 +577,12 @@ pub async fn add_collection_metadata( } // add the metadata to the asset_list - for item in &mut asset_list.items { - if let Some(groups) = &mut item.grouping { + for asset in assets.iter_mut() { + if let Some(groups) = &mut asset.grouping { for group in groups { if let Some(group_value) = &group.group_value { let collection_metadata = hashmap.get(group_value); if let Some(collection_metadata) = collection_metadata { - group.group_key = group.group_key.clone(); - group.group_value = Some(group_value.clone()); group.collection_metadata = Some(collection_metadata.clone()); } } @@ -523,7 +590,7 @@ pub async fn add_collection_metadata( } } - Ok(asset_list) + Ok(()) } fn get_collection_metadata(data: &asset_data::Model) -> CollectionMetadata { diff --git a/digital_asset_types/src/dapi/assets_by_authority.rs b/digital_asset_types/src/dapi/assets_by_authority.rs index 954a1adf..c75d4a35 100644 --- a/digital_asset_types/src/dapi/assets_by_authority.rs +++ b/digital_asset_types/src/dapi/assets_by_authority.rs @@ -1,7 +1,9 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::feature_flag::FeatureFlags; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; -use crate::rpc::transform::AssetTransform; use sea_orm::DatabaseConnection; use sea_orm::DbErr; @@ -12,30 +14,32 @@ pub async fn get_assets_by_authority( db: &DatabaseConnection, authority: Vec, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, - transform: &AssetTransform, - enable_grand_total_query: bool, + page_options: &PageOptions, + feature_flags: &FeatureFlags, + 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 enable_grand_total_query = + feature_flags.enable_grand_total_query && display_options.show_grand_total; + let (assets, grand_total) = scopes::asset::get_by_authority( db, authority, sort_column, sort_direction, &pagination, - limit, + page_options.limit, enable_grand_total_query, + display_options.show_unverified_collections, ) .await?; Ok(build_asset_response( assets, - limit, + page_options.limit, grand_total, &pagination, - transform, + 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 b0baf25b..a913e38b 100644 --- a/digital_asset_types/src/dapi/assets_by_creator.rs +++ b/digital_asset_types/src/dapi/assets_by_creator.rs @@ -1,8 +1,9 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::feature_flag::FeatureFlags; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; - -use crate::rpc::transform::AssetTransform; use sea_orm::DatabaseConnection; use sea_orm::DbErr; @@ -13,15 +14,16 @@ pub async fn get_assets_by_creator( creator: Vec, only_verified: bool, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, - transform: &AssetTransform, - enable_grand_total_query: bool, + page_options: &PageOptions, + feature_flags: &FeatureFlags, + 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 enable_grand_total_query = + feature_flags.enable_grand_total_query && display_options.show_grand_total; + let (assets, grand_total) = scopes::asset::get_by_creator( db, creator, @@ -29,15 +31,16 @@ pub async fn get_assets_by_creator( sort_column, sort_direction, &pagination, - limit, + page_options.limit, enable_grand_total_query, + display_options.show_unverified_collections, ) .await?; Ok(build_asset_response( assets, - limit, + page_options.limit, grand_total, &pagination, - transform, + 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 9ee3b8a7..11265d22 100644 --- a/digital_asset_types/src/dapi/assets_by_group.rs +++ b/digital_asset_types/src/dapi/assets_by_group.rs @@ -1,45 +1,47 @@ use crate::dao::scopes; +use crate::dao::PageOptions; +use crate::feature_flag::FeatureFlags; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; - -use crate::rpc::transform::AssetTransform; use sea_orm::DatabaseConnection; use sea_orm::DbErr; use super::common::create_sorting; use super::common::{build_asset_response, create_pagination}; + pub async fn get_assets_by_group( db: &DatabaseConnection, - group_key: String, group_value: String, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, - transform: &AssetTransform, - enable_grand_total_query: bool, + page_options: &PageOptions, + feature_flags: &FeatureFlags, + display_options: &DisplayOptions, ) -> Result { // TODO: Explore further optimizing the unsorted query - let pagination = create_pagination(before, after, page)?; + let pagination = create_pagination(&page_options)?; let (sort_direction, sort_column) = create_sorting(sorting); + + let enable_grand_total_query = + feature_flags.enable_grand_total_query && display_options.show_grand_total; + let (assets, grand_total) = scopes::asset::get_by_grouping( db, - group_key.clone(), group_value.clone(), sort_column, sort_direction, &pagination, - limit, + page_options.limit, enable_grand_total_query, + display_options.show_unverified_collections, ) .await?; Ok(build_asset_response( assets, - limit, + page_options.limit, grand_total, &pagination, - transform, + 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 b93f07ec..65fb3144 100644 --- a/digital_asset_types/src/dapi/assets_by_owner.rs +++ b/digital_asset_types/src/dapi/assets_by_owner.rs @@ -1,9 +1,9 @@ use crate::dao::scopes; - +use crate::dao::PageOptions; +use crate::feature_flag::FeatureFlags; +use crate::rpc::display_options::DisplayOptions; use crate::rpc::filter::AssetSorting; use crate::rpc::response::AssetList; -use crate::rpc::transform::AssetTransform; - use sea_orm::DatabaseConnection; use sea_orm::DbErr; @@ -13,30 +13,32 @@ pub async fn get_assets_by_owner( db: &DatabaseConnection, owner_address: Vec, sort_by: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, - transform: &AssetTransform, - enable_grand_total_query: bool, + page_options: &PageOptions, + feature_flags: &FeatureFlags, + 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 enable_grand_total_query = + feature_flags.enable_grand_total_query && display_options.show_grand_total; + let (assets, grand_total) = scopes::asset::get_assets_by_owner( db, owner_address, sort_column, sort_direction, &pagination, - limit, + page_options.limit, enable_grand_total_query, + display_options.show_unverified_collections, ) .await?; Ok(build_asset_response( assets, - limit, + page_options.limit, grand_total, &pagination, - transform, + display_options, )) } diff --git a/digital_asset_types/src/dapi/change_logs.rs b/digital_asset_types/src/dapi/change_logs.rs index a710c0a5..476e2de6 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,9 +15,24 @@ struct SimpleChangeLog { level: i64, node_idx: i64, seq: i64, + tree: Vec, } -pub async fn get_proof_for_asset( +#[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_asset_proof( db: &DatabaseConnection, asset_id: Vec, ) -> Result { @@ -42,9 +57,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 +73,170 @@ 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(), + ) + // get only the necessary columns + .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 3f1773b6..52b5282a 100644 --- a/digital_asset_types/src/dapi/common/asset.rs +++ b/digital_asset_types/src/dapi/common/asset.rs @@ -1,10 +1,11 @@ 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, TransactionSignatureList}; -use crate::rpc::transform::AssetTransform; use crate::rpc::{ Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface, MetadataMap, Ownership, Royalty, Scope, Supply, Uses, @@ -51,20 +52,29 @@ pub fn build_asset_response( limit: u64, grand_total: Option, pagination: &Pagination, - transform: &AssetTransform, + 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, transform); + + let (items, errors) = asset_list_to_rpc(assets, display_options); AssetList { - // grand_total: grand_total.unwrap(), // TODO + grand_total: grand_total, total, limit: limit as u32, page: page.map(|x| x as u32), @@ -72,11 +82,12 @@ pub fn build_asset_response( after, items, errors, + cursor, } } pub fn build_transaction_signatures_response( - items: Vec>, + items: Vec<(String, String)>, limit: u64, pagination: &Pagination, ) -> TransactionSignatureList { @@ -88,6 +99,7 @@ pub fn build_transaction_signatures_response( (None, bef, aft) } Pagination::Page { page } => (Some(*page), None, None), + Pagination::Cursor { .. } => (None, None, None), }; TransactionSignatureList { total, @@ -101,6 +113,7 @@ pub fn build_transaction_signatures_response( 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), @@ -113,18 +126,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())), + } } } @@ -169,8 +186,7 @@ fn process_raw_fields( pub fn v1_content_from_json( asset_data: &asset_data::Model, - cdn_prefix: Option, - raw_data: Option, + display_options: &DisplayOptions, ) -> Result { // todo -> move this to the bg worker for pre processing let json_uri = asset_data.metadata_url.clone(); @@ -180,7 +196,7 @@ pub fn v1_content_from_json( let selector = &mut selector_fn; let chain_data_selector = &mut chain_data_selector_fn; let mut meta: MetadataMap = MetadataMap::new(); - if raw_data.unwrap_or(false) { + if display_options.show_raw_data { let (name, symbol) = process_raw_fields(&asset_data.raw_name, &asset_data.raw_symbol); if let Some(name) = name { meta.set_item("name", name.into()); @@ -283,7 +299,7 @@ pub fn v1_content_from_json( }); // Enrich files with CDN for images (optional). - if let Some(cdn_prefix) = &cdn_prefix { + if let Some(cdn_prefix) = display_options.cdn_prefix.clone() { // Use default options for now. let cdn_options = ""; files.iter_mut().for_each(|f| match (&f.uri, &f.mime) { @@ -313,12 +329,11 @@ pub fn v1_content_from_json( pub fn get_content( asset: &asset::Model, data: &asset_data::Model, - cdn_prefix: Option, - raw_data: Option, + display_options: &DisplayOptions, ) -> Result { match asset.specification_version { Some(SpecificationVersions::V1) | Some(SpecificationVersions::V0) => { - v1_content_from_json(data, cdn_prefix, raw_data) + v1_content_from_json(data, display_options) } Some(_) => Err(DbErr::Custom("Version Not Implemented".to_string())), None => Err(DbErr::Custom("Specification version not found".to_string())), @@ -346,21 +361,29 @@ 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()))?, - ), - collection_metadata: None, +pub fn to_grouping( + groups: Vec, + display_options: &DisplayOptions, +) -> Result, DbErr> { + let result: Vec = groups + .iter() + .filter_map(|model| { + // Only show verification info if requested via display options. + 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, + collection_metadata: None, + }) }) - } - - groups.iter().map(find_group).collect() + .collect(); + Ok(result) } pub fn get_interface(asset: &asset::Model) -> Result { @@ -379,11 +402,7 @@ pub fn get_interface(asset: &asset::Model) -> Result { } //TODO -> impl custom error type -pub fn asset_to_rpc( - asset: FullAsset, - transform: &AssetTransform, - raw_data: Option, -) -> Result { +pub fn asset_to_rpc(asset: FullAsset, display_options: &DisplayOptions) -> Result { let FullAsset { asset, data, @@ -393,9 +412,9 @@ 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, transform.cdn_prefix.clone(), raw_data)?; + let content = get_content(&asset, &data, display_options)?; let mut chain_data_selector_fn = jsonpath_lib::selector(&data.chain_data); let chain_data_selector = &mut chain_data_selector_fn; let basis_points = safe_select(chain_data_selector, "$.primary_sale_happened") @@ -475,13 +494,13 @@ pub fn asset_to_rpc( pub fn asset_list_to_rpc( asset_list: Vec, - transform: &AssetTransform, + 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, transform, None) { + 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 77001933..ff65fd79 100644 --- a/digital_asset_types/src/dapi/get_asset.rs +++ b/digital_asset_types/src/dapi/get_asset.rs @@ -1,18 +1,48 @@ -use sea_orm::{DatabaseConnection, DbErr}; +use std::collections::HashMap; +use super::common::{asset_to_rpc, build_asset_response}; use crate::{ - dao::scopes, - rpc::{transform::AssetTransform, Asset}, + dao::{ + scopes::{self, asset::add_collection_metadata}, + Pagination, + }, + feature_flag::FeatureFlags, + rpc::{display_options::DisplayOptions, Asset}, }; - -use super::common::asset_to_rpc; +use sea_orm::{DatabaseConnection, DbErr}; pub async fn get_asset( db: &DatabaseConnection, id: Vec, - transform: &AssetTransform, - raw_data: Option, + feature_flags: &FeatureFlags, + display_options: &DisplayOptions, ) -> Result { - let asset = scopes::asset::get_by_id(db, id, false).await?; - asset_to_rpc(asset, transform, raw_data) + let asset = scopes::asset::get_by_id(db, id, false, display_options).await?; + let mut asset = asset_to_rpc(asset, display_options)?; + if display_options.show_collection_metadata && feature_flags.enable_collection_metadata { + let mut v = vec![asset.clone()]; + add_collection_metadata(db, &mut v).await?; + asset = v.pop().unwrap_or(asset); + } + return Ok(asset); +} + +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 mut asset_list = build_asset_response(assets, limit, None, &pagination, display_options); + if display_options.show_collection_metadata { + add_collection_metadata(db, &mut asset_list.items).await?; + } + 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 6f9ba552..aefbafdc 100644 --- a/digital_asset_types/src/dapi/search_assets.rs +++ b/digital_asset_types/src/dapi/search_assets.rs @@ -2,9 +2,10 @@ use super::common::{build_asset_response, create_pagination, create_sorting}; use crate::{ dao::{ scopes::{self, asset::add_collection_metadata}, - SearchAssetsQuery, + PageOptions, SearchAssetsQuery, }, - rpc::{filter::AssetSorting, response::AssetList, transform::AssetTransform}, + feature_flag::FeatureFlags, + rpc::{display_options::DisplayOptions, filter::AssetSorting, response::AssetList}, }; use sea_orm::{DatabaseConnection, DbErr}; @@ -12,17 +13,17 @@ pub async fn search_assets( db: &DatabaseConnection, search_assets_query: SearchAssetsQuery, sorting: AssetSorting, - limit: u64, - page: Option, - before: Option>, - after: Option>, - transform: &AssetTransform, - enable_grand_total_query: bool, - enable_collection_metadata: bool, + page_options: &PageOptions, + feature_flags: &FeatureFlags, + 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 enable_grand_total_query = + feature_flags.enable_grand_total_query && display_options.show_grand_total; + let (assets, grand_total) = scopes::asset::get_assets_by_condition( db, condition, @@ -30,13 +31,20 @@ pub async fn search_assets( sort_column, sort_direction, &pagination, - limit, + page_options.limit, enable_grand_total_query, + display_options.show_unverified_collections, ) .await?; - let mut asset_list = build_asset_response(assets, limit, grand_total, &pagination, &transform); - if enable_collection_metadata { - asset_list = add_collection_metadata(db, asset_list).await?; + let mut asset_list = build_asset_response( + assets, + page_options.limit, + grand_total, + &pagination, + display_options, + ); + if display_options.show_collection_metadata { + add_collection_metadata(db, &mut asset_list.items).await?; } Ok(asset_list) } diff --git a/digital_asset_types/src/dapi/signatures_for_asset.rs b/digital_asset_types/src/dapi/signatures_for_asset.rs index df681d0c..7127e55a 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::response::TransactionSignatureList; use sea_orm::DatabaseConnection; @@ -11,18 +12,21 @@ pub async fn get_signatures_for_asset( asset_id: Option>, tree: Option>, leaf_idx: Option, - limit: u64, - page: Option, - before: Option>, - after: Option>, + page_options: PageOptions, ) -> 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?; + let pagination = create_pagination(&page_options)?; + let transactions = scopes::asset::get_signatures_for_asset( + db, + asset_id, + tree, + leaf_idx, + &pagination, + page_options.limit, + ) + .await?; Ok(build_transaction_signatures_response( transactions, - limit, + page_options.limit, &pagination, )) } diff --git a/digital_asset_types/src/feature_flag.rs b/digital_asset_types/src/feature_flag.rs new file mode 100644 index 00000000..ab4c5a43 --- /dev/null +++ b/digital_asset_types/src/feature_flag.rs @@ -0,0 +1,4 @@ +pub struct FeatureFlags { + pub enable_grand_total_query: bool, + pub enable_collection_metadata: bool, +} diff --git a/digital_asset_types/src/lib.rs b/digital_asset_types/src/lib.rs index cf5ec167..b6cfd7a9 100644 --- a/digital_asset_types/src/lib.rs +++ b/digital_asset_types/src/lib.rs @@ -2,6 +2,7 @@ pub mod dao; #[cfg(feature = "sql_types")] pub mod dapi; +pub mod feature_flag; #[cfg(feature = "json_types")] pub mod json; #[cfg(feature = "json_types")] diff --git a/digital_asset_types/src/rpc/asset.rs b/digital_asset_types/src/rpc/asset.rs index b634fcc0..0cde3cfa 100644 --- a/digital_asset_types/src/rpc/asset.rs +++ b/digital_asset_types/src/rpc/asset.rs @@ -204,6 +204,8 @@ pub struct Group { pub group_key: String, pub group_value: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub verified: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub collection_metadata: Option, } 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 00000000..2ecdcc06 --- /dev/null +++ b/digital_asset_types/src/rpc/display_options.rs @@ -0,0 +1,18 @@ +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_collection_metadata: bool, + #[serde(default)] + pub show_raw_data: bool, + #[serde(default)] + pub show_unverified_collections: bool, + #[serde(default)] + pub show_grand_total: bool, + + #[serde(skip)] + pub cdn_prefix: Option, +} diff --git a/digital_asset_types/src/rpc/filter.rs b/digital_asset_types/src/rpc/filter.rs index f6aa010f..0de5574b 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 356b00c5..abad6a8c 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; pub mod transform; diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs index 7ed30fef..c9bf68cc 100644 --- a/digital_asset_types/src/rpc/response.rs +++ b/digital_asset_types/src/rpc/response.rs @@ -22,7 +22,8 @@ pub struct GetGroupingResponse { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)] #[serde(default)] pub struct AssetList { - // pub grand_total: u64, // TODO: not adding this for now because we aren't committing to the schema + #[serde(skip_serializing_if = "Option::is_none")] + pub grand_total: Option, pub total: u32, pub limit: u32, #[serde(skip_serializing_if = "Option::is_none")] @@ -31,6 +32,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, @@ -47,5 +50,5 @@ pub struct TransactionSignatureList { pub before: Option, #[serde(skip_serializing_if = "Option::is_none")] pub after: Option, - pub items: Vec>, + pub items: Vec<(String, String)>, } diff --git a/digital_asset_types/tests/json_parsing.rs b/digital_asset_types/tests/json_parsing.rs index fcd2e852..9f529ede 100644 --- a/digital_asset_types/tests/json_parsing.rs +++ b/digital_asset_types/tests/json_parsing.rs @@ -4,6 +4,7 @@ use digital_asset_types::dao::asset_data; use digital_asset_types::dao::sea_orm_active_enums::{ChainMutability, Mutability}; use digital_asset_types::dapi::common::v1_content_from_json; use digital_asset_types::json::ChainDataV1; +use digital_asset_types::rpc::display_options::DisplayOptions; use digital_asset_types::rpc::Content; use digital_asset_types::rpc::File; use solana_sdk::signature::Keypair; @@ -18,8 +19,7 @@ pub async fn load_test_json(file_name: &str) -> serde_json::Value { pub async fn parse_onchain_json( json: serde_json::Value, - cdn_prefix: Option, - raw_data: Option, + display_options: &DisplayOptions, ) -> Content { let asset_data = asset_data::Model { id: Keypair::new().pubkey().to_bytes().to_vec(), @@ -42,14 +42,17 @@ pub async fn parse_onchain_json( raw_symbol: Some(String::from(" ").into_bytes().to_vec()), }; - v1_content_from_json(&asset_data, cdn_prefix, raw_data).unwrap() + v1_content_from_json(&asset_data, display_options).unwrap() } #[tokio::test] async fn simple_content() { - let cdn_prefix = None; + let mut display_options = DisplayOptions::default(); + + display_options.cdn_prefix = None; + display_options.show_raw_data = true; let j = load_test_json("mad_lad.json").await; - let mut parsed = parse_onchain_json(j.clone(), cdn_prefix.clone(), Some(true)).await; + let mut parsed = parse_onchain_json(j.clone(), &display_options).await; assert_eq!( parsed.files, Some(vec![ @@ -83,19 +86,9 @@ async fn simple_content() { _ => panic!("symbol key not found or not a string"), } - parsed = parse_onchain_json(j.clone(), cdn_prefix.clone(), Some(false)).await; - - match parsed.metadata.get_item("name") { - Some(serde_json::Value::String(name)) => assert_eq!(name, "Handalf"), - _ => panic!("name key not found or not a string"), - } - - match parsed.metadata.get_item("symbol") { - Some(serde_json::Value::String(symbol)) => assert_eq!(symbol, ""), - _ => panic!("symbol key not found or not a string"), - } - - parsed = parse_onchain_json(j, cdn_prefix, None).await; + display_options.cdn_prefix = None; + display_options.show_raw_data = false; + parsed = parse_onchain_json(j.clone(), &display_options).await; match parsed.metadata.get_item("name") { Some(serde_json::Value::String(name)) => assert_eq!(name, "Handalf"), @@ -133,9 +126,11 @@ async fn simple_content() { #[tokio::test] async fn simple_content_with_cdn() { - let cdn_prefix = Some("https://cdn.foobar.blah".to_string()); + let mut display_options = DisplayOptions::default(); + display_options.cdn_prefix = Some("https://cdn.foobar.blah".to_string()); + let j = load_test_json("mad_lad.json").await; - let parsed = parse_onchain_json(j, cdn_prefix, None).await; + let parsed = parse_onchain_json(j, &display_options).await; assert_eq!( parsed.files, Some(vec![ @@ -162,9 +157,10 @@ async fn simple_content_with_cdn() { #[tokio::test] async fn complex_content() { - let cdn_prefix = None; + let mut display_options = DisplayOptions::default(); + display_options.cdn_prefix = None; let j = load_test_json("infinite_fungi.json").await; - let parsed = parse_onchain_json(j, cdn_prefix, None).await; + let parsed = parse_onchain_json(j, &display_options).await; assert_eq!( parsed.files, Some(vec![ @@ -216,9 +212,10 @@ async fn complex_content() { #[tokio::test] async fn complex_content_with_cdn() { - let cdn_prefix = Some("https://cdn.foobar.blah".to_string()); + let mut display_options = DisplayOptions::default(); + display_options.cdn_prefix = Some("https://cdn.foobar.blah".to_string()); let j = load_test_json("infinite_fungi.json").await; - let parsed = parse_onchain_json(j, cdn_prefix, None).await; + let parsed = parse_onchain_json(j, &display_options).await; assert_eq!( parsed.files, Some(vec![ diff --git a/migration/Cargo.lock b/migration/Cargo.lock index 126f6409..a1bbdb73 100644 --- a/migration/Cargo.lock +++ b/migration/Cargo.lock @@ -5598,33 +5598,3 @@ dependencies = [ "cc", "libc", ] - -[[patch.unused]] -name = "anchor-lang" -version = "0.25.0" -source = "git+https://github.com/metaplex-foundation/anchor#b00fe70d1bb2bf38807bcb3a1987cfb14eb62058" - -[[patch.unused]] -name = "blockbuster" -version = "0.7.3" -source = "git+https://github.com/metaplex-foundation/blockbuster?branch=1.14#e855eeb1f53030122afc9e3dd1b1124c818c64d6" - -[[patch.unused]] -name = "mpl-bubblegum" -version = "0.7.0" -source = "git+https://github.com/metaplex-foundation/metaplex-program-library?branch=update-deps#94b06af274ba7eff1f02546b35dd922912d0136b" - -[[patch.unused]] -name = "mpl-candy-guard" -version = "0.3.0" -source = "git+https://github.com/metaplex-foundation/mpl-candy-guard?branch=update-deps#7abc0b7640b807640850131d14214edbf12248bc" - -[[patch.unused]] -name = "mpl-candy-machine-core" -version = "0.2.0" -source = "git+https://github.com/metaplex-foundation/metaplex-program-library?branch=update-deps#94b06af274ba7eff1f02546b35dd922912d0136b" - -[[patch.unused]] -name = "mpl-token-metadata" -version = "1.7.0" -source = "git+https://github.com/metaplex-foundation/metaplex-program-library?branch=update-deps#94b06af274ba7eff1f02546b35dd922912d0136b" diff --git a/migration/Cargo.toml b/migration/Cargo.toml index d70c3910..3348b6d7 100644 --- a/migration/Cargo.toml +++ b/migration/Cargo.toml @@ -9,22 +9,14 @@ name = "migration" path = "src/lib.rs" [dependencies] -async-std = { version = "^1", features = ["attributes", "tokio1", ] } -digital_asset_types = { path = "../digital_asset_types", features = ["json_types", "sql_types"] } +async-std = { version = "^1", features = ["attributes", "tokio1"] } +digital_asset_types = { path = "../digital_asset_types", features = [ + "json_types", + "sql_types", +] } enum-iterator = "1.2.0" enum-iterator-derive = "1.1.0" [dependencies.sea-orm-migration] version = "0.10.6" -features = [ - "runtime-tokio-rustls", - "sqlx-postgres", -] - -[patch.crates-io] -blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster", branch="1.14" } -anchor-lang = { git="https://github.com/metaplex-foundation/anchor" } -mpl-token-metadata = { git="https://github.com/metaplex-foundation/metaplex-program-library", branch="update-deps"} -mpl-candy-machine-core = { git="https://github.com/metaplex-foundation/metaplex-program-library", branch="update-deps"} -mpl-candy-guard = { git="https://github.com/metaplex-foundation/mpl-candy-guard", branch="update-deps"} -mpl-bubblegum = "=1.0.1-beta.2" +features = ["runtime-tokio-rustls", "sqlx-postgres"]