11use std:: { fmt:: Formatter , ops:: RangeFrom } ;
22
33use nimiq_block:: {
4- Block , BlockType , MacroBlock , MacroHeader , MicroBlock , MicroHeader , MicroJustification ,
5- TendermintProof ,
4+ Block , BlockError , BlockType , MacroBlock , MacroHeader , MicroBlock , MicroHeader ,
5+ MicroJustification , TendermintProof ,
66} ;
77use nimiq_database_value_derive:: DbSerializable ;
88use nimiq_hash:: Blake2bHash ;
@@ -58,24 +58,30 @@ impl ChainInfo {
5858 }
5959
6060 /// Creates a new ChainInfo for a block given its predecessor.
61+ ///
62+ /// Returns an error if the transaction fee sum overflows.
6163 pub fn from_block (
6264 block : Block ,
6365 prev_info : & ChainInfo ,
6466 prev_missing_range : Option < RangeFrom < KeyNibbles > > ,
65- ) -> Self {
67+ ) -> Result < Self , BlockError > {
6668 assert_eq ! ( prev_info. head. block_number( ) , block. block_number( ) - 1 ) ;
6769
6870 // Reset the transaction fee accumulator if this is the first block of a batch. Otherwise,
6971 // just add the transactions fees of this block to the accumulator.
72+ let block_fees = block. sum_transaction_fees ( ) ?;
7073 let cum_tx_fees = if Policy :: is_macro_block_at ( prev_info. head . block_number ( ) ) {
71- block . sum_transaction_fees ( )
74+ block_fees
7275 } else {
73- prev_info. cum_tx_fees + block. sum_transaction_fees ( )
76+ prev_info
77+ . cum_tx_fees
78+ . checked_add ( block_fees)
79+ . ok_or ( BlockError :: TransactionFeeOverflow ) ?
7480 } ;
7581
7682 let prunable = !block. is_election ( ) ;
7783
78- ChainInfo {
84+ Ok ( ChainInfo {
7985 on_main_chain : false ,
8086 main_chain_successor : None ,
8187 head : block,
@@ -84,7 +90,7 @@ impl ChainInfo {
8490 history_tree_len : 0 ,
8591 prunable,
8692 prev_missing_range,
87- }
93+ } )
8894 }
8995
9096 /// Sets the value for the cumulative historic transaction size and the prunable flag
@@ -188,3 +194,70 @@ impl<'de> Visitor<'de> for ChainHeadVisitor {
188194 Ok ( block)
189195 }
190196}
197+
198+ #[ cfg( test) ]
199+ mod tests {
200+ use nimiq_block:: { BlockError , MicroBlock , MicroBody , MicroHeader } ;
201+ use nimiq_keys:: Address ;
202+ use nimiq_primitives:: { coin:: Coin , networks:: NetworkId , policy:: Policy } ;
203+ use nimiq_transaction:: ExecutedTransaction ;
204+
205+ use super :: * ;
206+
207+ fn tx_with_fee ( fee : Coin ) -> ExecutedTransaction {
208+ ExecutedTransaction :: Ok ( nimiq_transaction:: Transaction :: new_basic (
209+ Address :: default ( ) ,
210+ Address :: from ( [ 1u8 ; 20 ] ) ,
211+ Coin :: ZERO ,
212+ fee,
213+ 1 ,
214+ NetworkId :: UnitAlbatross ,
215+ ) )
216+ }
217+
218+ #[ test]
219+ fn test_chain_info_cumulative_fee_overflow ( ) {
220+ // Use block_number 2 so it's NOT a macro block predecessor, triggering the
221+ // cumulative addition path.
222+ let prev_block = Block :: Micro ( MicroBlock {
223+ header : MicroHeader {
224+ network : NetworkId :: UnitAlbatross ,
225+ version : Policy :: max_supported_version ( ) ,
226+ block_number : 2 ,
227+ timestamp : 0 ,
228+ ..Default :: default ( )
229+ } ,
230+ justification : None ,
231+ body : None ,
232+ } ) ;
233+
234+ let mut prev_info = ChainInfo :: new ( prev_block, true ) ;
235+ prev_info. cum_tx_fees = Coin :: MAX ;
236+
237+ // Create a block with fee = 1. Adding to MAX should overflow.
238+ let transactions = vec ! [ tx_with_fee( Coin :: from_u64_unchecked( 1 ) ) ] ;
239+ let micro_body = MicroBody {
240+ equivocation_proofs : vec ! [ ] ,
241+ transactions,
242+ } ;
243+ let block = Block :: Micro ( MicroBlock {
244+ header : MicroHeader {
245+ network : NetworkId :: UnitAlbatross ,
246+ version : Policy :: max_supported_version ( ) ,
247+ block_number : 3 ,
248+ timestamp : 1 ,
249+ ..Default :: default ( )
250+ } ,
251+ justification : None ,
252+ body : Some ( micro_body) ,
253+ } ) ;
254+
255+ assert ! (
256+ matches!(
257+ ChainInfo :: from_block( block, & prev_info, None ) ,
258+ Err ( BlockError :: TransactionFeeOverflow )
259+ ) ,
260+ "Expected TransactionFeeOverflow for cumulative fee overflow" ,
261+ ) ;
262+ }
263+ }
0 commit comments