Contractless/src/orphans/undo_transactions/undo_borrower.rs

106 lines
4.4 KiB
Rust
Raw Normal View History

2026-05-24 17:56:57 +00:00
use crate::blocks::loan_payment::ContractPaymentTransaction;
use crate::blocks::loans::LoanContractTransaction;
use crate::common::network_paths_and_settings::block_extension_and_paths;
use crate::decode;
use crate::records::balance_sheet::operations::balance_sheet_operation_with_db;
use crate::records::record_chain::add_payments_db::remove_payment;
use crate::records::record_chain::nft_provenance::remove_nft_history_entry;
use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid;
use crate::rpc::responses::RpcResponse;
use crate::sled::Db;
pub async fn undo_borrower_transaction(
transaction: ContractPaymentTransaction,
mining_receiver: &str,
db: &Db,
) -> Result<(), String> {
// restore balances and database state for a contract payment
// that is being removed during orphan rollback
let operand_subtraction = "subtraction";
let operand_addition = "addition";
let (
_network_name,
_padded_base_coin,
type_str,
_torrentpath,
_wallet_path,
_blockpath,
_db_path,
_balance_path,
_log_path,
) = block_extension_and_paths();
// reload the original loan contract so the rollback uses the
// same lender and asset information the payment was based on
let contract_hash = decode(&transaction.unsigned_contract_payment.contract_hash)
.map_err(|e| format!("Error decoding contract hash: {e}"))?;
let contract = request_transaction_by_txid(db, contract_hash.clone()).await;
let loan_txtype = 7;
let loan_tx = match contract {
RpcResponse::Binary(contract_bytes) => {
if contract_bytes.is_empty() {
return Err("Invalid loan contract: empty transaction bytes".to_string());
}
if contract_bytes[0] != loan_txtype {
return Err(
"Invalid loan contract: referenced transaction is not a loan contract"
.to_string(),
);
}
LoanContractTransaction::from_bytes(loan_txtype, &contract_bytes[1..])
.await
.map_err(|e| e.to_string())?
}
};
let lender = loan_tx.unsigned_loan_contract.lender;
let loan_coin = loan_tx.unsigned_loan_contract.loan_coin;
let borrower = &transaction.unsigned_contract_payment.address;
let payback_amount = transaction.unsigned_contract_payment.payback_amount;
let tip = transaction.unsigned_contract_payment.tip;
let txfee = transaction.unsigned_contract_payment.txfee;
// reverse the fee, tip, and repayment movements that were
// applied when the borrower payment was originally saved
let _ =
balance_sheet_operation_with_db(db, mining_receiver, txfee, &type_str, operand_subtraction);
let _ = balance_sheet_operation_with_db(db, borrower, txfee, &type_str, operand_addition);
let _ =
balance_sheet_operation_with_db(db, mining_receiver, tip, &loan_coin, operand_subtraction);
let _ = balance_sheet_operation_with_db(db, borrower, tip, &loan_coin, operand_addition);
let _ = balance_sheet_operation_with_db(
db,
&lender,
payback_amount,
&loan_coin,
operand_subtraction,
);
let _ =
balance_sheet_operation_with_db(db, borrower, payback_amount, &loan_coin, operand_addition);
// Remove the payment transaction lookup from the txid tree.
let txid_tree = db
.open_tree("txid")
.map_err(|e| format!("Failed to open txid tree: {e}"))?;
let tx_hash = transaction.unsigned_contract_payment.hash().await;
txid_tree
.remove(decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?)
.map_err(|e| format!("Failed to remove borrower txid: {e}"))?;
// Loan payments involving NFTs also add a provenance entry for the loan
// asset, so remove it if this loan coin is tracked as an NFT.
let nft_tree = db
.open_tree("nfts")
.map_err(|e| format!("Failed to open nfts tree: {e}"))?;
let tx_hash_bytes = decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?;
if nft_tree.contains_key(loan_coin.as_bytes()).unwrap_or(false) {
let _ = remove_nft_history_entry(db, &loan_coin, &tx_hash_bytes);
}
// The aggregate payment record is reduced by the payment being undone.
let _ = remove_payment(db, contract_hash, payback_amount);
Ok(())
}