@@ -82,27 +82,19 @@ pub fn maximum_allowed_solutions_per_epoch<N: Network>(prover_stake: u64, curren
8282}
8383
8484impl < N : Network , C : ConsensusStorage < N > > Ledger < N , C > {
85- /// Returns the number of remaining solutions a prover can submit in the current epoch.
86- ///
87- /// # Locking
88- /// This function may deadlock if called while holding a write lock to the current block.
89- pub fn num_remaining_solutions ( & self , prover_address : & Address < N > , additional_solutions_in_block : u64 ) -> u64 {
90- self . num_remaining_solutions_inner ( & self . current_block . read ( ) , prover_address, additional_solutions_in_block)
91- }
92-
93- /// Internal version of [`Self::num_remaining_solutions`] to be used when already holding a lock to the current block.
94- pub ( super ) fn num_remaining_solutions_inner (
85+ /// Returns the number of remaining solutions a prover can submit in the current epoch,
86+ /// using the provided ledger timestamp instead of re-reading the current block.
87+ pub ( crate ) fn num_remaining_solutions_at_timestamp (
9588 & self ,
96- latest_block : & Block < N > ,
9789 prover_address : & Address < N > ,
9890 additional_solutions_in_block : u64 ,
91+ current_time : i64 ,
9992 ) -> u64 {
10093 // Fetch the prover's stake.
10194 let prover_stake = self . get_bonded_amount ( prover_address) . unwrap_or ( 0 ) ;
10295
10396 // Determine the maximum number of solutions allowed based on this prover's stake.
104- let maximum_allowed_solutions =
105- maximum_allowed_solutions_per_epoch :: < N > ( prover_stake, latest_block. timestamp ( ) ) ;
97+ let maximum_allowed_solutions = maximum_allowed_solutions_per_epoch :: < N > ( prover_stake, current_time) ;
10698
10799 // Fetch the number of solutions the prover has earned rewards for in the current epoch.
108100 let prover_num_solutions_in_epoch = * self . epoch_provers_cache . read ( ) . get ( prover_address) . unwrap_or ( & 0 ) ;
@@ -114,33 +106,44 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
114106 maximum_allowed_solutions. saturating_sub ( num_solutions)
115107 }
116108
117- /// Returns `true` if the given prover address has reached their solution limit for the current epoch.
118- ///
119- /// # Locking
120- /// This function may deadlock if called while holding a write lock to the current block.
121- pub fn is_solution_limit_reached ( & self , prover_address : & Address < N > , additional_solutions_in_block : u64 ) -> bool {
122- self . is_solution_limit_reached_inner ( & self . current_block . read ( ) , prover_address, additional_solutions_in_block)
109+ /// Returns the number of remaining solutions a prover can submit in the current epoch.
110+ pub fn num_remaining_solutions ( & self , prover_address : & Address < N > , additional_solutions_in_block : u64 ) -> u64 {
111+ self . num_remaining_solutions_at_timestamp (
112+ prover_address,
113+ additional_solutions_in_block,
114+ self . latest_timestamp ( ) ,
115+ )
123116 }
124117
125- /// Internal version of [`Self::is_solution_limit_reached`] to be used when already holding a lock to the current block.
126- pub ( super ) fn is_solution_limit_reached_inner (
118+ /// Returns `true` if the given prover address has reached their solution limit for the current epoch,
119+ /// using the provided ledger timestamp instead of re-reading the current block.
120+ pub ( crate ) fn is_solution_limit_reached_at_timestamp (
127121 & self ,
128- latest_block : & Block < N > ,
129122 prover_address : & Address < N > ,
130123 additional_solutions_in_block : u64 ,
124+ current_time : i64 ,
131125 ) -> bool {
132- // Calculate the number of remaining solutions for the prover.
133- let num_remaining_solutions =
134- self . num_remaining_solutions_inner ( latest_block, prover_address, additional_solutions_in_block) ;
126+ self . num_remaining_solutions_at_timestamp ( prover_address, additional_solutions_in_block, current_time) == 0
127+ }
135128
136- // If the number of remaining solutions is zero, the limit is reached.
137- num_remaining_solutions == 0
129+ /// Returns `true` if the given prover address has reached their solution limit for the current epoch.
130+ ///
131+ /// # Locking
132+ /// This function may deadlock if called while holding a write lock to the current block.
133+ pub fn is_solution_limit_reached ( & self , prover_address : & Address < N > , additional_solutions_in_block : u64 ) -> bool {
134+ self . is_solution_limit_reached_at_timestamp (
135+ prover_address,
136+ additional_solutions_in_block,
137+ self . latest_timestamp ( ) ,
138+ )
138139 }
139140}
140141
141142#[ cfg( test) ]
142143mod tests {
143144 use super :: * ;
145+ use crate :: test_helpers:: { CurrentLedger , LedgerType } ;
146+ use std:: { thread, time:: Duration } ;
144147
145148 type CurrentNetwork = console:: network:: MainnetV0 ;
146149
@@ -226,4 +229,37 @@ mod tests {
226229 ) ;
227230 }
228231 }
232+
233+ #[ test]
234+ fn test_solution_limit_helper_avoids_current_block_reentry_with_pending_writer ( ) {
235+ let rng = & mut TestRng :: default ( ) ;
236+
237+ let private_key = PrivateKey :: < CurrentNetwork > :: new ( rng) . unwrap ( ) ;
238+ let validator_address = Address :: try_from ( & private_key) . unwrap ( ) ;
239+ let store = ConsensusStore :: < CurrentNetwork , LedgerType > :: open ( StorageMode :: new_test ( None ) ) . unwrap ( ) ;
240+ let genesis = VM :: from ( store) . unwrap ( ) . genesis_beacon ( & private_key, rng) . unwrap ( ) ;
241+ let ledger = CurrentLedger :: load ( genesis, StorageMode :: new_test ( None ) ) . unwrap ( ) ;
242+
243+ let next_block =
244+ ledger. prepare_advance_to_next_beacon_block ( & private_key, vec ! [ ] , vec ! [ ] , vec ! [ ] , rng) . unwrap ( ) ;
245+ let expected = ledger. num_remaining_solutions ( & validator_address, 0 ) ;
246+
247+ // Mimic check_next_block() holding a read guard while a writer queues behind it.
248+ let latest_block = ledger. current_block . read ( ) ;
249+ let latest_timestamp = latest_block. timestamp ( ) ;
250+
251+ let ledger_clone = ledger. clone ( ) ;
252+ let next_block_clone = next_block. clone ( ) ;
253+ let writer = thread:: spawn ( move || ledger_clone. advance_to_next_block ( & next_block_clone) ) ;
254+
255+ thread:: sleep ( Duration :: from_millis ( 50 ) ) ;
256+ assert ! ( !writer. is_finished( ) , "advance_to_next_block should be waiting on current_block.write()" ) ;
257+
258+ let actual = ledger. num_remaining_solutions_at_timestamp ( & validator_address, 0 , latest_timestamp) ;
259+ assert_eq ! ( actual, expected) ;
260+ assert_eq ! ( ledger. is_solution_limit_reached_at_timestamp( & validator_address, 0 , latest_timestamp) , actual == 0 ) ;
261+
262+ drop ( latest_block) ;
263+ writer. join ( ) . unwrap ( ) . unwrap ( ) ;
264+ }
229265}
0 commit comments