Contractless/src/orphans/undo_transactions/restore_mempool.rs

326 lines
11 KiB
Rust
Raw Normal View History

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(_) => {}
}
}