@@ -456,11 +456,14 @@ impl RelayerThread {
456
456
} ) ;
457
457
}
458
458
459
+ let mining_pkh_opt = self . get_mining_key_pkh ( ) ;
460
+
459
461
// a sortition happened, but we didn't win.
460
462
match Self :: can_continue_tenure (
461
463
& self . sortdb ,
464
+ & mut self . chainstate ,
462
465
sn. consensus_hash ,
463
- self . get_mining_key_pkh ( ) ,
466
+ mining_pkh_opt ,
464
467
) {
465
468
Ok ( Some ( _) ) => {
466
469
// we can continue our ongoing tenure, but we should give the new winning miner
@@ -1124,21 +1127,147 @@ impl RelayerThread {
1124
1127
Ok ( ih. get_last_snapshot_with_sortition ( sort_tip. block_height ) ?)
1125
1128
}
1126
1129
1130
+ /// Is the given sortition a valid sortition?
1131
+ /// I.e. whose winning commit's parent tenure ID is on the canonical Stacks history,
1132
+ /// and whose consensus hash corresponds to the ongoing tenure or a confirmed tenure?
1133
+ fn is_valid_sortition (
1134
+ chain_state : & mut StacksChainState ,
1135
+ stacks_tip_id : & StacksBlockId ,
1136
+ stacks_tip_sn : & BlockSnapshot ,
1137
+ burn_tip_ch : & ConsensusHash ,
1138
+ sn : & BlockSnapshot ,
1139
+ ) -> Result < bool , NakamotoNodeError > {
1140
+ if !sn. sortition {
1141
+ // definitely not a valid sortition
1142
+ debug ! ( "Relayer: Sortition {} is empty" , & sn. consensus_hash) ;
1143
+ return Ok ( false ) ;
1144
+ }
1145
+
1146
+ // check that this commit's parent tenure ID is on the history tipped at
1147
+ // `stacks_tip_id`
1148
+ let mut ic = chain_state. index_conn ( ) ;
1149
+ let parent_tenure_id = StacksBlockId ( sn. winning_stacks_block_hash . clone ( ) . 0 ) ;
1150
+ let height_opt = ic. get_ancestor_block_height ( & parent_tenure_id, stacks_tip_id) ?;
1151
+ if height_opt. is_none ( ) {
1152
+ // parent_tenure_id is not an ancestor of stacks_tip_id
1153
+ debug ! (
1154
+ "Relayer: Sortition {} has winning commit hash {}, which is not canonical" ,
1155
+ & sn. consensus_hash, & parent_tenure_id
1156
+ ) ;
1157
+ return Ok ( false ) ;
1158
+ }
1159
+
1160
+ if sn. consensus_hash == * burn_tip_ch {
1161
+ // sn is the sortition tip, so this sortition must commit to the tenure start block of
1162
+ // the ongoing Stacks tenure.
1163
+ let highest_tenure_start_block_header = NakamotoChainState :: get_tenure_start_block_header (
1164
+ & mut ic,
1165
+ stacks_tip_id,
1166
+ & stacks_tip_sn. consensus_hash
1167
+ ) ?
1168
+ . ok_or_else ( || {
1169
+ error ! (
1170
+ "Relayer: Failed to find tenure-start block header for stacks tip {stacks_tip_id}"
1171
+ ) ;
1172
+ NakamotoNodeError :: ParentNotFound
1173
+ } ) ?;
1174
+
1175
+ let highest_tenure_start_block_id =
1176
+ highest_tenure_start_block_header. index_block_hash ( ) ;
1177
+ if highest_tenure_start_block_id != parent_tenure_id {
1178
+ debug ! ( "Relayer: Sortition {} is at the tip, but does not commit to {} so cannot be valid" , & sn. consensus_hash, & parent_tenure_id;
1179
+ "highest_tenure_start_block_header.block_id()" => %highest_tenure_start_block_id) ;
1180
+ return Ok ( false ) ;
1181
+ }
1182
+ }
1183
+
1184
+ Ok ( true )
1185
+ }
1186
+
1187
+ /// Determine the highest valid sortition higher than `elected_tenure_id`, but no higher than
1188
+ /// `sort_tip`.
1189
+ ///
1190
+ /// This is the highest non-empty sortition (up to and including `sort_tip`)
1191
+ /// whose winning commit's parent tenure ID matches the
1192
+ /// Stacks tip, and whose consensus hash matches the Stacks tip's tenure ID.
1193
+ ///
1194
+ /// Returns Ok(Some(..)) if such a sortition is found, and is higher than that of
1195
+ /// `elected_tenure_id`.
1196
+ /// Returns Ok(None) if no such sortition is found.
1197
+ /// Returns Err(..) on DB errors.
1198
+ fn find_highest_valid_sortition (
1199
+ sortdb : & SortitionDB ,
1200
+ chain_state : & mut StacksChainState ,
1201
+ sort_tip : & BlockSnapshot ,
1202
+ elected_tenure_id : & ConsensusHash ,
1203
+ ) -> Result < Option < BlockSnapshot > , NakamotoNodeError > {
1204
+ // sanity check -- if sort_tip is the elected_tenure_id sortition, then there are no higher
1205
+ // valid sortitions.
1206
+ if sort_tip. consensus_hash == * elected_tenure_id {
1207
+ return Ok ( None ) ;
1208
+ }
1209
+
1210
+ let mut cursor = sort_tip. clone ( ) ;
1211
+ let ( canonical_stacks_tip_ch, canonical_stacks_tip_bh) =
1212
+ SortitionDB :: get_canonical_stacks_chain_tip_hash ( sortdb. conn ( ) ) . unwrap ( ) ;
1213
+ let canonical_stacks_tip =
1214
+ StacksBlockId :: new ( & canonical_stacks_tip_ch, & canonical_stacks_tip_bh) ;
1215
+
1216
+ let Ok ( Some ( canonical_stacks_tip_sn) ) =
1217
+ SortitionDB :: get_block_snapshot_consensus ( sortdb. conn ( ) , & canonical_stacks_tip_ch)
1218
+ else {
1219
+ return Err ( NakamotoNodeError :: ParentNotFound ) ;
1220
+ } ;
1221
+
1222
+ loop {
1223
+ debug ! (
1224
+ "Relayer: check sortition {} to see if it is valid" ,
1225
+ & cursor. consensus_hash
1226
+ ) ;
1227
+
1228
+ // is this a valid sortiton?
1229
+ if Self :: is_valid_sortition (
1230
+ chain_state,
1231
+ & canonical_stacks_tip,
1232
+ & canonical_stacks_tip_sn,
1233
+ & sort_tip. consensus_hash ,
1234
+ & cursor,
1235
+ ) ? {
1236
+ return Ok ( Some ( cursor) ) ;
1237
+ }
1238
+
1239
+ // nope. continue the search
1240
+ let Some ( cursor_parent) =
1241
+ SortitionDB :: get_block_snapshot ( sortdb. conn ( ) , & cursor. parent_sortition_id ) ?
1242
+ else {
1243
+ return Ok ( None ) ;
1244
+ } ;
1245
+
1246
+ if cursor_parent. consensus_hash == * elected_tenure_id {
1247
+ return Ok ( None ) ;
1248
+ }
1249
+
1250
+ cursor = cursor_parent;
1251
+ }
1252
+ }
1253
+
1127
1254
/// Determine if the miner can contine an existing tenure with the new sortition (identified
1128
1255
/// by `new_burn_view`)
1129
1256
///
1130
1257
/// Assumes that the caller has already checked that the given miner has _not_ won the new
1131
1258
/// sortition.
1132
1259
///
1133
- /// Will return Ok(Some(..)) even if `new_burn_view`'s sortition had a winner that was not this
1134
- /// miner. It's on signers to either accept the resulting tenure-extend from this miner, or a
1135
- /// block-found from the other winning miner.
1260
+ /// Returns Ok(Some(stacks-tip-election-snapshot)) if the last-winning miner needs to extend.
1261
+ /// For now, this only happens if the miner's election snapshot was the last-known valid and
1262
+ /// non-empty snapshot. In the future, this function may return Ok(Some(..)) if the node
1263
+ /// determines that a subsequent miner won sortition, but never came online.
1264
+ ///
1265
+ /// Returns OK(None) if the last-winning miner should not extend its tenure.
1136
1266
///
1137
- /// Returns Ok(Some(stacks-tip-election-snapshot)) if so
1138
- /// Returns OK(None) if not.
1139
1267
/// Returns Err(..) on DB error
1140
1268
pub ( crate ) fn can_continue_tenure (
1141
1269
sortdb : & SortitionDB ,
1270
+ chain_state : & mut StacksChainState ,
1142
1271
new_burn_view : ConsensusHash ,
1143
1272
mining_key_opt : Option < Hash160 > ,
1144
1273
) -> Result < Option < BlockSnapshot > , NakamotoNodeError > {
@@ -1187,6 +1316,22 @@ impl RelayerThread {
1187
1316
return Ok ( None ) ;
1188
1317
}
1189
1318
1319
+ // For now, only allow the miner to extend its tenure if won the highest valid sortition.
1320
+ // There cannot be any higher sortitions that are valid (as defined above).
1321
+ //
1322
+ // In the future, the miner will be able to extend its tenure even if there are higher
1323
+ // valid sortitions, but only if it determines that the miners of those sortitions are
1324
+ // offline.
1325
+ if let Some ( highest_valid_sortition) = Self :: find_highest_valid_sortition (
1326
+ sortdb,
1327
+ chain_state,
1328
+ & sort_tip,
1329
+ & canonical_stacks_snapshot. consensus_hash ,
1330
+ ) ? {
1331
+ info ! ( "Relayer: will not extend tenure -- we won sortition {}, but the highest valid sortition is {}" , & canonical_stacks_snapshot. consensus_hash, & highest_valid_sortition. consensus_hash) ;
1332
+ return Ok ( None ) ;
1333
+ }
1334
+
1190
1335
Ok ( Some ( canonical_stacks_snapshot) )
1191
1336
}
1192
1337
@@ -1203,10 +1348,12 @@ impl RelayerThread {
1203
1348
}
1204
1349
debug ! ( "Relayer: successfully stopped tenure; will try to continue." ) ;
1205
1350
1351
+ let mining_pkh_opt = self . get_mining_key_pkh ( ) ;
1206
1352
let Some ( canonical_stacks_tip_election_snapshot) = Self :: can_continue_tenure (
1207
1353
& self . sortdb ,
1354
+ & mut self . chainstate ,
1208
1355
new_burn_view. clone ( ) ,
1209
- self . get_mining_key_pkh ( ) ,
1356
+ mining_pkh_opt ,
1210
1357
) ?
1211
1358
else {
1212
1359
return Ok ( ( ) ) ;
@@ -1514,24 +1661,26 @@ impl RelayerThread {
1514
1661
) )
1515
1662
}
1516
1663
1517
- /// Try to start up a tenure-extend, after a delay has passed.
1518
- /// We would do this if we were the miner of the ongoing tenure, but did not win the last
1519
- /// sortition, and the winning miner never produced a block.
1664
+ /// Try to start up a tenure-extend.
1665
+ /// Only do this if the miner won the last-ever sortition but the burn view has changed.
1666
+ /// In the future, the miner will also try to extend its tenure if a subsequent miner appears
1667
+ /// to be offline.
1520
1668
fn try_continue_tenure ( & mut self ) {
1521
1669
if self . tenure_extend_timeout . is_none ( ) {
1522
1670
return ;
1523
1671
}
1524
1672
1673
+ // time to poll to see if we should begin a tenure-extend?
1525
1674
let deadline_passed = self
1526
1675
. tenure_extend_timeout
1527
1676
. map ( |tenure_extend_timeout| {
1528
1677
let deadline_passed =
1529
- tenure_extend_timeout. elapsed ( ) > self . config . miner . tenure_extend_wait_secs ;
1678
+ tenure_extend_timeout. elapsed ( ) > self . config . miner . tenure_extend_poll_secs ;
1530
1679
if !deadline_passed {
1531
1680
test_debug ! (
1532
1681
"Relayer: will not try to tenure-extend yet ({} <= {})" ,
1533
1682
tenure_extend_timeout. elapsed( ) . as_secs( ) ,
1534
- self . config. miner. tenure_extend_wait_secs . as_secs( )
1683
+ self . config. miner. tenure_extend_poll_secs . as_secs( )
1535
1684
) ;
1536
1685
}
1537
1686
deadline_passed
0 commit comments