Skip to content

Commit

Permalink
Merge pull request #88 from holaplex/abdul/batch-mint
Browse files Browse the repository at this point in the history
Support batch mint of random queued nfts
  • Loading branch information
imabdulbasit authored Oct 18, 2023
2 parents 21aff9e + c0e070b commit c889b5e
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 22 deletions.
13 changes: 10 additions & 3 deletions consumer/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use holaplex_hub_nfts_solana_core::proto::{
};
use holaplex_hub_nfts_solana_entity::{collection_mints, collections, update_revisions};
use hub_core::prelude::*;
use solana_program::pubkey::Pubkey;
use solana_program::{hash::Hash, pubkey::Pubkey};
#[derive(Clone)]
pub struct MasterEditionAddresses {
pub metadata: Pubkey,
Expand Down Expand Up @@ -77,6 +77,8 @@ pub struct TransferAssetAddresses {

/// Represents a response from a transaction on the blockchain. This struct
/// provides the serialized message and the signatures of the signed message.

#[derive(Clone)]
pub struct TransactionResponse<A> {
/// The serialized version of the message from the transaction.
pub serialized_message: Vec<u8>,
Expand Down Expand Up @@ -138,15 +140,20 @@ pub trait CollectionBackend {

#[async_trait]
pub trait MintBackend<T, R> {
async fn mint(&self, collection: &collections::Model, txn: T)
-> Result<TransactionResponse<R>>;
async fn mint(
&self,
collection: &collections::Model,
blockhash: Option<Hash>,
txn: T,
) -> Result<TransactionResponse<R>>;
}

#[async_trait]
pub trait TransferBackend<M, R> {
async fn transfer(
&self,
collection_mint: &M,

txn: TransferMetaplexAssetTransaction,
) -> Result<TransactionResponse<R>>;
}
168 changes: 161 additions & 7 deletions consumer/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use holaplex_hub_nfts_solana_core::{
MetaplexMasterEditionTransaction, MintMetaplexEditionTransaction,
MintMetaplexMetadataTransaction, SolanaCompletedMintTransaction,
SolanaCompletedTransferTransaction, SolanaCompletedUpdateTransaction,
SolanaFailedTransaction, SolanaNftEventKey, SolanaNftEvents, SolanaPendingTransaction,
SolanaFailedTransaction, SolanaMintOpenDropBatchedPayload, SolanaMintPendingTransactions,
SolanaMintTransaction, SolanaNftEventKey, SolanaNftEvents, SolanaPendingTransaction,
SolanaTransactionFailureReason, SwitchCollectionPayload, TransferMetaplexAssetTransaction,
UpdateSolanaMintPayload,
},
Expand All @@ -20,6 +21,7 @@ use holaplex_hub_nfts_solana_entity::{
collection_mints, collections, compression_leafs, update_revisions,
};
use hub_core::{
backon::{ExponentialBuilder, Retryable},
chrono::Utc,
metrics::KeyValue,
prelude::*,
Expand All @@ -29,6 +31,7 @@ use hub_core::{
uuid,
uuid::Uuid,
};
use solana_client::client_error::ClientError;
use solana_program::pubkey::{ParsePubkeyError, Pubkey};
use solana_sdk::signature::Signature;

Expand All @@ -39,6 +42,7 @@ use crate::{
},
metrics::Metrics,
solana::{CompressedRef, EditionRef, Solana, SolanaAssetIdError, UncompressedRef},
with_retry,
};

#[derive(Debug, thiserror::Error, Triage)]
Expand Down Expand Up @@ -126,6 +130,7 @@ pub enum EventKind {
UpdateOpenDrop,
RetryCreateOpenDrop,
RetryMintOpenDrop,
MintOpenDropBatched,
}

impl EventKind {
Expand All @@ -150,6 +155,7 @@ impl EventKind {
Self::UpdateOpenDrop => "open drop update",
Self::RetryCreateOpenDrop => "open drop creation retry",
Self::RetryMintOpenDrop => "open drop mint retry",
Self::MintOpenDropBatched => "open drop mint batch",
}
}

Expand Down Expand Up @@ -190,6 +196,7 @@ impl EventKind {
SolanaNftEvent::RetryCreateOpenDropSigningRequested(tx)
},
EventKind::RetryMintOpenDrop => SolanaNftEvent::RetryMintOpenDropSigningRequested(tx),
EventKind::MintOpenDropBatched => unreachable!(),
}
}

Expand Down Expand Up @@ -390,6 +397,7 @@ impl EventKind {
address: collection_mint.mint,
})
},
Self::MintOpenDropBatched => unreachable!(),
})
}

Expand All @@ -414,6 +422,7 @@ impl EventKind {
Self::UpdateOpenDrop => SolanaNftEvent::UpdateOpenDropFailed(tx),
Self::RetryCreateOpenDrop => SolanaNftEvent::RetryCreateOpenDropFailed(tx),
Self::RetryMintOpenDrop => SolanaNftEvent::RetryMintOpenDropFailed(tx),
Self::MintOpenDropBatched => unreachable!(),
}
}
}
Expand Down Expand Up @@ -625,6 +634,15 @@ impl Processor {
)
.await
},
Some(NftEvent::SolanaMintOpenDropBatched(payload)) => {
self.process_mint_batch(&key, payload).await.map_err(|e| {
ProcessorError::new(
e,
EventKind::MintOpenDropBatched,
ErrorSource::NftFailure,
)
})
},
_ => Ok(()),
}
},
Expand Down Expand Up @@ -714,6 +732,142 @@ impl Processor {
}
}

