@@ -15,9 +15,12 @@ use rand::{
1515
1616use zebra_chain:: {
1717 amount:: Amount ,
18- block:: { Height , MAX_BLOCK_BYTES } ,
18+ block:: { Header , Height , MAX_BLOCK_BYTES } ,
1919 parameters:: Network ,
20- transaction:: { self , zip317:: BLOCK_UNPAID_ACTION_LIMIT , VerifiedUnminedTx } ,
20+ serialization:: { CompactSizeMessage , ZcashSerialize } ,
21+ transaction:: {
22+ self , zip317:: BLOCK_UNPAID_ACTION_LIMIT , VerifiedUnminedTx , MIN_TRANSPARENT_TX_SIZE ,
23+ } ,
2124} ;
2225use zebra_consensus:: MAX_BLOCK_SIGOPS ;
2326use zebra_node_services:: mempool:: TransactionDependencies ;
@@ -96,6 +99,12 @@ pub fn select_mempool_transactions(
9699 let mut remaining_block_sigops = MAX_BLOCK_SIGOPS ;
97100 let mut remaining_block_unpaid_actions: u32 = BLOCK_UNPAID_ACTION_LIMIT ;
98101
102+ // `MAX_BLOCK_BYTES` limits the whole serialized block, so reserve space for the block header
103+ // and the transaction count before budgeting transactions, or the assembled block could
104+ // exceed the consensus size limit (GHSA-95m2-vx53-v2jw).
105+ remaining_block_bytes -= Header :: serialized_size ( net) ;
106+ remaining_block_bytes -= max_transaction_count_size ( ) ;
107+
99108 // Adjust the limits based on the coinbase transaction
100109 remaining_block_bytes -= fake_coinbase_tx. data . as_ref ( ) . len ( ) ;
101110 remaining_block_sigops -= fake_coinbase_tx. sigops ;
@@ -138,6 +147,25 @@ pub fn select_mempool_transactions(
138147 selected_txs
139148}
140149
150+ /// Returns the maximum possible serialized size of a block's transaction count, in bytes.
151+ ///
152+ /// The transaction count is a CompactSize whose width grows with the count. A serialized
153+ /// transaction takes at least [`MIN_TRANSPARENT_TX_SIZE`] bytes, so a block can never contain
154+ /// more than `MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE` transactions, which bounds the width.
155+ fn max_transaction_count_size ( ) -> usize {
156+ let max_transaction_count: usize = ( MAX_BLOCK_BYTES / MIN_TRANSPARENT_TX_SIZE )
157+ . try_into ( )
158+ . expect ( "fits in memory" ) ;
159+
160+ let max_transaction_count = CompactSizeMessage :: try_from ( max_transaction_count)
161+ . expect ( "the maximum transaction count is below the CompactSize message limit" ) ;
162+
163+ max_transaction_count
164+ . zcash_serialize_to_vec ( )
165+ . expect ( "serialization into a vec can't fail" )
166+ . len ( )
167+ }
168+
141169/// Returns a fee-weighted index and the total weight of `transactions`.
142170///
143171/// Returns `None` if there are no transactions, or if the weights are invalid.
0 commit comments