From 13e9e8c4a79c17d4457b37b201dc6b2f39b918b7 Mon Sep 17 00:00:00 2001 From: Ash Manning Date: Wed, 15 Jul 2026 21:44:43 +0800 Subject: [PATCH 1/2] Provide suffix txs in initial template --- lib/cusf_block_producer.rs | 6 +- lib/server.rs | 143 ++++++++++++++++++++++++++----------- 2 files changed, 105 insertions(+), 44 deletions(-) diff --git a/lib/cusf_block_producer.rs b/lib/cusf_block_producer.rs index 3ce0457..9b567f9 100644 --- a/lib/cusf_block_producer.rs +++ b/lib/cusf_block_producer.rs @@ -47,7 +47,9 @@ where typewit::const_marker::Bool: CoinbaseTxn pub coinbase_txouts: as CoinbaseTxn>::CoinbaseTxouts, /// Prefix txs, with absolute fee pub prefix_txs: Vec<(Transaction, bitcoin::Amount)>, - /// prefix txs do not need to be included here + /// Suffix txs, with absolute fee + pub suffix_txs: Vec<(Transaction, bitcoin::Amount)>, + /// prefix/suffix txs do not need to be included here pub exclude_mempool_txs: HashSet, } @@ -170,7 +172,7 @@ where } typewit::const_marker::BoolWit::False(_) => (), } - template.prefix_txs.extend(suffix_left.txs.iter().cloned()); + template.suffix_txs.extend(suffix_left.txs.iter().cloned()); let suffix_right = self .1 .block_template_suffix( diff --git a/lib/server.rs b/lib/server.rs index 7ace8e1..580c940 100644 --- a/lib/server.rs +++ b/lib/server.rs @@ -169,6 +169,33 @@ where ) } +#[repr(transparent)] +struct DisplayList<'a, T>(&'a T); + +impl<'a, T> std::fmt::Display for DisplayList<'a, T> +where + &'a T: IntoIterator, + <&'a T as IntoIterator>::Item: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[repr(transparent)] + struct DebugDisplay(T); + + impl std::fmt::Debug for DebugDisplay + where + T: std::fmt::Display, + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + f.debug_list() + .entries(self.0.into_iter().map(DebugDisplay)) + .finish() + } +} + /// Compute the block reward for the specified height fn get_block_reward(height: u32, fees: Amount, network: Network) -> Amount { let subsidy_sats = 50 * Amount::ONE_BTC.to_sat(); @@ -336,6 +363,8 @@ where MempoolRemove(#[from] crate::mempool::MempoolRemoveError), #[error(transparent)] SuffixTxs(BP::SuffixTxsError), + #[error("weight overflow")] + WeightOverflow, } // select block txs, and coinbase txouts if coinbasetxn is set @@ -368,16 +397,16 @@ async fn block_txs( .iter() .map(|(tx, _fee)| tx.compute_txid()) .collect(); + let suffix_txids: hashlink::LinkedHashSet = initial_block_template + .suffix_txs + .iter() + .map(|(tx, _fee)| tx.compute_txid()) + .collect(); { - let prefix_txids: String = format!( - "[{}]", - prefix_txids - .iter() - .map(|txid| txid.to_string()) - .collect::>() - .join(", ") + tracing::debug!( + prefix_txids = %DisplayList(&prefix_txids), + suffix_txids = %DisplayList(&suffix_txids), ); - tracing::debug!(%prefix_txids); } let mut mempool = mempool.clone(); tracing::debug!("Inserting prefix txs into cloned mempool"); @@ -407,7 +436,7 @@ async fn block_txs( }) .collect() }; - // Remove prefix txs + // Remove prefix/excluded/suffix txs { tracing::debug!("Removing prefix txs"); let _removed_txs = mempool @@ -418,24 +447,20 @@ async fn block_txs( .map_err(|err| match err { either::Either::Left(err) => err, })?; - { - let excluded_txids: String = format!( - "[{}]", - initial_block_template - .exclude_mempool_txs - .iter() - .map(|txid| txid.to_string()) - .collect::>() - .join(", ") - ); - tracing::debug!(%excluded_txids, "Removing excluded txs"); - } + tracing::debug!("Removing prefix txs"); + tracing::debug!( + excluded_txids = %DisplayList( + &initial_block_template.exclude_mempool_txs + ), + "Removing excluded/suffix txs" + ); let _removed_txs = mempool .try_filter(true, |tx, _| { let txid = tx.compute_txid(); - Result::<_, Infallible>::Ok( - !initial_block_template.exclude_mempool_txs.contains(&txid), - ) + let excluded = + initial_block_template.exclude_mempool_txs.contains(&txid) + || suffix_txids.contains(&txid); + Result::<_, Infallible>::Ok(!excluded) }) .map_err(|err| match err { either::Either::Left(err) => err, @@ -443,16 +468,20 @@ async fn block_txs( } tracing::debug!("Proposing txs for inclusion in block"); let coinbase_txouts_weight = { - let txouts_weight = match typewit::MakeTypeWitness::MAKE { + let mut txouts_weight = Weight::ZERO; + match typewit::MakeTypeWitness::MAKE { typewit::const_marker::BoolWit::True(wit) => { let wit = wit.map(cusf_block_producer::CoinbaseTxouts); - wit.in_ref() + for tx_out in wit + .in_ref() .to_right(&initial_block_template.coinbase_txouts) - .iter() - .map(|tx_out| tx_out.weight()) - .sum() + { + txouts_weight = txouts_weight + .checked_add(tx_out.weight()) + .ok_or(BuildBlockError::WeightOverflow)?; + } } - typewit::const_marker::BoolWit::False(_) => Weight::ZERO, + typewit::const_marker::BoolWit::False(_) => (), }; const COINBASE_WITNESS_COMMITMENT_TXOUT_WEIGHT: Weight = { let weight_wu = Weight::from_non_witness_data_size(Amount::SIZE as u64).to_wu() @@ -461,20 +490,39 @@ async fn block_txs( Weight::from_wu(weight_wu) }; Weight::from_wu( - txouts_weight.to_wu() - + COINBASE_WITNESS_COMMITMENT_TXOUT_WEIGHT.to_wu(), + txouts_weight + .to_wu() + .checked_add(COINBASE_WITNESS_COMMITMENT_TXOUT_WEIGHT.to_wu()) + .ok_or(BuildBlockError::WeightOverflow)?, ) }; - let prefix_txs_weight = initial_block_template - .prefix_txs - .iter() - .map(|(tx, _)| tx.weight()) - .sum(); - let initial_block_template_weight = - coinbase_txouts_weight + prefix_txs_weight; - let mempool_txs = mempool.propose_txs(Some( - mempool::MAX_USABLE_BLOCK_WEIGHT - initial_block_template_weight, - ))?; + let prefix_txs_weight = { + let mut weight = Weight::ZERO; + for (tx, _) in &initial_block_template.prefix_txs { + weight = weight + .checked_add(tx.weight()) + .ok_or(BuildBlockError::WeightOverflow)?; + } + weight + }; + let suffix_txs_weight = { + let mut weight = Weight::ZERO; + for (tx, _) in &initial_block_template.suffix_txs { + weight = weight + .checked_add(tx.weight()) + .ok_or(BuildBlockError::WeightOverflow)?; + } + weight + }; + let initial_block_template_weight = coinbase_txouts_weight + .checked_add(prefix_txs_weight) + .and_then(|weight| weight.checked_add(suffix_txs_weight)) + .ok_or(BuildBlockError::WeightOverflow)?; + let mempool_txs = mempool.propose_txs(Some(Weight::from_wu( + mempool::MAX_USABLE_BLOCK_WEIGHT + .to_wu() + .saturating_sub(initial_block_template_weight.to_wu()), + )))?; { let proposed_txids: String = { use std::fmt::Write; @@ -531,6 +579,17 @@ async fn block_txs( typewit::const_marker::BoolWit::False(_) => (), } res_txs.extend(mempool_txs); + res_txs.extend(initial_block_template.suffix_txs.iter().map( + |(tx, fee)| BlockTemplateTransaction { + data: bitcoin::consensus::serialize(tx), + txid: tx.compute_txid(), + hash: tx.compute_wtxid(), + depends: Vec::new(), + fee: (*fee).try_into().unwrap(), + sigops: None, + weight: tx.weight().to_wu(), + }, + )); res_txs.extend(template_suffix.txs.iter().map(|(tx, fee)| { BlockTemplateTransaction { data: bitcoin::consensus::serialize(tx), From 90ac723b7c78fb57cdab4a0e269e57ff23a6ebb7 Mon Sep 17 00:00:00 2001 From: Ash Manning Date: Wed, 15 Jul 2026 21:45:31 +0800 Subject: [PATCH 2/2] Bump version for release 0.4.0 --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84d1ae4..75ff8eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,7 +446,7 @@ dependencies = [ [[package]] name = "cusf-enforcer-mempool" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "async-trait", @@ -475,7 +475,7 @@ dependencies = [ [[package]] name = "cusf-enforcer-mempool-demo" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "bitcoin", @@ -491,7 +491,7 @@ dependencies = [ [[package]] name = "cusf-enforcer-mempool-integration-tests" -version = "0.3.0" +version = "0.4.0" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index b544e4b..80b0303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = ["app", "integration_tests", "lib"] [workspace.package] authors = ["Ash Manning "] edition = "2024" -version = "0.3.0" +version = "0.4.0" license-file = "LICENSE.txt" publish = false