2026-05-24 17:56:57 +00:00
|
|
|
use super::*;
|
|
|
|
|
use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects};
|
2026-06-12 16:27:28 +00:00
|
|
|
use crate::records::record_chain::wallet_tx_index::index_wallet_transaction;
|
|
|
|
|
|
|
|
|
|
fn index_selected_wallet_transaction(
|
|
|
|
|
pending_effects: &mut PendingEffects,
|
|
|
|
|
addresses: &[&str],
|
|
|
|
|
block_height: u32,
|
|
|
|
|
entry_index: usize,
|
|
|
|
|
txid: &[u8],
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
let entry_index =
|
|
|
|
|
u32::try_from(entry_index).map_err(|_| anyhow!("Wallet transaction index overflowed"))?;
|
|
|
|
|
index_wallet_transaction(pending_effects, addresses, block_height, entry_index, txid)
|
|
|
|
|
.map_err(|err| anyhow!(err))
|
|
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
|
|
|
|
|
pub async fn select_transactions_for_block(limit: i64) -> Result<SelectedMempoolBatch> {
|
|
|
|
|
// Pull the highest-priority unprocessed rows across all mempool
|
|
|
|
|
// tables, keeping the original bytes for block-file streaming later.
|
2026-06-23 15:17:12 +00:00
|
|
|
let rows =
|
|
|
|
|
pg_query(
|
2026-05-26 06:24:57 +00:00
|
|
|
r#"
|
|
|
|
|
SELECT * FROM (
|
|
|
|
|
SELECT
|
|
|
|
|
'transfer'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
sender, receiver, coin, value, nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
|
|
|
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1,
|
|
|
|
|
NULL::BIGINT AS value2, NULL::TEXT AS sender1, NULL::BIGINT AS tip1,
|
|
|
|
|
NULL::BIGINT AS tip2, NULL::TEXT AS sender2, NULL::TEXT AS loan_coin,
|
|
|
|
|
NULL::BIGINT AS loan_amount, NULL::TEXT AS lender, NULL::TEXT AS collateral,
|
|
|
|
|
NULL::BIGINT AS collateral_amount, NULL::TEXT AS borrower,
|
|
|
|
|
NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash, NULL::TEXT AS address,
|
|
|
|
|
hash, signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM transfer
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'token'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
number, creator, ticker, NULL::TEXT AS nft_name, NULL::SMALLINT AS series,
|
|
|
|
|
NULL::BIGINT AS count, NULL::TEXT AS advertiser, NULL::BIGINT AS fee1,
|
|
|
|
|
NULL::BIGINT AS fee2, NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2,
|
|
|
|
|
NULL::BIGINT AS value1, NULL::BIGINT AS value2, NULL::TEXT AS sender1,
|
|
|
|
|
NULL::BIGINT AS tip1, NULL::BIGINT AS tip2, NULL::TEXT AS sender2,
|
|
|
|
|
NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount, NULL::TEXT AS lender,
|
|
|
|
|
NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount, NULL::TEXT AS borrower,
|
|
|
|
|
NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash, NULL::TEXT AS address,
|
|
|
|
|
hash, signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, hard_limit, original
|
|
|
|
|
FROM token
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'issue_token'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
number, creator, ticker, NULL::TEXT AS nft_name, NULL::SMALLINT AS series,
|
|
|
|
|
NULL::BIGINT AS count, NULL::TEXT AS advertiser, NULL::BIGINT AS fee1,
|
|
|
|
|
NULL::BIGINT AS fee2, NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2,
|
|
|
|
|
NULL::BIGINT AS value1, NULL::BIGINT AS value2, NULL::TEXT AS sender1,
|
|
|
|
|
NULL::BIGINT AS tip1, NULL::BIGINT AS tip2, NULL::TEXT AS sender2,
|
|
|
|
|
NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount, NULL::TEXT AS lender,
|
|
|
|
|
NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount, NULL::TEXT AS borrower,
|
|
|
|
|
NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash, NULL::TEXT AS address,
|
|
|
|
|
hash, signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM issue_token
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'burn'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, coin, value, nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
|
|
|
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2,
|
|
|
|
|
NULL::BIGINT AS value1, NULL::BIGINT AS value2, NULL::TEXT AS sender1,
|
|
|
|
|
NULL::BIGINT AS tip1, NULL::BIGINT AS tip2, NULL::TEXT AS sender2,
|
|
|
|
|
NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount, NULL::TEXT AS lender,
|
|
|
|
|
NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount, NULL::TEXT AS borrower,
|
|
|
|
|
NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash, address,
|
|
|
|
|
hash, signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM burn
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'nft'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, creator, NULL::TEXT AS ticker, nft_name, series, count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
|
|
|
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1,
|
|
|
|
|
NULL::BIGINT AS value2, NULL::TEXT AS sender1, NULL::BIGINT AS tip1,
|
|
|
|
|
NULL::BIGINT AS tip2, NULL::TEXT AS sender2, NULL::TEXT AS loan_coin,
|
|
|
|
|
NULL::BIGINT AS loan_amount, NULL::TEXT AS lender, NULL::TEXT AS collateral,
|
|
|
|
|
NULL::BIGINT AS collateral_amount, NULL::TEXT AS borrower,
|
|
|
|
|
NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash, NULL::TEXT AS address,
|
|
|
|
|
hash, signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM nft
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'marketing'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2, NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1,
|
|
|
|
|
NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1, NULL::BIGINT AS value2,
|
|
|
|
|
NULL::TEXT AS sender1, NULL::BIGINT AS tip1, NULL::BIGINT AS tip2,
|
|
|
|
|
NULL::TEXT AS sender2, NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount,
|
|
|
|
|
NULL::TEXT AS lender, NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount,
|
|
|
|
|
NULL::TEXT AS borrower, NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash,
|
|
|
|
|
NULL::TEXT AS address, hash, signature AS signature1, NULL::TEXT AS signature2,
|
|
|
|
|
NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM marketing
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'vanity_address'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2, NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1,
|
|
|
|
|
NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1, NULL::BIGINT AS value2,
|
|
|
|
|
NULL::TEXT AS sender1, NULL::BIGINT AS tip1, NULL::BIGINT AS tip2,
|
|
|
|
|
NULL::TEXT AS sender2, NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount,
|
|
|
|
|
NULL::TEXT AS lender, NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount,
|
|
|
|
|
NULL::TEXT AS borrower, NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash,
|
|
|
|
|
address, hash, signature AS signature1, NULL::TEXT AS signature2,
|
|
|
|
|
vanity_address AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM vanity_address
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'swap'::TEXT AS kind, id, GREATEST(fee1, fee2) AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, fee1, fee2, ticker1, nft_series1, ticker2, nft_series2, value1, value2,
|
|
|
|
|
sender1, tip1, tip2, sender2, NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount,
|
|
|
|
|
NULL::TEXT AS lender, NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount,
|
|
|
|
|
NULL::TEXT AS borrower, NULL::BIGINT AS payback_amount, NULL::TEXT AS contract_hash,
|
|
|
|
|
NULL::TEXT AS address, hash, signature1, signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM swap
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'loan_contract'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
|
|
|
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1,
|
|
|
|
|
NULL::BIGINT AS value2, NULL::TEXT AS sender1, NULL::BIGINT AS tip1,
|
|
|
|
|
NULL::BIGINT AS tip2, NULL::TEXT AS sender2, loan_coin, loan_amount, lender,
|
|
|
|
|
collateral, collateral_amount, borrower, NULL::BIGINT AS payback_amount,
|
|
|
|
|
NULL::TEXT AS contract_hash, NULL::TEXT AS address, hash, signature1, signature2,
|
|
|
|
|
txid AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM loan_contract
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'loan_payment'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
|
|
|
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1,
|
|
|
|
|
NULL::BIGINT AS value2, NULL::TEXT AS sender1, tip AS tip1, NULL::BIGINT AS tip2,
|
|
|
|
|
NULL::TEXT AS sender2, NULL::TEXT AS loan_coin, NULL::BIGINT AS loan_amount,
|
|
|
|
|
NULL::TEXT AS lender, NULL::TEXT AS collateral, NULL::BIGINT AS collateral_amount,
|
|
|
|
|
NULL::TEXT AS borrower, payback_amount, contract_hash, address, hash,
|
|
|
|
|
signature AS signature1, NULL::TEXT AS signature2, txid AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM loan_payment
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
|
|
|
|
|
UNION ALL
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
'collateral_claim'::TEXT AS kind, id, fee AS priority_fee, time,
|
|
|
|
|
NULL::TEXT AS sender, NULL::TEXT AS receiver, NULL::TEXT AS coin, NULL::BIGINT AS value, NULL::INTEGER AS nft_series,
|
|
|
|
|
NULL::BIGINT AS number, NULL::TEXT AS creator, NULL::TEXT AS ticker,
|
|
|
|
|
NULL::TEXT AS nft_name, NULL::SMALLINT AS series, NULL::BIGINT AS count,
|
|
|
|
|
NULL::TEXT AS advertiser, NULL::BIGINT AS fee1, NULL::BIGINT AS fee2,
|
|
|
|
|
NULL::TEXT AS ticker1, NULL::INTEGER AS nft_series1, NULL::TEXT AS ticker2, NULL::INTEGER AS nft_series2, NULL::BIGINT AS value1,
|
|
|
|
|
NULL::BIGINT AS value2, NULL::TEXT AS sender1, NULL::BIGINT AS tip1,
|
|
|
|
|
NULL::BIGINT AS tip2, NULL::TEXT AS sender2, NULL::TEXT AS loan_coin,
|
|
|
|
|
NULL::BIGINT AS loan_amount, NULL::TEXT AS lender, NULL::TEXT AS collateral,
|
|
|
|
|
NULL::BIGINT AS collateral_amount, NULL::TEXT AS borrower,
|
|
|
|
|
NULL::BIGINT AS payback_amount, contract_hash, address, hash,
|
|
|
|
|
signature AS signature1, NULL::TEXT AS signature2, NULL::TEXT AS stored_txid, NULL::SMALLINT AS hard_limit, original
|
|
|
|
|
FROM collateral_claim
|
|
|
|
|
WHERE processed = false
|
|
|
|
|
) AS combined
|
|
|
|
|
ORDER BY priority_fee DESC, time ASC, kind ASC, id ASC
|
|
|
|
|
LIMIT $1
|
|
|
|
|
"#,
|
|
|
|
|
&[&limit],
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
let mut transactions = Vec::with_capacity(rows.len());
|
|
|
|
|
let mut originals = Vec::with_capacity(rows.len());
|
|
|
|
|
for row in rows {
|
|
|
|
|
// Preserve the original serialized transaction bytes so the miner can
|
|
|
|
|
// write exactly what entered the mempool.
|
|
|
|
|
originals.push(row.get("original"));
|
|
|
|
|
|
|
|
|
|
// The UNION query flattens every mempool table into one row shape;
|
|
|
|
|
// this tag restores the correct selected-transaction variant.
|
|
|
|
|
let kind: String = row.get("kind");
|
|
|
|
|
let transaction = match kind.as_str() {
|
2026-05-26 06:24:57 +00:00
|
|
|
"transfer" => SelectedMempoolTransaction::Transfer {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
sender: required_string(&row, "sender")?,
|
|
|
|
|
value: row.get("value"),
|
|
|
|
|
coin: required_string(&row, "coin")?,
|
|
|
|
|
nft_series: row.get("nft_series"),
|
|
|
|
|
receiver: required_string(&row, "receiver")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"token" => SelectedMempoolTransaction::Token {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
creator: required_string(&row, "creator")?,
|
|
|
|
|
number: row.get("number"),
|
|
|
|
|
ticker: required_string(&row, "ticker")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"issue_token" => SelectedMempoolTransaction::IssueToken {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
creator: required_string(&row, "creator")?,
|
|
|
|
|
number: row.get("number"),
|
|
|
|
|
ticker: required_string(&row, "ticker")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"burn" => SelectedMempoolTransaction::Burn {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
address: required_string(&row, "address")?,
|
|
|
|
|
coin: required_string(&row, "coin")?,
|
|
|
|
|
nft_series: row.get("nft_series"),
|
|
|
|
|
value: row.get("value"),
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"nft" => SelectedMempoolTransaction::Nft {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
creator: required_string(&row, "creator")?,
|
|
|
|
|
nft_name: required_string(&row, "nft_name")?,
|
|
|
|
|
series: row.get("series"),
|
|
|
|
|
count: row.get("count"),
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"marketing" => SelectedMempoolTransaction::Marketing {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
advertiser: required_string(&row, "advertiser")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"vanity_address" => SelectedMempoolTransaction::Vanity {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
address: required_string(&row, "address")?,
|
|
|
|
|
vanity_address: required_string(&row, "stored_txid")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"swap" => SelectedMempoolTransaction::Swap {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee1: row.get("fee1"),
|
|
|
|
|
fee2: row.get("fee2"),
|
|
|
|
|
ticker1: required_string(&row, "ticker1")?,
|
|
|
|
|
nft_series1: row.get("nft_series1"),
|
|
|
|
|
ticker2: required_string(&row, "ticker2")?,
|
|
|
|
|
nft_series2: row.get("nft_series2"),
|
|
|
|
|
value1: row.get("value1"),
|
|
|
|
|
value2: row.get("value2"),
|
|
|
|
|
sender1: required_string(&row, "sender1")?,
|
|
|
|
|
tip1: row.get("tip1"),
|
|
|
|
|
tip2: row.get("tip2"),
|
|
|
|
|
sender2: required_string(&row, "sender2")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"loan_contract" => SelectedMempoolTransaction::Lender {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
loan_coin: required_string(&row, "loan_coin")?,
|
|
|
|
|
loan_amount: row.get("loan_amount"),
|
|
|
|
|
lender: required_string(&row, "lender")?,
|
|
|
|
|
collateral: required_string(&row, "collateral")?,
|
|
|
|
|
collateral_amount: row.get("collateral_amount"),
|
|
|
|
|
borrower: required_string(&row, "borrower")?,
|
|
|
|
|
txid: required_string(&row, "stored_txid")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"loan_payment" => SelectedMempoolTransaction::Borrower {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
payback_amount: row.get("payback_amount"),
|
|
|
|
|
contract_hash: required_string(&row, "contract_hash")?,
|
|
|
|
|
address: required_string(&row, "address")?,
|
|
|
|
|
tip: row.get("tip1"),
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
|
|
|
|
"collateral_claim" => SelectedMempoolTransaction::Collateral {
|
|
|
|
|
id: row.get("id"),
|
|
|
|
|
fee: row.get("priority_fee"),
|
|
|
|
|
address: required_string(&row, "address")?,
|
|
|
|
|
contract_hash: required_string(&row, "contract_hash")?,
|
|
|
|
|
hash: row.get("hash"),
|
|
|
|
|
},
|
2026-05-24 17:56:57 +00:00
|
|
|
_ => return Err(anyhow!("Unsupported mempool kind {kind}")),
|
|
|
|
|
};
|
2026-05-26 06:24:57 +00:00
|
|
|
transactions.push(transaction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(SelectedMempoolBatch {
|
|
|
|
|
transactions,
|
|
|
|
|
originals,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
pub async fn apply_selected_transaction_math(
|
|
|
|
|
batch: &SelectedMempoolBatch,
|
|
|
|
|
db: &Db,
|
|
|
|
|
miner: String,
|
|
|
|
|
block_number: u32,
|
|
|
|
|
start_index: usize,
|
|
|
|
|
pending_effects: &mut PendingEffects,
|
2026-06-24 17:32:25 +00:00
|
|
|
) -> Result<()> {
|
2026-05-24 17:56:57 +00:00
|
|
|
// Net balance deltas are collected first so multiple changes to the same
|
|
|
|
|
// address/asset pair become one balance-sheet write.
|
|
|
|
|
let mut balance_changes: HashMap<BalanceKey, i64> = HashMap::new();
|
2026-05-26 06:24:57 +00:00
|
|
|
let miner_address = address_key_bytes(db, &miner);
|
|
|
|
|
let base_coin = BASECOIN.as_bytes().to_vec();
|
|
|
|
|
let mut tx_index = start_index;
|
2026-05-24 17:56:57 +00:00
|
|
|
for tx in &batch.transactions {
|
|
|
|
|
// Transaction index is stored with the block number for later txid
|
|
|
|
|
// lookups inside the saved block payload.
|
|
|
|
|
tx_index = tx_index.wrapping_add(1);
|
|
|
|
|
match tx {
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Transfer {
|
|
|
|
|
fee,
|
|
|
|
|
sender,
|
|
|
|
|
value,
|
|
|
|
|
coin,
|
|
|
|
|
nft_series,
|
|
|
|
|
receiver,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Transfers move the asset, charge the base fee, and credit
|
|
|
|
|
// that fee to the miner including this transaction.
|
|
|
|
|
let transfer_asset = nft_asset_name(coin, *nft_series as u32);
|
2026-05-26 06:24:57 +00:00
|
|
|
add_balance_change(db, &mut balance_changes, sender, &transfer_asset, -*value);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, receiver, &transfer_asset, *value);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
let nft_tree = db.open_tree("nfts")?;
|
|
|
|
|
if nft_tree.contains_key(transfer_asset.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
transfer_asset.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[sender, receiver],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Token {
|
|
|
|
|
fee,
|
|
|
|
|
creator,
|
|
|
|
|
number,
|
|
|
|
|
ticker,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Token creation mints the initial supply and records metadata
|
|
|
|
|
// used by later issue and burn validation.
|
|
|
|
|
add_balance_change(db, &mut balance_changes, creator, ticker, *number);
|
2026-05-26 06:24:57 +00:00
|
|
|
add_balance_change(db, &mut balance_changes, creator, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
let number_bytes = (*number as u64).to_le_bytes();
|
2026-05-26 06:24:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"tokens",
|
|
|
|
|
ticker.as_bytes().to_vec(),
|
|
|
|
|
number_bytes.to_vec(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Local mined blocks need to persist the token hard-limit
|
|
|
|
|
// metadata just like the downloaded-block save path does.
|
2026-06-12 16:27:28 +00:00
|
|
|
let client_handle = db_client().await?;
|
|
|
|
|
let client = client_handle.as_ref();
|
2026-05-26 06:24:57 +00:00
|
|
|
let token_row = client
|
|
|
|
|
.query_opt(
|
|
|
|
|
"SELECT hard_limit FROM token WHERE hash = $1 LIMIT 1",
|
|
|
|
|
&[hash],
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
let hard_limit = token_row
|
|
|
|
|
.and_then(|row| row.try_get::<_, Option<i16>>("hard_limit").ok().flatten())
|
|
|
|
|
.unwrap_or(1) as u8;
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"token_limits",
|
|
|
|
|
ticker.as_bytes().to_vec(),
|
|
|
|
|
vec![hard_limit],
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"token_origins",
|
|
|
|
|
ticker.as_bytes().to_vec(),
|
|
|
|
|
hash.as_bytes().to_vec(),
|
|
|
|
|
);
|
2026-05-26 06:24:57 +00:00
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"token_history",
|
|
|
|
|
ticker.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[creator],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::IssueToken {
|
|
|
|
|
fee,
|
|
|
|
|
creator,
|
|
|
|
|
number,
|
|
|
|
|
ticker,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
add_balance_change(db, &mut balance_changes, creator, ticker, *number);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, creator, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Additional issuance increases the live fungible supply without
|
|
|
|
|
// changing the original token metadata entries.
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.add_token_supply(ticker, *number as u64);
|
2026-05-26 06:24:57 +00:00
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"token_history",
|
|
|
|
|
ticker.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[creator],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Burn {
|
|
|
|
|
fee,
|
|
|
|
|
address,
|
|
|
|
|
coin,
|
|
|
|
|
nft_series,
|
|
|
|
|
value,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
let burned_asset = nft_asset_name(coin, *nft_series as u32);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, address, &burned_asset, -*value);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Live NFT state removes burned assets, while fungible token
|
|
|
|
|
// state reduces the stored supply by the burned amount.
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.burn_asset_state(&burned_asset, coin, decode(hash)?, *value as u64);
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[address],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Nft {
|
|
|
|
|
fee,
|
|
|
|
|
creator,
|
|
|
|
|
nft_name,
|
|
|
|
|
series,
|
|
|
|
|
count,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Series-one NFT creation expands into numbered assets; later
|
|
|
|
|
// series use the given NFT name as the asset key.
|
|
|
|
|
if *series == 1 {
|
2026-05-26 06:24:57 +00:00
|
|
|
for item_number in 1..=*count {
|
|
|
|
|
let nft_save_name = nft_asset_name(nft_name, item_number as u32);
|
|
|
|
|
add_balance_change(
|
|
|
|
|
db,
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
creator,
|
|
|
|
|
&nft_save_name,
|
|
|
|
|
NFT_UNIT,
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"nfts",
|
|
|
|
|
nft_save_name.as_bytes().to_vec(),
|
|
|
|
|
b"1".to_vec(),
|
|
|
|
|
);
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"nft_origins",
|
|
|
|
|
nft_save_name.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
nft_save_name.into_bytes(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
add_balance_change(db, &mut balance_changes, creator, nft_name, NFT_UNIT);
|
|
|
|
|
|
|
|
|
|
pending_effects.set_tree("nfts", nft_name.as_bytes().to_vec(), b"1".to_vec());
|
2026-05-26 06:24:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"nft_origins",
|
|
|
|
|
nft_name.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
nft_name.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
add_balance_change(db, &mut balance_changes, creator, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[creator],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Marketing {
|
|
|
|
|
fee,
|
|
|
|
|
advertiser,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
add_balance_change(db, &mut balance_changes, advertiser, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[advertiser],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Vanity {
|
|
|
|
|
fee,
|
|
|
|
|
address,
|
|
|
|
|
vanity_address,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Vanity transactions update the alias registry during the
|
|
|
|
|
// final block-effect commit so rollback can restore it.
|
|
|
|
|
pending_effects.update_vanity(address, vanity_address, hash);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
2026-05-26 06:24:57 +00:00
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[address],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Swap {
|
|
|
|
|
fee1,
|
|
|
|
|
fee2,
|
|
|
|
|
ticker1,
|
|
|
|
|
nft_series1,
|
|
|
|
|
ticker2,
|
|
|
|
|
nft_series2,
|
|
|
|
|
value1,
|
|
|
|
|
value2,
|
|
|
|
|
sender1,
|
|
|
|
|
tip1,
|
|
|
|
|
tip2,
|
|
|
|
|
sender2,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Swaps debit both sides first, then credit received assets,
|
|
|
|
|
// miner fees, and optional asset tips.
|
|
|
|
|
let asset1 = nft_asset_name(ticker1, *nft_series1 as u32);
|
2026-05-26 06:24:57 +00:00
|
|
|
let asset2 = nft_asset_name(ticker2, *nft_series2 as u32);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender1, &asset1, -*value1);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender1, &asset1, -*tip1);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender2, &asset2, -*value2);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender2, &asset2, -*tip2);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender2, &asset1, *value1);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender1, &asset2, *value2);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender1, &BASECOIN, -*fee1);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, sender2, &BASECOIN, -*fee2);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee1,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee2,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
asset1.as_bytes().to_vec(),
|
|
|
|
|
*tip1,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
asset2.as_bytes().to_vec(),
|
|
|
|
|
*tip2,
|
|
|
|
|
);
|
2026-05-24 17:56:57 +00:00
|
|
|
let nft_tree = db.open_tree("nfts")?;
|
|
|
|
|
if nft_tree.contains_key(asset1.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
asset1.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if nft_tree.contains_key(asset2.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
asset2.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[sender1, sender2],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Lender {
|
|
|
|
|
fee,
|
|
|
|
|
loan_coin,
|
|
|
|
|
loan_amount,
|
|
|
|
|
lender,
|
|
|
|
|
collateral,
|
|
|
|
|
collateral_amount,
|
|
|
|
|
borrower,
|
|
|
|
|
txid,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Loan creation moves the loan asset to the borrower and locks
|
|
|
|
|
// collateral under a contract-specific holding key.
|
|
|
|
|
add_balance_change(db, &mut balance_changes, lender, &BASECOIN, -*fee);
|
2026-05-26 06:24:57 +00:00
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, lender, loan_coin, -*loan_amount);
|
|
|
|
|
add_balance_change(db, &mut balance_changes, borrower, loan_coin, *loan_amount);
|
|
|
|
|
add_balance_change(
|
|
|
|
|
db,
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
borrower,
|
|
|
|
|
collateral,
|
|
|
|
|
-*collateral_amount,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let collateral_holding = format!("collateral_{txid}");
|
|
|
|
|
add_balance_change(
|
|
|
|
|
db,
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
&collateral_holding,
|
|
|
|
|
collateral,
|
|
|
|
|
*collateral_amount,
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree("loan", decode(txid)?, b"true".to_vec());
|
|
|
|
|
let nft_tree = db.open_tree("nfts")?;
|
|
|
|
|
if nft_tree.contains_key(collateral.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
collateral.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if nft_tree.contains_key(loan_coin.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
loan_coin.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[lender, borrower],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Borrower {
|
|
|
|
|
fee,
|
|
|
|
|
payback_amount,
|
|
|
|
|
contract_hash,
|
|
|
|
|
address,
|
|
|
|
|
tip,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Loan payments resolve the contract, then move repayment from
|
|
|
|
|
// borrower to lender while paying any tip to the miner.
|
|
|
|
|
let (loan_coin, lender) = resolve_loan_details(db, contract_hash).await?;
|
2026-05-26 06:24:57 +00:00
|
|
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
address_key_bytes(db, address),
|
|
|
|
|
loan_coin.clone(),
|
|
|
|
|
-*payback_amount,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
lender.clone(),
|
|
|
|
|
loan_coin.clone(),
|
|
|
|
|
*payback_amount,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
address_key_bytes(db, address),
|
|
|
|
|
loan_coin.clone(),
|
|
|
|
|
-*tip,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
loan_coin.clone(),
|
|
|
|
|
*tip,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
pending_effects
|
|
|
|
|
.append_contract_payment(decode(contract_hash)?, *payback_amount as u64);
|
2026-05-24 17:56:57 +00:00
|
|
|
let loan_coin_name = binary_to_string(loan_coin.clone());
|
|
|
|
|
let nft_tree = db.open_tree("nfts")?;
|
|
|
|
|
if nft_tree.contains_key(loan_coin_name.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
loan_coin_name.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
let lender_address = binary_to_string(lender.clone());
|
|
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[address, &lender_address],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
SelectedMempoolTransaction::Collateral {
|
|
|
|
|
fee,
|
|
|
|
|
address,
|
|
|
|
|
contract_hash,
|
|
|
|
|
hash,
|
|
|
|
|
..
|
2026-05-24 17:56:57 +00:00
|
|
|
} => {
|
|
|
|
|
// Collateral claims release the contract holding to the claimant
|
|
|
|
|
// and mark the loan as closed.
|
|
|
|
|
let (collateral, collateral_amount) =
|
2026-05-26 06:24:57 +00:00
|
|
|
resolve_collateral_details(db, contract_hash).await?;
|
|
|
|
|
let collateral_holding = format!("collateral_{contract_hash}");
|
|
|
|
|
add_balance_change(db, &mut balance_changes, address, &BASECOIN, -*fee);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
miner_address.clone(),
|
|
|
|
|
base_coin.clone(),
|
|
|
|
|
*fee,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
collateral_holding.as_bytes().to_vec(),
|
|
|
|
|
collateral.clone(),
|
|
|
|
|
-collateral_amount,
|
|
|
|
|
);
|
|
|
|
|
add_balance_change_bytes(
|
|
|
|
|
&mut balance_changes,
|
|
|
|
|
address_key_bytes(db, address),
|
|
|
|
|
collateral.clone(),
|
|
|
|
|
collateral_amount,
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
pending_effects.set_tree("loan", decode(contract_hash)?, b"false".to_vec());
|
|
|
|
|
let collateral_name = binary_to_string(collateral.clone());
|
|
|
|
|
let nft_tree = db.open_tree("nfts")?;
|
|
|
|
|
if nft_tree.contains_key(collateral_name.as_bytes())? {
|
|
|
|
|
pending_effects.append_tree(
|
|
|
|
|
"nft_history",
|
|
|
|
|
collateral_name.as_bytes().to_vec(),
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
pending_effects.set_tree(
|
|
|
|
|
"txid",
|
|
|
|
|
decode(hash)?,
|
|
|
|
|
format!("{block_number}:{tx_index}").into_bytes(),
|
|
|
|
|
);
|
2026-06-12 16:27:28 +00:00
|
|
|
index_selected_wallet_transaction(
|
|
|
|
|
pending_effects,
|
2026-06-19 18:36:01 +00:00
|
|
|
&[address],
|
2026-06-12 16:27:28 +00:00
|
|
|
block_number,
|
|
|
|
|
tx_index,
|
|
|
|
|
&decode(hash)?,
|
|
|
|
|
)?;
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
for (key, change) in balance_changes {
|
|
|
|
|
// The balance sheet stores string keys, so convert the internal byte
|
|
|
|
|
// keys back into wallet/asset strings before applying the delta.
|
|
|
|
|
let address = if key.address.len() == Wallet::SHORT_ADDRESS_BYTES_LENGTH {
|
2026-05-26 06:24:57 +00:00
|
|
|
Wallet::bytes_to_short_address(&key.address).unwrap_or_default()
|
|
|
|
|
} else {
|
|
|
|
|
binary_to_string(key.address)
|
|
|
|
|
};
|
|
|
|
|
let coin = binary_to_string(key.coin);
|
2026-05-24 17:56:57 +00:00
|
|
|
let operand = if change >= 0 {
|
|
|
|
|
BalanceOperand::Addition
|
|
|
|
|
} else {
|
|
|
|
|
BalanceOperand::Subtraction
|
|
|
|
|
};
|
|
|
|
|
pending_effects.add_balance(&address, change.unsigned_abs(), &coin, operand);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-24 17:32:25 +00:00
|
|
|
Ok(())
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
|
|
|
|
|
pub async fn clear_selected_transaction_sql(
|
|
|
|
|
db: &Db,
|
|
|
|
|
miner: String,
|
|
|
|
|
limit: i64,
|
|
|
|
|
block_number: u32,
|
|
|
|
|
start_index: usize,
|
2026-05-24 17:56:57 +00:00
|
|
|
) -> Result<SelectedMempoolBatch> {
|
|
|
|
|
// Selection and ledger math stay paired so the caller can stream this exact
|
|
|
|
|
// selected batch into the block file afterward.
|
|
|
|
|
let batch = select_transactions_for_block(limit).await?;
|
|
|
|
|
let mut pending_effects = PendingEffects::default();
|
2026-06-24 17:32:25 +00:00
|
|
|
apply_selected_transaction_math(
|
2026-05-24 17:56:57 +00:00
|
|
|
&batch,
|
|
|
|
|
db,
|
|
|
|
|
miner,
|
|
|
|
|
block_number,
|
|
|
|
|
start_index,
|
|
|
|
|
&mut pending_effects,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
pending_effects.apply(db).map_err(|err| anyhow!(err))?;
|
|
|
|
|
Ok(batch)
|
|
|
|
|
}
|
2026-05-26 06:24:57 +00:00
|
|
|
|
|
|
|
|
pub async fn stream_selected_transaction_originals(
|
|
|
|
|
file: &mut File,
|
|
|
|
|
batch: &SelectedMempoolBatch,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
if batch.originals.is_empty() {
|
|
|
|
|
file.flush()?;
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
// Block payloads store the original mempool bytes back-to-back in selected
|
|
|
|
|
// order rather than rebuilding each transaction from parsed fields.
|
|
|
|
|
let total_len: usize = batch.originals.iter().map(Vec::len).sum();
|
2026-05-26 06:24:57 +00:00
|
|
|
let mut payload = Vec::with_capacity(total_len);
|
|
|
|
|
for original in &batch.originals {
|
|
|
|
|
payload.extend_from_slice(original);
|
|
|
|
|
}
|
|
|
|
|
file.write_all(&payload)?;
|
|
|
|
|
file.flush()?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 17:56:57 +00:00
|
|
|
pub async fn delete_selected_transactions(batch: &SelectedMempoolBatch) -> Result<()> {
|
|
|
|
|
// Each transaction kind still lives in a separate SQL table, so deletion
|
|
|
|
|
// groups the selected IDs by table after the block has been written.
|
|
|
|
|
let transfer_ids = ids_for_table(batch, "transfer");
|
2026-05-26 06:24:57 +00:00
|
|
|
let token_ids = ids_for_table(batch, "token");
|
|
|
|
|
let issue_token_ids = ids_for_table(batch, "issue_token");
|
|
|
|
|
let burn_ids = ids_for_table(batch, "burn");
|
|
|
|
|
let nft_ids = ids_for_table(batch, "nft");
|
|
|
|
|
let marketing_ids = ids_for_table(batch, "marketing");
|
|
|
|
|
let vanity_ids = ids_for_table(batch, "vanity_address");
|
|
|
|
|
let swap_ids = ids_for_table(batch, "swap");
|
|
|
|
|
let lender_ids = ids_for_table(batch, "loan_contract");
|
|
|
|
|
let borrower_ids = ids_for_table(batch, "loan_payment");
|
|
|
|
|
let collateral_ids = ids_for_table(batch, "collateral_claim");
|
|
|
|
|
|
2026-06-23 15:17:12 +00:00
|
|
|
delete_rows("transfer", &transfer_ids).await?;
|
|
|
|
|
delete_rows("token", &token_ids).await?;
|
|
|
|
|
delete_rows("issue_token", &issue_token_ids).await?;
|
|
|
|
|
delete_rows("burn", &burn_ids).await?;
|
|
|
|
|
delete_rows("nft", &nft_ids).await?;
|
|
|
|
|
delete_rows("marketing", &marketing_ids).await?;
|
|
|
|
|
delete_rows("vanity_address", &vanity_ids).await?;
|
|
|
|
|
delete_rows("swap", &swap_ids).await?;
|
|
|
|
|
delete_rows("loan_contract", &lender_ids).await?;
|
|
|
|
|
delete_rows("loan_payment", &borrower_ids).await?;
|
|
|
|
|
delete_rows("collateral_claim", &collateral_ids).await?;
|
2026-05-26 06:24:57 +00:00
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|