use crate::blocks::burn::BurnTransaction; use crate::blocks::collateral::CollateralClaimTransaction; use crate::blocks::issue_token::IssueTokenTransaction; use crate::blocks::loan_payment::ContractPaymentTransaction; use crate::blocks::loans::LoanContractTransaction; use crate::blocks::marketing::MarketingTransaction; use crate::blocks::nft::CreateNftTransaction; use crate::blocks::swap::SwapTransaction; use crate::blocks::token::CreateTokenTransaction; use crate::blocks::transfer::TransferTransaction; use crate::blocks::vanity::VanityAddressTransaction; use crate::common::nft_assets::nft_asset_name; use crate::common::types::Transaction; use crate::decode; use crate::log::warn; use crate::records::memory::mempool::{db_client, restore_processed_by_signatures, BASECOIN}; use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid; use crate::rpc::responses::RpcResponse; use crate::sled::Db; use crate::verifications::async_funcs::checks::balance_check::balance_checkup; async fn restore_if_spendable( tx_label: &str, txid: &str, signatures: &[String], spendable: bool, insert: F, ) where F: FnOnce() -> Fut, Fut: std::future::Future>>, { if !spendable { warn!("[mempool_restore] skipped rolled-back tx: type={tx_label} txid={txid} reason=not_spendable_after_rollback"); return; } match restore_processed_by_signatures(signatures).await { Ok(true) => {} Ok(false) => { if let Err(err) = insert().await { warn!("[mempool_restore] failed to reinsert rolled-back tx: type={tx_label} txid={txid} err={err}"); } } Err(err) => { warn!("[mempool_restore] failed to unmark processed rolled-back tx: type={tx_label} txid={txid} err={err}"); if let Err(insert_err) = insert().await { warn!("[mempool_restore] failed to reinsert rolled-back tx after unmark error: type={tx_label} txid={txid} err={insert_err}"); } } } } async fn restore_if_spendable_void( tx_label: &str, txid: &str, signatures: &[String], spendable: bool, insert: F, ) where F: FnOnce() -> Fut, Fut: std::future::Future, { restore_if_spendable(tx_label, txid, signatures, spendable, || async { insert().await; Ok(()) }) .await; } async fn loan_coin_for_contract(db: &Db, contract_hash: &str) -> Option { let contract_key = decode(contract_hash).ok()?; let RpcResponse::Binary(bytes) = request_transaction_by_txid(db, contract_key).await; if bytes.is_empty() || bytes[0] != 7 { return None; } LoanContractTransaction::from_bytes(7, &bytes[1..]) .await .ok() .map(|contract| contract.unsigned_loan_contract.loan_coin) } async fn restore_loan_contract_row( transaction: &LoanContractTransaction, ) -> Result<(), Box> { let client_handle = db_client().await?; let client = client_handle.as_ref(); let hash = &transaction.hash; let signature1 = &transaction.signature1; let signature2 = &transaction.signature2; client .execute( r#" UPDATE loan_contract SET processed=false, processed_block_number=NULL WHERE signature1 = $1 OR signature2 = $2 OR hash = $3 OR txid = $3 "#, &[signature1, signature2, hash], ) .await?; let row = client .query_one( r#" SELECT COUNT(*) FROM loan_contract WHERE processed=false AND ( signature1 = $1 OR signature2 = $2 OR hash = $3 OR txid = $3 ) "#, &[signature1, signature2, hash], ) .await?; let pending_count: i64 = row.get(0); if pending_count == 0 { transaction.add_to_memory().await?; } Ok(()) } pub async fn restore_transfer(transaction: &TransferTransaction, db: &Db) { let transfer = &transaction.unsigned_transfer; let asset = nft_asset_name(&transfer.coin, transfer.nft_series); let spendable = balance_checkup(db, transfer.value, transfer.txfee, asset, &transfer.sender).await; let signature = transaction.signature.clone(); restore_if_spendable_void("transfer", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }) .await; } pub async fn restore_burn(transaction: &BurnTransaction, db: &Db) { let burn = &transaction.unsigned_burn; let asset = nft_asset_name(&burn.coin, burn.nft_series); let spendable = balance_checkup(db, burn.value, burn.txfee, asset, &burn.address).await; let signature = transaction.signature.clone(); restore_if_spendable_void("burn", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }) .await; } pub async fn restore_create_token(transaction: &CreateTokenTransaction, db: &Db) { let token = &transaction.unsigned_create_token; let spendable = balance_checkup(db, 0, token.txfee, BASECOIN.clone(), &token.creator).await; let signature = transaction.signature.clone(); restore_if_spendable_void( "create_token", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }, ) .await; } pub async fn restore_issue_token(transaction: &IssueTokenTransaction, db: &Db) { let token = &transaction.unsigned_issue_token; let spendable = balance_checkup(db, 0, token.txfee, BASECOIN.clone(), &token.creator).await; let signature = transaction.signature.clone(); restore_if_spendable_void( "issue_token", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }, ) .await; } pub async fn restore_create_nft(transaction: &CreateNftTransaction, db: &Db) { let nft = &transaction.unsigned_create_nft; let spendable = balance_checkup(db, 0, nft.txfee, BASECOIN.clone(), &nft.creator).await; let signature = transaction.signature.clone(); restore_if_spendable_void("create_nft", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }) .await; } pub async fn restore_marketing(transaction: &MarketingTransaction, db: &Db) { let marketing = &transaction.unsigned_marketing; let spendable = balance_checkup( db, 0, marketing.txfee, BASECOIN.clone(), &marketing.advertiser, ) .await; let signature = transaction.signature.clone(); restore_if_spendable_void("marketing", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }) .await; } pub async fn restore_swap(transaction: &SwapTransaction, db: &Db) { let swap = &transaction.unsigned_swap; let asset1 = nft_asset_name(&swap.ticker1, swap.nft_series1); let asset2 = nft_asset_name(&swap.ticker2, swap.nft_series2); let value1 = swap.value1.saturating_add(swap.tip1); let value2 = swap.value2.saturating_add(swap.tip2); let sender1_spendable = balance_checkup(db, value1, swap.txfee1, asset1, &swap.sender1).await; let sender2_spendable = balance_checkup(db, value2, swap.txfee2, asset2, &swap.sender2).await; let signatures = vec![ transaction.signature1.clone(), transaction.signature2.clone(), ]; restore_if_spendable_void( "swap", "unknown", &signatures, sender1_spendable && sender2_spendable, || async { let _ = transaction.add_to_memory().await; }, ) .await; } pub async fn restore_loan_creation(transaction: &LoanContractTransaction, _db: &Db) { if let Err(err) = restore_loan_contract_row(transaction).await { warn!( "[mempool_restore] failed to restore rolled-back loan_contract txid={} err={err}", transaction.hash ); } } pub async fn restore_borrower(transaction: &ContractPaymentTransaction, db: &Db) { let payment = &transaction.unsigned_contract_payment; let Some(loan_coin) = loan_coin_for_contract(db, &payment.contract_hash).await else { return; }; let value = payment.payback_amount.saturating_add(payment.tip); let spendable = balance_checkup(db, value, payment.txfee, loan_coin, &payment.address).await; let signature = transaction.signature.clone(); restore_if_spendable_void( "loan_payment", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }, ) .await; } pub async fn restore_collateral(transaction: &CollateralClaimTransaction, db: &Db) { let collateral = &transaction.unsigned_collateral_claim; let spendable = balance_checkup( db, 0, collateral.txfee, BASECOIN.clone(), &collateral.address, ) .await; let signature = transaction.signature.clone(); restore_if_spendable_void( "collateral_claim", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }, ) .await; } pub async fn restore_vanity(transaction: &VanityAddressTransaction, db: &Db) { let vanity = &transaction.unsigned_vanity_address; let spendable = balance_checkup(db, 0, vanity.txfee, BASECOIN.clone(), &vanity.address).await; let signature = transaction.signature.clone(); restore_if_spendable_void("vanity", "unknown", &[signature], spendable, || async { let _ = transaction.add_to_memory().await; }) .await; } pub async fn restore_rolled_back_transaction(transaction: &Transaction, db: &Db) { match transaction { Transaction::Transfer(tx) => restore_transfer(tx, db).await, Transaction::Burn(tx) => restore_burn(tx, db).await, Transaction::Token(tx) => restore_create_token(tx, db).await, Transaction::IssueToken(tx) => restore_issue_token(tx, db).await, Transaction::Nft(tx) => restore_create_nft(tx, db).await, Transaction::Marketing(tx) => restore_marketing(tx, db).await, Transaction::Swap(tx) => restore_swap(tx, db).await, Transaction::Lender(tx) => restore_loan_creation(tx, db).await, Transaction::Borrower(tx) => restore_borrower(tx, db).await, Transaction::Collateral(tx) => restore_collateral(tx, db).await, Transaction::Vanity(tx) => restore_vanity(tx, db).await, Transaction::Genesis(_) | Transaction::Rewards(_) => {} } }