@@ -4295,38 +4295,55 @@ impl PeerNetwork {
4295
4295
}
4296
4296
}
4297
4297
4298
- /// Refresh our view of the last three reward cycles
4299
- /// This ensures that the PeerNetwork has cached copies of the reward cycle data (including the
4300
- /// signing set) for the current, previous, and previous-previous reward cycles. This data is
4301
- /// in turn consumed by the Nakamoto block downloader, which must validate blocks signed from
4302
- /// any of these reward cycles.
4303
- #[ cfg_attr( test, mutants:: skip) ]
4304
- fn refresh_reward_cycles (
4305
- & mut self ,
4298
+ /// Determine if we need to invalidate a given cached reward set.
4299
+ ///
4300
+ /// In Epoch 2, this requires checking the first sortition in the start of the reward set's
4301
+ /// reward phase.
4302
+ ///
4303
+ /// In Nakamoto, this requires checking the anchor block in the prepare phase for the upcoming
4304
+ /// reward phase.
4305
+ fn check_reload_cached_reward_set (
4306
+ & self ,
4306
4307
sortdb : & SortitionDB ,
4307
- chainstate : & mut StacksChainState ,
4308
+ chainstate : & StacksChainState ,
4309
+ rc : u64 ,
4308
4310
tip_sn : & BlockSnapshot ,
4309
4311
tip_block_id : & StacksBlockId ,
4310
- ) -> Result < ( ) , net_error > {
4311
- let cur_rc = self
4312
- . burnchain
4313
- . block_height_to_reward_cycle ( tip_sn. block_height )
4314
- . expect ( "FATAL: sortition from before system start" ) ;
4315
-
4316
- let prev_rc = cur_rc. saturating_sub ( 1 ) ;
4317
- let prev_prev_rc = prev_rc. saturating_sub ( 1 ) ;
4318
- let ih = sortdb. index_handle ( & tip_sn. sortition_id ) ;
4319
-
4320
- for rc in [ cur_rc, prev_rc, prev_prev_rc] {
4321
- debug ! ( "Refresh reward cycle info for cycle {}" , rc) ;
4312
+ tip_height : u64 ,
4313
+ ) -> Result < bool , net_error > {
4314
+ let epoch = self . get_epoch_at_burn_height ( tip_sn. block_height ) ;
4315
+ if epoch. epoch_id >= StacksEpochId :: Epoch30 {
4316
+ // epoch 3, where there are no forks except from bugs or burnchain reorgs.
4317
+ // invalidate reward cycles on burnchain or stacks reorg, should they ever happen
4318
+ let reorg = Self :: is_reorg ( Some ( & self . burnchain_tip ) , tip_sn, sortdb)
4319
+ || Self :: is_nakamoto_reorg (
4320
+ & self . stacks_tip . block_id ( ) ,
4321
+ self . stacks_tip . height ,
4322
+ tip_block_id,
4323
+ tip_height,
4324
+ chainstate,
4325
+ ) ;
4326
+ if reorg {
4327
+ info ! ( "Burnchain or Stacks reorg detected; will invalidate cached reward set for cycle {rc}" ) ;
4328
+ }
4329
+ return Ok ( reorg) ;
4330
+ } else {
4331
+ // epoch 2
4322
4332
// NOTE: + 1 needed because the sortition db indexes anchor blocks at index height 1,
4323
4333
// not 0
4334
+ let ih = sortdb. index_handle ( & tip_sn. sortition_id ) ;
4324
4335
let rc_start_height = self . burnchain . nakamoto_first_block_of_cycle ( rc) + 1 ;
4325
4336
let Some ( ancestor_sort_id) =
4326
4337
get_ancestor_sort_id ( & ih, rc_start_height, & tip_sn. sortition_id ) ?
4327
4338
else {
4328
- // reward cycle is too far back for there to be an ancestor
4329
- continue ;
4339
+ // reward cycle is too far back for there to be an ancestor, so no need to
4340
+ // reload
4341
+ test_debug ! (
4342
+ "No ancestor sortition ID off of {} (height {}) at {rc_start_height})" ,
4343
+ & tip_sn. sortition_id,
4344
+ tip_sn. block_height
4345
+ ) ;
4346
+ return Ok ( false ) ;
4330
4347
} ;
4331
4348
let ancestor_ih = sortdb. index_handle ( & ancestor_sort_id) ;
4332
4349
let anchor_hash_opt = ancestor_ih. get_last_anchor_block_hash ( ) ?;
@@ -4340,12 +4357,53 @@ impl PeerNetwork {
4340
4357
|| cached_rc_info. anchor_block_hash == * anchor_hash
4341
4358
{
4342
4359
// cached reward set data is still valid
4343
- continue ;
4360
+ test_debug ! ( "Cached reward cycle {rc} is still valid" ) ;
4361
+ return Ok ( false ) ;
4344
4362
}
4345
4363
}
4346
4364
}
4365
+ }
4366
+
4367
+ Ok ( true )
4368
+ }
4347
4369
4348
- debug ! ( "Load reward cycle info for cycle {}" , rc) ;
4370
+ /// Refresh our view of the last three reward cycles
4371
+ /// This ensures that the PeerNetwork has cached copies of the reward cycle data (including the
4372
+ /// signing set) for the current, previous, and previous-previous reward cycles. This data is
4373
+ /// in turn consumed by the Nakamoto block downloader, which must validate blocks signed from
4374
+ /// any of these reward cycles.
4375
+ #[ cfg_attr( test, mutants:: skip) ]
4376
+ pub fn refresh_reward_cycles (
4377
+ & mut self ,
4378
+ sortdb : & SortitionDB ,
4379
+ chainstate : & mut StacksChainState ,
4380
+ tip_sn : & BlockSnapshot ,
4381
+ tip_block_id : & StacksBlockId ,
4382
+ tip_height : u64 ,
4383
+ ) -> Result < ( ) , net_error > {
4384
+ let cur_rc = self
4385
+ . burnchain
4386
+ . block_height_to_reward_cycle ( tip_sn. block_height )
4387
+ . expect ( "FATAL: sortition from before system start" ) ;
4388
+
4389
+ let prev_rc = cur_rc. saturating_sub ( 1 ) ;
4390
+ let prev_prev_rc = prev_rc. saturating_sub ( 1 ) ;
4391
+
4392
+ for rc in [ cur_rc, prev_rc, prev_prev_rc] {
4393
+ debug ! ( "Refresh reward cycle info for cycle {}" , rc) ;
4394
+ if self . current_reward_sets . contains_key ( & rc)
4395
+ && !self . check_reload_cached_reward_set (
4396
+ sortdb,
4397
+ chainstate,
4398
+ rc,
4399
+ tip_sn,
4400
+ tip_block_id,
4401
+ tip_height,
4402
+ ) ?
4403
+ {
4404
+ continue ;
4405
+ }
4406
+ debug ! ( "Refresh reward cycle info for cycle {rc}" ) ;
4349
4407
let Some ( ( reward_set_info, anchor_block_header) ) = load_nakamoto_reward_set (
4350
4408
rc,
4351
4409
& tip_sn. sortition_id ,
@@ -4452,6 +4510,7 @@ impl PeerNetwork {
4452
4510
chainstate,
4453
4511
& canonical_sn,
4454
4512
& new_stacks_tip_block_id,
4513
+ stacks_tip_height,
4455
4514
) ?;
4456
4515
}
4457
4516
@@ -4649,7 +4708,7 @@ impl PeerNetwork {
4649
4708
debug ! (
4650
4709
"{:?}: handle unsolicited stacks messages: tenure changed {} != {}, {} buffered" ,
4651
4710
self . get_local_peer( ) ,
4652
- & self . burnchain_tip . consensus_hash,
4711
+ & self . stacks_tip . consensus_hash,
4653
4712
& canonical_sn. consensus_hash,
4654
4713
self . pending_stacks_messages
4655
4714
. iter( )
@@ -4751,7 +4810,6 @@ impl PeerNetwork {
4751
4810
ibd,
4752
4811
true ,
4753
4812
) ;
4754
-
4755
4813
let unhandled_messages =
4756
4814
self . handle_unsolicited_stacks_messages ( chainstate, unhandled_messages, true ) ;
4757
4815
@@ -4998,7 +5056,7 @@ impl PeerNetwork {
4998
5056
Ok ( ( ) )
4999
5057
}
5000
5058
5001
- /// Static helper to check to see if there has been a reorg
5059
+ /// Static helper to check to see if there has been a burnchain reorg
5002
5060
pub fn is_reorg (
5003
5061
last_sort_tip : Option < & BlockSnapshot > ,
5004
5062
sort_tip : & BlockSnapshot ,
@@ -5021,15 +5079,15 @@ impl PeerNetwork {
5021
5079
{
5022
5080
// current and previous sortition tips are at the same height, but represent different
5023
5081
// blocks.
5024
- debug ! (
5025
- "Reorg detected at burn height {}: {} != {}" ,
5082
+ info ! (
5083
+ "Burnchain reorg detected at burn height {}: {} != {}" ,
5026
5084
sort_tip. block_height, & last_sort_tip. consensus_hash, & sort_tip. consensus_hash
5027
5085
) ;
5028
5086
return true ;
5029
5087
}
5030
5088
5031
5089
// It will never be the case that the last and current tip have different heights, but the
5032
- // smae consensus hash. If they have the same height, then we would have already returned
5090
+ // same consensus hash. If they have the same height, then we would have already returned
5033
5091
// since we've handled both the == and != cases for their consensus hashes. So if we reach
5034
5092
// this point, the heights and consensus hashes are not equal. We only need to check that
5035
5093
// last_sort_tip is an ancestor of sort_tip
@@ -5061,6 +5119,60 @@ impl PeerNetwork {
5061
5119
false
5062
5120
}
5063
5121
5122
+ /// Static helper to check to see if there has been a Nakamoto reorg.
5123
+ /// Return true if there's a Nakamoto reorg
5124
+ /// Return false otherwise.
5125
+ pub fn is_nakamoto_reorg (
5126
+ last_stacks_tip : & StacksBlockId ,
5127
+ last_stacks_tip_height : u64 ,
5128
+ stacks_tip : & StacksBlockId ,
5129
+ stacks_tip_height : u64 ,
5130
+ chainstate : & StacksChainState ,
5131
+ ) -> bool {
5132
+ if last_stacks_tip == stacks_tip {
5133
+ // same tip
5134
+ return false ;
5135
+ }
5136
+
5137
+ if last_stacks_tip_height == stacks_tip_height && last_stacks_tip != stacks_tip {
5138
+ // last block is a sibling
5139
+ info ! (
5140
+ "Stacks reorg detected at stacks height {last_stacks_tip_height}: {last_stacks_tip} != {stacks_tip}" ,
5141
+ ) ;
5142
+ return true ;
5143
+ }
5144
+
5145
+ if stacks_tip_height < last_stacks_tip_height {
5146
+ info ! (
5147
+ "Stacks reorg (chain shrink) detected at stacks height {last_stacks_tip_height}: {last_stacks_tip} != {stacks_tip}" ,
5148
+ ) ;
5149
+ return true ;
5150
+ }
5151
+
5152
+ // It will never be the case that the last and current tip have different heights, but the
5153
+ // same block ID. If they have the same height, then we would have already returned
5154
+ // since we've handled both the == and != cases for their block IDs. So if we reach
5155
+ // this point, the heights and block IDs are not equal. We only need to check that
5156
+ // last_stacks_tip is an ancestor of stacks_tip
5157
+
5158
+ let mut cursor = stacks_tip. clone ( ) ;
5159
+ for _ in last_stacks_tip_height..stacks_tip_height {
5160
+ let Ok ( Some ( parent_id) ) =
5161
+ NakamotoChainState :: get_nakamoto_parent_block_id ( chainstate. db ( ) , & cursor)
5162
+ else {
5163
+ error ! ( "Failed to load parent id of {cursor}" ) ;
5164
+ return true ;
5165
+ } ;
5166
+ cursor = parent_id;
5167
+ }
5168
+
5169
+ debug ! ( "is_nakamoto_reorg check" ;
5170
+ "parent_id" => %cursor,
5171
+ "last_stacks_tip" => %last_stacks_tip) ;
5172
+
5173
+ cursor != * last_stacks_tip
5174
+ }
5175
+
5064
5176
/// Log our neighbors.
5065
5177
/// Used for testing and debuggin
5066
5178
fn log_neighbors ( & mut self ) {
@@ -5143,13 +5255,19 @@ impl PeerNetwork {
5143
5255
}
5144
5256
} ;
5145
5257
5258
+ test_debug ! (
5259
+ "unsolicited_buffered_messages = {:?}" ,
5260
+ & unsolicited_buffered_messages
5261
+ ) ;
5146
5262
let mut network_result = NetworkResult :: new (
5147
5263
self . stacks_tip . block_id ( ) ,
5148
5264
self . num_state_machine_passes ,
5149
5265
self . num_inv_sync_passes ,
5150
5266
self . num_downloader_passes ,
5151
5267
self . peers . len ( ) ,
5152
5268
self . chain_view . burn_block_height ,
5269
+ self . stacks_tip . coinbase_height ,
5270
+ self . stacks_tip . height ,
5153
5271
self . chain_view . rc_consensus_hash . clone ( ) ,
5154
5272
self . get_stacker_db_configs_owned ( ) ,
5155
5273
) ;
0 commit comments