@@ -19,6 +19,7 @@ use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
19
19
use blockstack_lib:: chainstate:: stacks:: TenureChangePayload ;
20
20
use blockstack_lib:: net:: api:: getsortition:: SortitionInfo ;
21
21
use blockstack_lib:: util_lib:: db:: Error as DBError ;
22
+ use libsigner:: v0:: messages:: RejectReason ;
22
23
use slog:: { slog_info, slog_warn} ;
23
24
use stacks_common:: types:: chainstate:: { BurnchainHeaderHash , ConsensusHash , StacksPublicKey } ;
24
25
use stacks_common:: util:: get_epoch_time_secs;
@@ -40,6 +41,12 @@ pub enum SignerChainstateError {
40
41
ClientError ( #[ from] ClientError ) ,
41
42
}
42
43
44
+ impl From < SignerChainstateError > for RejectReason {
45
+ fn from ( error : SignerChainstateError ) -> Self {
46
+ RejectReason :: ConnectivityIssues ( error. to_string ( ) )
47
+ }
48
+ }
49
+
43
50
/// Captures this signer's current view of a sortition's miner.
44
51
#[ derive( PartialEq , Eq , Debug ) ]
45
52
pub enum SortitionMinerStatus {
@@ -201,7 +208,7 @@ impl SortitionsView {
201
208
block : & NakamotoBlock ,
202
209
block_pk : & StacksPublicKey ,
203
210
reset_view_if_wrong_consensus_hash : bool ,
204
- ) -> Result < bool , SignerChainstateError > {
211
+ ) -> Result < ( ) , RejectReason > {
205
212
if self
206
213
. cur_sortition
207
214
. is_timed_out ( self . config . block_proposal_timeout , signer_db) ?
@@ -213,7 +220,16 @@ impl SortitionsView {
213
220
"current_sortition_consensus_hash" => ?self . cur_sortition. consensus_hash,
214
221
) ;
215
222
self . cur_sortition . miner_status = SortitionMinerStatus :: InvalidatedBeforeFirstBlock ;
216
- } else if let Some ( tip) = signer_db. get_canonical_tip ( ) ? {
223
+
224
+ // If the current proposal is also for this current
225
+ // sortition, then we can return early here.
226
+ if self . cur_sortition . consensus_hash == block. header . consensus_hash {
227
+ return Err ( RejectReason :: InvalidMiner ) ;
228
+ }
229
+ } else if let Some ( tip) = signer_db
230
+ . get_canonical_tip ( )
231
+ . map_err ( SignerChainstateError :: from) ?
232
+ {
217
233
// Check if the current sortition is aligned with the expected tenure:
218
234
// - If the tip is in the current tenure, we are in the process of mining this tenure.
219
235
// - If the tip is not in the current tenure, then we’re starting a new tenure,
@@ -243,6 +259,12 @@ impl SortitionsView {
243
259
) ;
244
260
self . cur_sortition . miner_status =
245
261
SortitionMinerStatus :: InvalidatedBeforeFirstBlock ;
262
+
263
+ // If the current proposal is also for this current
264
+ // sortition, then we can return early here.
265
+ if self . cur_sortition . consensus_hash == block. header . consensus_hash {
266
+ return Err ( RejectReason :: ReorgNotAllowed ) ;
267
+ }
246
268
}
247
269
}
248
270
}
@@ -266,7 +288,7 @@ impl SortitionsView {
266
288
"current_sortition_consensus_hash" => ?self . cur_sortition. consensus_hash,
267
289
"last_sortition_consensus_hash" => ?self . last_sortition. as_ref( ) . map( |x| x. consensus_hash) ,
268
290
) ;
269
- return Ok ( false ) ;
291
+ return Err ( RejectReason :: InvalidBitvec ) ;
270
292
}
271
293
272
294
let block_pkh = Hash160 :: from_data ( & block_pk. to_bytes_compressed ( ) ) ;
@@ -293,7 +315,8 @@ impl SortitionsView {
293
315
"current_sortition_consensus_hash" => ?self . cur_sortition. consensus_hash,
294
316
"last_sortition_consensus_hash" => ?self . last_sortition. as_ref( ) . map( |x| x. consensus_hash) ,
295
317
) ;
296
- self . reset_view ( client) ?;
318
+ self . reset_view ( client)
319
+ . map_err ( SignerChainstateError :: from) ?;
297
320
return self . check_proposal ( client, signer_db, block, block_pk, false ) ;
298
321
}
299
322
warn ! (
@@ -303,7 +326,7 @@ impl SortitionsView {
303
326
"current_sortition_consensus_hash" => ?self . cur_sortition. consensus_hash,
304
327
"last_sortition_consensus_hash" => ?self . last_sortition. as_ref( ) . map( |x| x. consensus_hash) ,
305
328
) ;
306
- return Ok ( false ) ;
329
+ return Err ( RejectReason :: SortitionViewMismatch ) ;
307
330
} ;
308
331
309
332
if proposed_by. state ( ) . miner_pkh != block_pkh {
@@ -315,7 +338,7 @@ impl SortitionsView {
315
338
"proposed_block_pubkey_hash" => %block_pkh,
316
339
"sortition_winner_pubkey_hash" => %proposed_by. state( ) . miner_pkh,
317
340
) ;
318
- return Ok ( false ) ;
341
+ return Err ( RejectReason :: PubkeyHashMismatch ) ;
319
342
}
320
343
321
344
// check that this miner is the most recent sortition
@@ -327,7 +350,7 @@ impl SortitionsView {
327
350
"proposed_block_consensus_hash" => %block. header. consensus_hash,
328
351
"proposed_block_signer_sighash" => %block. header. signer_signature_hash( ) ,
329
352
) ;
330
- return Ok ( false ) ;
353
+ return Err ( RejectReason :: InvalidMiner ) ;
331
354
}
332
355
}
333
356
ProposedBy :: LastSortition ( last_sortition) => {
@@ -343,27 +366,26 @@ impl SortitionsView {
343
366
"current_sortition_miner_status" => ?self . cur_sortition. miner_status,
344
367
"last_sortition" => %last_sortition. consensus_hash
345
368
) ;
346
- return Ok ( false ) ;
369
+ return Err ( RejectReason :: NotLatestSortitionWinner ) ;
347
370
}
348
371
}
349
372
} ;
350
373
351
374
if let Some ( tenure_change) = block. get_tenure_change_tx_payload ( ) {
352
- if ! self . validate_tenure_change_payload (
375
+ self . validate_tenure_change_payload (
353
376
& proposed_by,
354
377
tenure_change,
355
378
block,
356
379
signer_db,
357
380
client,
358
- ) ? {
359
- return Ok ( false ) ;
360
- }
381
+ ) ?;
361
382
} else {
362
383
// check if the new block confirms the last block in the current tenure
363
384
let confirms_latest_in_tenure =
364
- Self :: confirms_latest_block_in_same_tenure ( block, signer_db) ?;
385
+ Self :: confirms_latest_block_in_same_tenure ( block, signer_db)
386
+ . map_err ( SignerChainstateError :: from) ?;
365
387
if !confirms_latest_in_tenure {
366
- return Ok ( false ) ;
388
+ return Err ( RejectReason :: InvalidParentBlock ) ;
367
389
}
368
390
}
369
391
@@ -389,11 +411,11 @@ impl SortitionsView {
389
411
"extend_timestamp" => extend_timestamp,
390
412
"epoch_time" => epoch_time,
391
413
) ;
392
- return Ok ( false ) ;
414
+ return Err ( RejectReason :: InvalidTenureExtend ) ;
393
415
}
394
416
}
395
417
396
- Ok ( true )
418
+ Ok ( ( ) )
397
419
}
398
420
399
421
fn check_parent_tenure_choice (
@@ -471,7 +493,7 @@ impl SortitionsView {
471
493
0
472
494
} ;
473
495
if Duration :: from_secs ( proposal_to_sortition)
474
- <= * first_proposal_burn_block_timing
496
+ < * first_proposal_burn_block_timing
475
497
{
476
498
info ! (
477
499
"Miner is not building off of most recent tenure. A tenure they reorg has already mined blocks, but the block was poorly timed, allowing the reorg." ;
@@ -577,7 +599,7 @@ impl SortitionsView {
577
599
"proposed_chain_length" => block. header. chain_length,
578
600
"expected_at_least" => info. block. header. chain_length + 1 ,
579
601
) ;
580
- if info. signed_group . map_or ( true , |signed_time| {
602
+ if info. signed_group . is_none_or ( |signed_time| {
581
603
signed_time + reorg_attempts_activity_timeout. as_secs ( ) > get_epoch_time_secs ( )
582
604
} ) {
583
605
// Note if there is no signed_group time, this is a locally accepted block (i.e. tenure_last_block_proposal_timeout has not been exceeded).
@@ -649,7 +671,7 @@ impl SortitionsView {
649
671
block : & NakamotoBlock ,
650
672
signer_db : & mut SignerDb ,
651
673
client : & StacksClient ,
652
- ) -> Result < bool , SignerChainstateError > {
674
+ ) -> Result < ( ) , RejectReason > {
653
675
// Ensure that the tenure change block confirms the expected parent block
654
676
let confirms_expected_parent = Self :: check_tenure_change_confirms_parent (
655
677
tenure_change,
@@ -658,9 +680,10 @@ impl SortitionsView {
658
680
client,
659
681
self . config . tenure_last_block_proposal_timeout ,
660
682
self . config . reorg_attempts_activity_timeout ,
661
- ) ?;
683
+ )
684
+ . map_err ( SignerChainstateError :: from) ?;
662
685
if !confirms_expected_parent {
663
- return Ok ( false ) ;
686
+ return Err ( RejectReason :: InvalidParentBlock ) ;
664
687
}
665
688
// now, we have to check if the parent tenure was a valid choice.
666
689
let is_valid_parent_tenure = Self :: check_parent_tenure_choice (
@@ -671,21 +694,23 @@ impl SortitionsView {
671
694
& self . config . first_proposal_burn_block_timing ,
672
695
) ?;
673
696
if !is_valid_parent_tenure {
674
- return Ok ( false ) ;
697
+ return Err ( RejectReason :: ReorgNotAllowed ) ;
675
698
}
676
699
let last_in_current_tenure = signer_db
677
700
. get_last_globally_accepted_block ( & block. header . consensus_hash )
678
- . map_err ( |e| ClientError :: InvalidResponse ( e. to_string ( ) ) ) ?;
701
+ . map_err ( |e| {
702
+ SignerChainstateError :: from ( ClientError :: InvalidResponse ( e. to_string ( ) ) )
703
+ } ) ?;
679
704
if let Some ( last_in_current_tenure) = last_in_current_tenure {
680
705
warn ! (
681
706
"Miner block proposal contains a tenure change, but we've already signed a block in this tenure. Considering proposal invalid." ;
682
707
"proposed_block_consensus_hash" => %block. header. consensus_hash,
683
708
"proposed_block_signer_sighash" => %block. header. signer_signature_hash( ) ,
684
709
"last_in_tenure_signer_sighash" => %last_in_current_tenure. block. header. signer_signature_hash( ) ,
685
710
) ;
686
- return Ok ( false ) ;
711
+ return Err ( RejectReason :: DuplicateBlockFound ) ;
687
712
}
688
- Ok ( true )
713
+ Ok ( ( ) )
689
714
}
690
715
691
716
fn confirms_latest_block_in_same_tenure (
0 commit comments