async fn process_mint_batch(
&self,
key: &SolanaNftEventKey,
payload: SolanaMintOpenDropBatchedPayload,
) -> ProcessResult<()> {
let conn = self.db.get();
let producer = self.producer.clone();

let collection_id = Uuid::parse_str(&payload.collection_id)?;
let collection = Collection::find_by_id(conn, collection_id)
.await?
.ok_or(ProcessorErrorKind::RecordNotFound)?;

let signers_pubkeys = vec![
self.solana().treasury_wallet().to_string(),
collection.owner.clone(),
];
let blockhash = with_retry!(self.solana().rpc().get_latest_blockhash())
.await
.context("blockhash not found")
.map_err(ProcessorErrorKind::Solana)?;

let send_event = |mint_transactions: Vec<SolanaMintTransaction>,
signers_pubkeys: Vec<String>| async {
producer
.send(
Some(&SolanaNftEvents {
event: Some(SolanaNftEvent::MintOpenDropBatchedSigningRequested(
SolanaMintPendingTransactions {
signers_pubkeys,
mint_transactions,
},
)),
}),
Some(key),
)
.await
};

if payload.compressed {
let backend = &CompressedRef(self.solana());
let mut leafs: Vec<compression_leafs::ActiveModel> = Vec::new();
let mut mint_transactions = Vec::new();

for mint_tx in payload.mint_open_drop_transactions.clone() {
let id = Uuid::from_str(&mint_tx.mint_id)?;

let tx = backend
.mint(
&collection,
Some(blockhash),
MintMetaplexMetadataTransaction {
recipient_address: mint_tx.recipient_address,
metadata: mint_tx.metadata,
collection_id: payload.collection_id.clone(),
compressed: payload.compressed,
},
)
.await
.map_err(ProcessorErrorKind::Solana)?;

mint_transactions.push(SolanaMintTransaction {
serialized_message: tx.serialized_message,
mint_id: mint_tx.mint_id,
signer_signature: None,
});

let compression_leaf = compression_leafs::Model {
id,
collection_id: collection.id,
merkle_tree: tx.addresses.merkle_tree.to_string(),
tree_authority: tx.addresses.tree_authority.to_string(),
tree_delegate: tx.addresses.tree_delegate.to_string(),
leaf_owner: tx.addresses.leaf_owner.to_string(),
created_at: Utc::now().naive_utc(),
..Default::default()
};

leafs.push(compression_leaf.into());
}

compression_leafs::Entity::insert_many(leafs)
.exec(conn)
.await?;

send_event(mint_transactions, signers_pubkeys).await?;

return Ok(());
}

let backend = &UncompressedRef(self.solana());
let mut mints: Vec<collection_mints::ActiveModel> = Vec::new();
let mut mint_transactions = Vec::new();

for mint_tx in payload.mint_open_drop_transactions.clone() {
let id = Uuid::from_str(&mint_tx.mint_id)?;
let tx = backend
.mint(
&collection,
Some(blockhash),
MintMetaplexMetadataTransaction {
recipient_address: mint_tx.recipient_address,
metadata: mint_tx.metadata,
collection_id: payload.collection_id.clone(),
compressed: payload.compressed,
},
)
.await
.map_err(ProcessorErrorKind::Solana)?;

mint_transactions.push(SolanaMintTransaction {
serialized_message: tx.serialized_message,
mint_id: mint_tx.mint_id,
signer_signature: tx.signatures_or_signers_public_keys.get(1).cloned(),
});

let collection_mint = collection_mints::Model {
id,
collection_id: collection.id,
owner: tx.addresses.recipient.to_string(),
mint: tx.addresses.mint.to_string(),
created_at: Utc::now().naive_utc(),
associated_token_account: tx.addresses.associated_token_account.to_string(),
};

mints.push(collection_mint.into());
}

collection_mints::Entity::insert_many(mints)
.exec(conn)
.await?;

send_event(mint_transactions, signers_pubkeys).await?;
Ok(())
}

async fn process_nft(
&self,
kind: EventKind,
Expand Down Expand Up @@ -880,7 +1034,7 @@ impl Processor {
let backend = &CompressedRef(self.solana());

let tx = backend
.mint(&collection, payload)
.mint(&collection, None, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand Down Expand Up @@ -911,7 +1065,7 @@ impl Processor {
let backend = &UncompressedRef(self.solana());

let tx = backend
.mint(&collection, payload)
.mint(&collection, None, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand Down Expand Up @@ -951,7 +1105,7 @@ impl Processor {
.ok_or(ProcessorErrorKind::RecordNotFound)?;

let tx = backend
.mint(&collection, payload)
.mint(&collection, None, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand Down Expand Up @@ -1165,7 +1319,7 @@ impl Processor {
let collection = collection.ok_or(ProcessorErrorKind::RecordNotFound)?;

let tx = backend
.mint(&collection, payload)
.mint(&collection, None, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand Down Expand Up @@ -1205,7 +1359,7 @@ impl Processor {
let backend = &CompressedRef(self.solana());

let tx = backend
.mint(&collection, payload)
.mint(&collection, None, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand All @@ -1228,7 +1382,7 @@ impl Processor {
let backend = &UncompressedRef(self.solana());

let tx = backend
.mint(&collection, payload)
.mint(&collection, None, payload)
.await
.map_err(ProcessorErrorKind::Solana)?;

Expand Down
Loading

0 comments on commit c889b5e

Please sign in to comment.