diff --git a/das_api/src/api/api_impl.rs b/das_api/src/api/api_impl.rs index e7ffa95bb..8678c8421 100644 --- a/das_api/src/api/api_impl.rs +++ b/das_api/src/api/api_impl.rs @@ -9,13 +9,14 @@ use digital_asset_types::{ dapi::{ 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, + OwnershipModel, RoyaltyModel }, - rpc::{OwnershipModel, RoyaltyModel}, + rpc::{}, }; use open_rpc_derive::document_rpc; use sea_orm::{sea_query::ConditionType, ConnectionTrait, DbBackend, Statement}; @@ -32,6 +33,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 +499,38 @@ 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, + sort_by, + cursor, + } = payload; + + if !((id.is_some() && tree.is_none() && leaf_index.is_none()) + || (id.is_none() && tree.is_some() && leaf_index.is_some())) + { + return Err(DasApiError::ValidationError( + "Must provide either 'id' or both 'tree' and 'leafIndex'".to_string(), + )); + } + let id = validate_opt_pubkey(&id)?; + let tree = validate_opt_pubkey(&tree)?; + let 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 3c8ad5f82..14ba499fa 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,21 @@ 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, + pub sort_by: Option, + #[serde(default)] + pub cursor: Option, +} + #[document_rpc] #[async_trait] pub trait ApiContract: Send + Sync + 'static { @@ -229,4 +244,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..ebe406e25 100644 --- a/digital_asset_types/src/dao/scopes/asset.rs +++ b/digital_asset_types/src/dao/scopes/asset.rs @@ -1,8 +1,13 @@ -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, cl_audits, Cursor, FullAsset, + GroupingSize, Pagination, + }, + dapi::common::safe_select, + rpc::response::AssetList, }; +// >>>>>>> helius-nikhil/get-sigs-for-asset use indexmap::IndexMap; use sea_orm::{entity::*, query::*, ConnectionTrait, DbErr, Order}; @@ -397,3 +402,71 @@ 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, + 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, sort_direction, 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, sort_direction, pagination, limit).await?; + Ok(transactions) + } else { + Ok(Vec::new()) + } +} + +pub async fn fetch_transactions( + conn: &impl ConnectionTrait, + tree: Vec, + leaf_id: i64, + sort_direction: Order, + 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, sort_direction, cl_audits::Column::Id); + 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) +} diff --git a/digital_asset_types/src/dapi/common/asset.rs b/digital_asset_types/src/dapi/common/asset.rs index 68be6482a..007d793f9 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,35 @@ 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, 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) + } + Pagination::Page { page } => (Some(*page), None, None, None), + Pagination::Cursor(_) => { + // tmp: helius please fix it ;) + (None, None, None, None) + } + }; + TransactionSignatureList { + total, + limit: limit as u32, + page: page.map(|x| x as u32), + before, + after, + cursor, + 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), @@ -292,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/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..f34893800 --- /dev/null +++ b/digital_asset_types/src/dapi/signatures_for_asset.rs @@ -0,0 +1,36 @@ +use crate::dao::scopes; +use crate::dao::PageOptions; +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, + sorting: AssetSorting, + page_options: &PageOptions, +) -> Result { + 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, + page_options.limit + ) + .await?; + Ok(build_transaction_signatures_response( + transactions, + page_options.limit, + &pagination, + )) +} diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs index 3945f6955..2fe0bc6ae 100644 --- a/digital_asset_types/src/rpc/response.rs +++ b/digital_asset_types/src/rpc/response.rs @@ -36,3 +36,19 @@ 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, + #[serde(skip_serializing_if = "Option::is_none")] + pub cursor: 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()