2026-05-24 17:56:57 +00:00
|
|
|
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;
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::decode;
|
2026-06-28 22:12:38 +00:00
|
|
|
use crate::log::warn;
|
|
|
|
|
use crate::records::memory::mempool::{db_client, restore_processed_by_signatures, BASECOIN};
|
2026-05-24 17:56:57 +00:00
|
|
|
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;
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
async fn restore_if_spendable<F, Fut>(
|
|
|
|
|
tx_label: &str,
|
|
|
|
|
txid: &str,
|
|
|
|
|
signatures: &[String],
|
|
|
|
|
spendable: bool,
|
|
|
|
|
insert: F,
|
|
|
|
|
) where
|
2026-05-24 17:56:57 +00:00
|
|
|
F: FnOnce() -> Fut,
|
2026-06-28 22:12:38 +00:00
|
|
|
Fut: std::future::Future<Output = Result<(), Box<dyn std::error::Error + Send + Sync>>>,
|
2026-05-24 17:56:57 +00:00
|
|
|
{
|
|
|
|
|
if !spendable {
|
2026-06-28 22:12:38 +00:00
|
|
|
warn!("[mempool_restore] skipped rolled-back tx: type={tx_label} txid={txid} reason=not_spendable_after_rollback");
|
2026-05-24 17:56:57 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match restore_processed_by_signatures(signatures).await {
|
|
|
|
|
Ok(true) => {}
|
2026-06-28 22:12:38 +00:00
|
|
|
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}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
async fn restore_if_spendable_void<F, Fut>(
|
|
|
|
|
tx_label: &str,
|
|
|
|
|
txid: &str,
|
|
|
|
|
signatures: &[String],
|
|
|
|
|
spendable: bool,
|
|
|
|
|
insert: F,
|
|
|
|
|
) where
|
|
|
|
|
F: FnOnce() -> Fut,
|
|
|
|
|
Fut: std::future::Future<Output = ()>,
|
|
|
|
|
{
|
|
|
|
|
restore_if_spendable(tx_label, txid, signatures, spendable, || async {
|
|
|
|
|
insert().await;
|
|
|
|
|
Ok(())
|
|
|
|
|
})
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
async fn loan_coin_for_contract(db: &Db, contract_hash: &str) -> Option<String> {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
async fn restore_loan_contract_row(
|
|
|
|
|
transaction: &LoanContractTransaction,
|
|
|
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
|
|
|
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(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void("transfer", "unknown", &[signature], spendable, || async {
|
2026-05-24 17:56:57 +00:00
|
|
|
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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void("burn", "unknown", &[signature], spendable, || async {
|
2026-05-24 17:56:57 +00:00
|
|
|
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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void(
|
|
|
|
|
"create_token",
|
|
|
|
|
"unknown",
|
|
|
|
|
&[signature],
|
|
|
|
|
spendable,
|
|
|
|
|
|| async {
|
|
|
|
|
let _ = transaction.add_to_memory().await;
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-24 17:56:57 +00:00
|
|
|
.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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void(
|
|
|
|
|
"issue_token",
|
|
|
|
|
"unknown",
|
|
|
|
|
&[signature],
|
|
|
|
|
spendable,
|
|
|
|
|
|| async {
|
|
|
|
|
let _ = transaction.add_to_memory().await;
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-24 17:56:57 +00:00
|
|
|
.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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void("create_nft", "unknown", &[signature], spendable, || async {
|
2026-05-24 17:56:57 +00:00
|
|
|
let _ = transaction.add_to_memory().await;
|
|
|
|
|
})
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn restore_marketing(transaction: &MarketingTransaction, db: &Db) {
|
|
|
|
|
let marketing = &transaction.unsigned_marketing;
|
2026-05-26 06:24:57 +00:00
|
|
|
let spendable = balance_checkup(
|
|
|
|
|
db,
|
|
|
|
|
0,
|
|
|
|
|
marketing.txfee,
|
|
|
|
|
BASECOIN.clone(),
|
|
|
|
|
&marketing.advertiser,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
2026-05-24 17:56:57 +00:00
|
|
|
let signature = transaction.signature.clone();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void("marketing", "unknown", &[signature], spendable, || async {
|
2026-05-24 17:56:57 +00:00
|
|
|
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);
|
2026-05-26 06:24:57 +00:00
|
|
|
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(),
|
|
|
|
|
];
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void(
|
|
|
|
|
"swap",
|
|
|
|
|
"unknown",
|
2026-05-26 06:24:57 +00:00
|
|
|
&signatures,
|
|
|
|
|
sender1_spendable && sender2_spendable,
|
|
|
|
|
|| async {
|
|
|
|
|
let _ = transaction.add_to_memory().await;
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-24 17:56:57 +00:00
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void(
|
|
|
|
|
"loan_payment",
|
|
|
|
|
"unknown",
|
|
|
|
|
&[signature],
|
|
|
|
|
spendable,
|
|
|
|
|
|| async {
|
|
|
|
|
let _ = transaction.add_to_memory().await;
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-24 17:56:57 +00:00
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn restore_collateral(transaction: &CollateralClaimTransaction, db: &Db) {
|
|
|
|
|
let collateral = &transaction.unsigned_collateral_claim;
|
2026-05-26 06:24:57 +00:00
|
|
|
let spendable = balance_checkup(
|
|
|
|
|
db,
|
|
|
|
|
0,
|
|
|
|
|
collateral.txfee,
|
|
|
|
|
BASECOIN.clone(),
|
|
|
|
|
&collateral.address,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
2026-05-24 17:56:57 +00:00
|
|
|
let signature = transaction.signature.clone();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void(
|
|
|
|
|
"collateral_claim",
|
|
|
|
|
"unknown",
|
|
|
|
|
&[signature],
|
|
|
|
|
spendable,
|
|
|
|
|
|| async {
|
|
|
|
|
let _ = transaction.add_to_memory().await;
|
|
|
|
|
},
|
|
|
|
|
)
|
2026-05-24 17:56:57 +00:00
|
|
|
.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();
|
|
|
|
|
|
2026-06-28 22:12:38 +00:00
|
|
|
restore_if_spendable_void("vanity", "unknown", &[signature], spendable, || async {
|
2026-05-24 17:56:57 +00:00
|
|
|
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(_) => {}
|
|
|
|
|
}
|
|
|
|
|
}
|