From b8214bf687326917b86b25ff5b5f4727dcffeaf2 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Fri, 13 Oct 2023 00:40:23 -0700 Subject: [PATCH] Add code to index Bubblegum Update Metadata --- Cargo.lock | 6 +- das_api/Cargo.toml | 4 +- digital_asset_types/Cargo.toml | 2 +- docker-compose.yaml | 2 +- nft_ingester/Cargo.toml | 4 +- .../bubblegum/collection_verification.rs | 6 +- .../src/program_transformers/bubblegum/db.rs | 110 ++++++- .../program_transformers/bubblegum/mint_v1.rs | 80 +++-- .../src/program_transformers/bubblegum/mod.rs | 11 +- .../bubblegum/update_metadata.rs | 273 ++++++++++++++++++ tools/fetch_trees/Cargo.toml | 2 +- 11 files changed, 438 insertions(+), 62 deletions(-) create mode 100644 nft_ingester/src/program_transformers/bubblegum/update_metadata.rs diff --git a/Cargo.lock b/Cargo.lock index d10ad0434..24d4bcd39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -882,7 +882,7 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blockbuster" version = "0.8.0" -source = "git+https://github.com/metaplex-foundation/blockbuster.git?rev=e5d96d62969af42ce1c7f10cca88dc1c4f643598#e5d96d62969af42ce1c7f10cca88dc1c4f643598" +source = "git+https://github.com/metaplex-foundation/blockbuster.git?rev=f5f7a3dc2c95021e61211401dbbb447bef3514a9#f5f7a3dc2c95021e61211401dbbb447bef3514a9" dependencies = [ "anchor-lang", "async-trait", @@ -2881,9 +2881,9 @@ dependencies = [ [[package]] name = "mpl-bubblegum" -version = "1.0.1-beta.2" +version = "1.0.1-beta.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b271e10afc5a53a0615455b7cf9bb0901f65a1aae99e4cfe6290ff4eb6e21634" +checksum = "29346f26192bb7f73330196fde4c8cfb35675bf1a4b026cd088f7ca8fda69f3f" dependencies = [ "borsh 0.10.3", "kaigan", diff --git a/das_api/Cargo.toml b/das_api/Cargo.toml index 3b0bb4def..9b3129ab5 100644 --- a/das_api/Cargo.toml +++ b/das_api/Cargo.toml @@ -33,9 +33,9 @@ schemars = "0.8.6" schemars_derive = "0.8.6" open-rpc-derive = { version = "0.0.4"} open-rpc-schema = { version = "0.0.4"} -blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster.git", rev = "e5d96d62969af42ce1c7f10cca88dc1c4f643598" } +blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster.git", rev = "f5f7a3dc2c95021e61211401dbbb447bef3514a9" } anchor-lang = "0.28.0" mpl-token-metadata = { version = "=2.0.0-beta.1", features = ["serde-feature"] } mpl-candy-machine-core = { version = "2.0.1", features = ["no-entrypoint"] } -mpl-bubblegum = "1.0.1-beta.2" +mpl-bubblegum = "1.0.1-beta.3" mpl-candy-guard = { version = "2.0.0", features = ["no-entrypoint"] } diff --git a/digital_asset_types/Cargo.toml b/digital_asset_types/Cargo.toml index f5a8bb58d..27fedc362 100644 --- a/digital_asset_types/Cargo.toml +++ b/digital_asset_types/Cargo.toml @@ -18,7 +18,7 @@ solana-sdk = "~1.16.16" num-traits = "0.2.15" num-derive = "0.3.3" thiserror = "1.0.31" -blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster.git", rev = "e5d96d62969af42ce1c7f10cca88dc1c4f643598" } +blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster.git", rev = "f5f7a3dc2c95021e61211401dbbb447bef3514a9" } jsonpath_lib = "0.3.0" mime_guess = "2.0.4" url = "2.3.1" diff --git a/docker-compose.yaml b/docker-compose.yaml index d910d8ce7..be60e2e56 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -89,7 +89,7 @@ services: volumes: - ./db-data/:/var/lib/postgresql/data/:rw solana: - image: ghcr.io/metaplex-foundation/plerkle-test-validator:v1.5.1-1.64.0-v1.14.15 + image: ghcr.io/metaplex-foundation/plerkle-test-validator:v1.6.0-1.69.0-v1.16.6 volumes: - ./programs:/so/:ro - ./ledger:/config:rw diff --git a/nft_ingester/Cargo.toml b/nft_ingester/Cargo.toml index b8416c0f7..b1f4d1fd8 100644 --- a/nft_ingester/Cargo.toml +++ b/nft_ingester/Cargo.toml @@ -29,13 +29,13 @@ flatbuffers = "23.1.21" lazy_static = "1.4.0" regex = "1.5.5" digital_asset_types = { path = "../digital_asset_types", features = ["json_types", "sql_types"] } -mpl-bubblegum = "1.0.1-beta.2" +mpl-bubblegum = "1.0.1-beta.3" spl-account-compression = { version = "0.2.0", features = ["no-entrypoint"] } spl-concurrent-merkle-tree = "0.2.0" uuid = "1.0.0" async-trait = "0.1.53" num-traits = "0.2.15" -blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster.git", rev = "e5d96d62969af42ce1c7f10cca88dc1c4f643598" } +blockbuster = { git = "https://github.com/metaplex-foundation/blockbuster.git", rev = "f5f7a3dc2c95021e61211401dbbb447bef3514a9" } figment = { version = "0.10.6", features = ["env", "toml", "yaml"] } cadence = "0.29.0" cadence-macros = "0.29.0" diff --git a/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs b/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs index 7517f1544..e6825774a 100644 --- a/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs +++ b/nft_ingester/src/program_transformers/bubblegum/collection_verification.rs @@ -26,7 +26,7 @@ where let (collection, verify) = match payload { Payload::CollectionVerification { collection, verify, .. - } => (collection.clone(), verify.clone()), + } => (collection, verify), _ => { return Err(IngesterError::ParsingError( "Ix not parsed correctly".to_string(), @@ -64,8 +64,8 @@ where txn, id_bytes.to_vec(), Some(Collection { - key: collection.clone(), - verified: verify, + key: *collection, + verified: *verify, }), bundle.slot as i64, seq as i64, diff --git a/nft_ingester/src/program_transformers/bubblegum/db.rs b/nft_ingester/src/program_transformers/bubblegum/db.rs index 7e930abdc..58f65eafe 100644 --- a/nft_ingester/src/program_transformers/bubblegum/db.rs +++ b/nft_ingester/src/program_transformers/bubblegum/db.rs @@ -1,6 +1,7 @@ use crate::error::IngesterError; use digital_asset_types::dao::{ - asset, asset_creators, asset_grouping, backfill_items, cl_audits, cl_items, + asset, asset_creators, asset_data, asset_grouping, backfill_items, cl_audits, cl_items, + sea_orm_active_enums::{ChainMutability, Mutability}, }; use log::{debug, info}; use mpl_bubblegum::types::Collection; @@ -9,8 +10,6 @@ use sea_orm::{ }; use spl_account_compression::events::ChangeLogEventV1; -use std::convert::From; - pub async fn save_changelog_event<'c, T>( change_log_event: &ChangeLogEventV1, slot: u64, @@ -68,7 +67,7 @@ where ..Default::default() }; - let mut audit_item: Option = if (cl_audits) { + let audit_item: Option = if cl_audits { let mut ai: cl_audits::ActiveModel = item.clone().into(); ai.tx = Set(txn_id.to_string()); Some(ai) @@ -135,6 +134,7 @@ where //TODO -> set maximum size of path and break into multiple statements } +#[allow(clippy::too_many_arguments)] pub async fn upsert_asset_with_leaf_info( txn: &T, id: Vec, @@ -441,3 +441,105 @@ where Ok(()) } + +pub async fn upsert_asset_data( + txn: &T, + id: Vec, + chain_data_mutability: ChainMutability, + chain_data: JsonValue, + metadata_url: String, + metadata_mutability: Mutability, + metadata: JsonValue, + slot_updated: i64, + reindex: Option, + raw_name: Vec, + raw_symbol: Vec, + seq: i64, +) -> Result<(), IngesterError> +where + T: ConnectionTrait + TransactionTrait, +{ + let model = asset_data::ActiveModel { + id: Set(id), + chain_data_mutability: Set(chain_data_mutability), + chain_data: Set(chain_data), + metadata_url: Set(metadata_url), + metadata_mutability: Set(metadata_mutability), + metadata: Set(metadata), + slot_updated: Set(slot_updated), + reindex: Set(reindex), + raw_name: Set(raw_name), + raw_symbol: Set(raw_symbol), + //seq: Set(seq), + }; + + let mut query = asset_data::Entity::insert(model) + .on_conflict( + OnConflict::columns([asset_data::Column::Id]) + .update_columns([ + asset_data::Column::ChainDataMutability, + asset_data::Column::ChainData, + asset_data::Column::MetadataUrl, + asset_data::Column::MetadataMutability, + //TODO DEAL WITH THIS + //asset_data::Column::Metadata, + asset_data::Column::SlotUpdated, + asset_data::Column::Reindex, + asset_data::Column::RawName, + asset_data::Column::RawSymbol, + //asset_data::Column::Seq, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + // TODO DEAL WITH THIS + "{} WHERE (excluded.slot_updated > asset_data.slot_updated) AND (excluded.seq >= asset_data.seq OR asset_data.seq IS NULL)", + query.sql +); + txn.execute(query) + .await + .map_err(|db_err| IngesterError::StorageWriteError(db_err.to_string()))?; + + Ok(()) +} + +pub async fn upsert_asset_with_royalty_amount( + txn: &T, + id: Vec, + royalty_amount: i32, + seq: i64, +) -> Result<(), IngesterError> +where + T: ConnectionTrait + TransactionTrait, +{ + let model = asset::ActiveModel { + id: Set(id), + royalty_amount: Set(royalty_amount), + //royalty_amount_seq: Set(Some(seq)), + ..Default::default() + }; + + let mut query = asset::Entity::insert(model) + .on_conflict( + OnConflict::column(asset::Column::Id) + .update_columns([ + asset::Column::RoyaltyAmount, + //asset::Column::RoyaltyAmountSeq, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + + query.sql = format!( + // TODO DEAL WITH THIS + "{} WHERE (NOT asset.was_decompressed) AND (excluded.royalty_amount_seq >= asset.royalty_amount_seq OR royalty_amount_seq.seq IS NULL)", + query.sql + ); + + txn.execute(query) + .await + .map_err(|db_err| IngesterError::StorageWriteError(db_err.to_string()))?; + + Ok(()) +} diff --git a/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs b/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs index 5920a19d5..b0fdaecec 100644 --- a/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs +++ b/nft_ingester/src/program_transformers/bubblegum/mint_v1.rs @@ -1,8 +1,9 @@ use crate::{ error::IngesterError, program_transformers::bubblegum::{ - save_changelog_event, upsert_asset_with_compression_info, upsert_asset_with_leaf_info, - upsert_asset_with_owner_and_delegate_info, upsert_asset_with_seq, upsert_collection_info, + save_changelog_event, upsert_asset_data, upsert_asset_with_compression_info, + upsert_asset_with_leaf_info, upsert_asset_with_owner_and_delegate_info, + upsert_asset_with_royalty_amount, upsert_asset_with_seq, upsert_collection_info, }, tasks::{DownloadMetadata, IntoTaskData, TaskData}, }; @@ -17,7 +18,7 @@ use blockbuster::{ use chrono::Utc; use digital_asset_types::{ dao::{ - asset, asset_authority, asset_creators, asset_data, asset_v1_account_attachments, + asset, asset_authority, asset_creators, asset_v1_account_attachments, sea_orm_active_enums::{ChainMutability, Mutability, OwnerType, RoyaltyTargetType}, }, json::ChainDataV1, @@ -62,7 +63,14 @@ where let (edition_attachment_address, _) = find_master_edition_account(&id); let id_bytes = id.to_bytes(); let slot_i = bundle.slot as i64; + let uri = metadata.uri.replace('\0', ""); + if uri.is_empty() { + return Err(IngesterError::DeserializationError( + "URI is empty".to_string(), + )); + } + let name = metadata.name.clone().into_bytes(); let symbol = metadata.symbol.clone().into_bytes(); let mut chain_data = ChainDataV1 { @@ -84,46 +92,23 @@ where true => ChainMutability::Mutable, false => ChainMutability::Immutable, }; - if uri.is_empty() { - return Err(IngesterError::DeserializationError( - "URI is empty".to_string(), - )); - } - let data = asset_data::ActiveModel { - id: Set(id_bytes.to_vec()), - chain_data_mutability: Set(chain_mutability), - chain_data: Set(chain_data_json), - metadata_url: Set(uri), - metadata: Set(JsonValue::String("processing".to_string())), - 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()), - ..Default::default() - }; - let mut query = asset_data::Entity::insert(data) - .on_conflict( - OnConflict::columns([asset_data::Column::Id]) - .update_columns([ - asset_data::Column::ChainDataMutability, - asset_data::Column::ChainData, - asset_data::Column::MetadataUrl, - asset_data::Column::MetadataMutability, - asset_data::Column::SlotUpdated, - asset_data::Column::Reindex, - ]) - .to_owned(), - ) - .build(DbBackend::Postgres); - query.sql = format!( - "{} WHERE excluded.slot_updated > asset_data.slot_updated", - query.sql - ); - txn.execute(query) - .await - .map_err(|db_err| IngesterError::AssetIndexError(db_err.to_string()))?; + upsert_asset_data( + txn, + id_bytes.to_vec(), + chain_mutability, + chain_data_json, + uri, + Mutability::Mutable, + JsonValue::String("processing".to_string()), + slot_i, + Some(true), + name.to_vec(), + symbol.to_vec(), + seq as i64, + ) + .await?; + // Insert into `asset` table. let delegate = if owner == delegate || delegate.to_bytes() == [0; 32] { None @@ -143,7 +128,6 @@ where nonce: Set(Some(nonce as i64)), royalty_target_type: Set(RoyaltyTargetType::Creators), royalty_target: Set(None), - royalty_amount: Set(metadata.seller_fee_basis_points as i32), //basis points asset_data: Set(Some(id_bytes.to_vec())), slot_updated: Set(Some(slot_i)), ..Default::default() @@ -160,7 +144,6 @@ where asset::Column::SpecificationAssetClass, asset::Column::RoyaltyTargetType, asset::Column::RoyaltyTarget, - asset::Column::RoyaltyAmount, asset::Column::AssetData, ]) .to_owned(), @@ -176,6 +159,14 @@ where .await .map_err(|db_err| IngesterError::AssetIndexError(db_err.to_string()))?; + upsert_asset_with_royalty_amount( + txn, + id_bytes.to_vec(), + metadata.seller_fee_basis_points as i32, + seq as i64, + ) + .await?; + // Partial update of asset table with just compression info elements. upsert_asset_with_compression_info( txn, @@ -248,6 +239,7 @@ where if creators_set.contains(&c.address) { continue; } + db_creator_infos.push(asset_creators::ActiveModel { asset_id: Set(id_bytes.to_vec()), creator: Set(c.address.to_bytes().to_vec()), diff --git a/nft_ingester/src/program_transformers/bubblegum/mod.rs b/nft_ingester/src/program_transformers/bubblegum/mod.rs index af186808d..3ac69c9d8 100644 --- a/nft_ingester/src/program_transformers/bubblegum/mod.rs +++ b/nft_ingester/src/program_transformers/bubblegum/mod.rs @@ -17,6 +17,7 @@ mod delegate; mod mint_v1; mod redeem; mod transfer; +mod update_metadata; pub use db::*; @@ -53,7 +54,8 @@ where InstructionName::VerifyCollection => "VerifyCollection", InstructionName::UnverifyCollection => "UnverifyCollection", InstructionName::SetAndVerifyCollection => "SetAndVerifyCollection", - InstructionName::SetDecompressibleState | InstructionName::UpdateMetadata => todo!(), + InstructionName::SetDecompressibleState => "SetDecompressibleState", + InstructionName::UpdateMetadata => "UpdateMetadata", }; info!("BGUM instruction txn={:?}: {:?}", ix_str, bundle.txn_id); @@ -92,6 +94,13 @@ where | InstructionName::SetAndVerifyCollection => { collection_verification::process(parsing_result, bundle, txn, cl_audits).await?; } + InstructionName::SetDecompressibleState => (), // Nothing to index. + InstructionName::UpdateMetadata => { + let task = + update_metadata::update_metadata(parsing_result, bundle, txn, cl_audits).await?; + + task_manager.send(task)?; + } _ => debug!("Bubblegum: Not Implemented Instruction"), } Ok(()) diff --git a/nft_ingester/src/program_transformers/bubblegum/update_metadata.rs b/nft_ingester/src/program_transformers/bubblegum/update_metadata.rs new file mode 100644 index 000000000..2a0d79ed8 --- /dev/null +++ b/nft_ingester/src/program_transformers/bubblegum/update_metadata.rs @@ -0,0 +1,273 @@ +use crate::{ + error::IngesterError, + program_transformers::bubblegum::{ + save_changelog_event, upsert_asset_data, upsert_asset_with_leaf_info, + upsert_asset_with_royalty_amount, upsert_asset_with_seq, + }, + tasks::{DownloadMetadata, IntoTaskData, TaskData}, +}; +use blockbuster::{ + instruction::InstructionBundle, + programs::bubblegum::{BubblegumInstruction, LeafSchema, Payload}, + token_metadata::state::{TokenStandard, UseMethod, Uses}, +}; +use chrono::Utc; +use digital_asset_types::{ + dao::{ + asset_creators, + sea_orm_active_enums::{ChainMutability, Mutability}, + }, + json::ChainDataV1, +}; +use num_traits::FromPrimitive; +use sea_orm::{ + entity::*, query::*, sea_query::OnConflict, ConnectionTrait, DbBackend, EntityTrait, JsonValue, +}; +use std::collections::HashSet; + +pub async fn update_metadata<'c, T>( + parsing_result: &BubblegumInstruction, + bundle: &InstructionBundle<'c>, + txn: &'c T, + cl_audits: bool, +) -> Result +where + T: ConnectionTrait + TransactionTrait, +{ + if let ( + Some(le), + Some(cl), + Some(Payload::UpdateMetadata { + current_metadata, + update_args, + }), + ) = ( + &parsing_result.leaf_update, + &parsing_result.tree_update, + &parsing_result.payload, + ) { + let seq = save_changelog_event(cl, bundle.slot, bundle.txn_id, txn, cl_audits).await?; + #[allow(unreachable_patterns)] + return match le.schema { + LeafSchema::V1 { id, nonce, .. } => { + let id_bytes = id.to_bytes(); + let slot_i = bundle.slot as i64; + + let uri = if let Some(uri) = &update_args.uri { + uri.replace('\0', "") + } else { + current_metadata.uri.replace('\0', "") + }; + if uri.is_empty() { + return Err(IngesterError::DeserializationError( + "URI is empty".to_string(), + )); + } + + let name = if let Some(name) = update_args.name.clone() { + name + } else { + current_metadata.name.clone() + }; + + let symbol = if let Some(symbol) = update_args.symbol.clone() { + symbol + } else { + current_metadata.symbol.clone() + }; + + let primary_sale_happened = + if let Some(primary_sale_happened) = update_args.primary_sale_happened { + primary_sale_happened + } else { + current_metadata.primary_sale_happened + }; + + let mut chain_data = ChainDataV1 { + name: name.clone(), + symbol: symbol.clone(), + edition_nonce: current_metadata.edition_nonce, + primary_sale_happened, + token_standard: Some(TokenStandard::NonFungible), + uses: current_metadata.uses.clone().map(|u| Uses { + use_method: UseMethod::from_u8(u.use_method as u8).unwrap(), + remaining: u.remaining, + total: u.total, + }), + }; + chain_data.sanitize(); + let chain_data_json = serde_json::to_value(chain_data) + .map_err(|e| IngesterError::DeserializationError(e.to_string()))?; + + let is_mutable = if let Some(is_mutable) = update_args.is_mutable { + is_mutable + } else { + current_metadata.is_mutable + }; + + let chain_mutability = if is_mutable { + ChainMutability::Mutable + } else { + ChainMutability::Immutable + }; + + upsert_asset_data( + txn, + id_bytes.to_vec(), + chain_mutability, + chain_data_json, + uri.clone(), + Mutability::Mutable, + JsonValue::String("processing".to_string()), + slot_i, + Some(true), + name.into_bytes().to_vec(), + symbol.into_bytes().to_vec(), + seq as i64, + ) + .await?; + + // Partial update of asset table with just seller fee basis points. + let seller_fee_basis_points = + if let Some(seller_fee_basis_points) = update_args.seller_fee_basis_points { + seller_fee_basis_points + } else { + current_metadata.seller_fee_basis_points + }; + + upsert_asset_with_royalty_amount( + txn, + id_bytes.to_vec(), + seller_fee_basis_points as i32, + seq as i64, + ) + .await?; + + // Partial update of asset table with just leaf. + let tree_id = bundle.keys.get(5).unwrap().0.to_vec(); + upsert_asset_with_leaf_info( + txn, + id_bytes.to_vec(), + nonce as i64, + tree_id, + le.leaf_hash.to_vec(), + le.schema.data_hash(), + le.schema.creator_hash(), + seq as i64, + false, + ) + .await?; + + upsert_asset_with_seq(txn, id_bytes.to_vec(), seq as i64).await?; + + // Update `asset_creators` table. + if let Some(creators) = &update_args.creators { + // Vec to hold base creator information. + let mut db_creator_infos = Vec::with_capacity(creators.len()); + + // Vec to hold info on whether a creator is verified. This info is protected by `seq` number. + let mut db_creator_verified_infos = Vec::with_capacity(creators.len()); + + // Set to prevent duplicates. + let mut creators_set = HashSet::new(); + + for (i, c) in creators.iter().enumerate() { + if creators_set.contains(&c.address) { + continue; + } + + db_creator_infos.push(asset_creators::ActiveModel { + asset_id: Set(id_bytes.to_vec()), + creator: Set(c.address.to_bytes().to_vec()), + position: Set(i as i16), + share: Set(c.share as i32), + slot_updated: Set(Some(slot_i)), + ..Default::default() + }); + + db_creator_verified_infos.push(asset_creators::ActiveModel { + asset_id: Set(id_bytes.to_vec()), + creator: Set(c.address.to_bytes().to_vec()), + verified: Set(c.verified), + seq: Set(Some(seq as i64)), + ..Default::default() + }); + + creators_set.insert(c.address); + } + + // Remove creators no longer present in creator array. + let db_creators_to_remove: Vec> = current_metadata + .creators + .iter() + .filter(|c| !creators_set.contains(&c.address)) + .map(|c| c.address.to_bytes().to_vec()) + .collect(); + + asset_creators::Entity::delete_many() + .filter( + Condition::all() + .add(asset_creators::Column::AssetId.eq(id_bytes.to_vec())) + .add(asset_creators::Column::Creator.is_in(db_creators_to_remove)) + // TODO WHAT IF SEQ IS NULL + .add(asset_creators::Column::Seq.lt(seq as i64)), + ) + .exec(txn) + .await?; + + // This statement will update base information for each creator. + let query = asset_creators::Entity::insert_many(db_creator_infos) + .on_conflict( + OnConflict::columns([ + asset_creators::Column::AssetId, + asset_creators::Column::Creator, + ]) + .update_columns([ + asset_creators::Column::Position, + asset_creators::Column::Share, + asset_creators::Column::SlotUpdated, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + txn.execute(query).await?; + + // This statement will update whether the creator is verified and the `seq` + // number. `seq` is used to protect the `verified` field, allowing for `mint` + // and `verifyCreator` to be processed out of order. + let mut query = asset_creators::Entity::insert_many(db_creator_verified_infos) + .on_conflict( + OnConflict::columns([ + asset_creators::Column::AssetId, + asset_creators::Column::Creator, + ]) + .update_columns([ + asset_creators::Column::Verified, + asset_creators::Column::Seq, + ]) + .to_owned(), + ) + .build(DbBackend::Postgres); + query.sql = format!( + "{} WHERE excluded.seq > asset_creators.seq OR asset_creators.seq IS NULL", + query.sql + ); + txn.execute(query).await?; + } + + // TODO DEAL WITH TASKS + let mut task = DownloadMetadata { + asset_data_id: id_bytes.to_vec(), + uri, + created_at: Some(Utc::now().naive_utc()), + }; + task.sanitize(); + return task.into_task_data(); + } + _ => Err(IngesterError::NotImplemented), + }?; + } + Err(IngesterError::ParsingError( + "Ix not parsed correctly".to_string(), + )) +} diff --git a/tools/fetch_trees/Cargo.toml b/tools/fetch_trees/Cargo.toml index 518b21c02..4035014ca 100644 --- a/tools/fetch_trees/Cargo.toml +++ b/tools/fetch_trees/Cargo.toml @@ -9,7 +9,7 @@ anyhow = "1.0.70" async-trait = "0.1.53" borsh = "~0.10.3" clap = { version = "4.2.2", features = ["derive", "cargo"] } -mpl-bubblegum = "1.0.1-beta.2" +mpl-bubblegum = "1.0.1-beta.3" solana-account-decoder = "~1.16.16" solana-client = "~1.16.16" solana-sdk = "~1.16.16"