Skip to content

Commit c6f128f

Browse files
committed
Remove enforcing blocks to be build off of the stacks tips tenure
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent db52891 commit c6f128f

File tree

3 files changed

+98
-102
lines changed

3 files changed

+98
-102
lines changed

stackslib/src/chainstate/nakamoto/coordinator/tests.rs

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -824,15 +824,19 @@ fn test_nakamoto_chainstate_getters() {
824824
.unwrap()
825825
.is_some());
826826

827-
// this should fail, since it's not idempotent -- the highest tenure _is_ this tenure
828-
assert!(NakamotoChainState::check_nakamoto_tenure(
829-
chainstate.db(),
830-
&mut sort_tx,
831-
&blocks[0].header,
832-
&tenure_change_payload,
833-
)
834-
.unwrap()
835-
.is_none());
827+
// this should return the previous tenure
828+
assert_eq!(
829+
NakamotoChainState::check_nakamoto_tenure(
830+
chainstate.db(),
831+
&mut sort_tx,
832+
&blocks[0].header,
833+
&tenure_change_payload,
834+
)
835+
.unwrap()
836+
.unwrap()
837+
.tenure_id_consensus_hash,
838+
tenure_change_payload.prev_tenure_consensus_hash
839+
);
836840

837841
let cur_burn_tip = SortitionDB::get_canonical_burn_chain_tip(sort_tx.sqlite()).unwrap();
838842
let (cur_stacks_ch, cur_stacks_bhh, cur_stacks_height) =
@@ -854,14 +858,18 @@ fn test_nakamoto_chainstate_getters() {
854858
.unwrap();
855859

856860
// check works (this would be the first tenure)
857-
assert!(NakamotoChainState::check_nakamoto_tenure(
858-
chainstate.db(),
859-
&mut sort_tx,
860-
&blocks[0].header,
861-
&tenure_change_payload,
862-
)
863-
.unwrap()
864-
.is_some());
861+
assert_eq!(
862+
NakamotoChainState::check_nakamoto_tenure(
863+
chainstate.db(),
864+
&mut sort_tx,
865+
&blocks[0].header,
866+
&tenure_change_payload,
867+
)
868+
.unwrap()
869+
.unwrap()
870+
.tenure_id_consensus_hash,
871+
tenure_change_payload.prev_tenure_consensus_hash
872+
);
865873

866874
// restore
867875
sort_tx
@@ -1057,24 +1065,32 @@ fn test_nakamoto_chainstate_getters() {
10571065
)
10581066
.unwrap();
10591067

1060-
assert!(NakamotoChainState::check_nakamoto_tenure(
1061-
chainstate.db(),
1062-
&mut sort_tx,
1063-
&new_blocks[0].header,
1064-
&tenure_change_payload,
1065-
)
1066-
.unwrap()
1067-
.is_some());
1068+
assert_eq!(
1069+
NakamotoChainState::check_nakamoto_tenure(
1070+
chainstate.db(),
1071+
&mut sort_tx,
1072+
&new_blocks[0].header,
1073+
&tenure_change_payload,
1074+
)
1075+
.unwrap()
1076+
.unwrap()
1077+
.tenure_id_consensus_hash,
1078+
tenure_change_payload.prev_tenure_consensus_hash
1079+
);
10681080

1069-
// checks on older confired tenures continue to fail
1070-
assert!(NakamotoChainState::check_nakamoto_tenure(
1071-
chainstate.db(),
1072-
&mut sort_tx,
1073-
&blocks[0].header,
1074-
&old_tenure_change_payload,
1075-
)
1076-
.unwrap()
1077-
.is_none());
1081+
// checks on older confired tenures return the prev tenure
1082+
assert_eq!(
1083+
NakamotoChainState::check_nakamoto_tenure(
1084+
chainstate.db(),
1085+
&mut sort_tx,
1086+
&blocks[0].header,
1087+
&old_tenure_change_payload,
1088+
)
1089+
.unwrap()
1090+
.unwrap()
1091+
.tenure_id_consensus_hash,
1092+
old_tenure_change_payload.prev_tenure_consensus_hash
1093+
);
10781094

10791095
// restore
10801096
sort_tx

stackslib/src/chainstate/nakamoto/tenure.rs

Lines changed: 28 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,17 @@ impl NakamotoChainState {
528528
}
529529
}
530530

531+
/// Get the nakamoto tenure by id
532+
pub fn get_nakamoto_tenure_change_by_tenure_id(
533+
headers_conn: &Connection,
534+
tenure_consensus_hash: &ConsensusHash,
535+
) -> Result<Option<NakamotoTenure>, ChainstateError> {
536+
let sql = "SELECT * FROM nakamoto_tenures WHERE tenure_id_consensus_hash = ?1 ORDER BY tenure_index DESC LIMIT 1";
537+
let args: &[&dyn ToSql] = &[&tenure_consensus_hash];
538+
let tenure_opt: Option<NakamotoTenure> = query_row(headers_conn, sql, args)?;
539+
Ok(tenure_opt)
540+
}
541+
531542
/// Get a nakamoto tenure-change by its tenure ID consensus hash.
532543
/// Get the highest such record. It will be the last-processed BlockFound tenure
533544
/// for the given sortition consensus hash.
@@ -544,7 +555,7 @@ impl NakamotoChainState {
544555
Ok(tenure_opt)
545556
}
546557

547-
/// Get the highest processed tenure on the canonical sortition history.
558+
/// Get the highest non-empty processed tenure on the canonical sortition history.
548559
pub fn get_highest_nakamoto_tenure(
549560
headers_conn: &Connection,
550561
sortdb_conn: &Connection,
@@ -555,10 +566,7 @@ impl NakamotoChainState {
555566
// no chain tip, so no tenure
556567
return Ok(None);
557568
}
558-
let sql = "SELECT * FROM nakamoto_tenures WHERE tenure_id_consensus_hash = ?1 ORDER BY tenure_index DESC LIMIT 1";
559-
let args: &[&dyn ToSql] = &[&tip_ch];
560-
let tenure_opt: Option<NakamotoTenure> = query_row(headers_conn, sql, args)?;
561-
Ok(tenure_opt)
569+
Self::get_nakamoto_tenure_change_by_tenure_id(headers_conn, &tip_ch)
562570
}
563571

564572
/// Verify that a tenure change tx is a valid first-ever tenure change. It must connect to an
@@ -655,7 +663,7 @@ impl NakamotoChainState {
655663
/// * previous_tenure_blocks
656664
/// * cause
657665
///
658-
/// Returns Ok(Some(highest-processed-tenure)) on success
666+
/// Returns Ok(Some(processed-tenure)) on success
659667
/// Returns Ok(None) if the tenure change is invalid
660668
/// Returns Err(..) on DB error
661669
pub(crate) fn check_nakamoto_tenure<SH: SortitionHandle>(
@@ -742,10 +750,13 @@ impl NakamotoChainState {
742750
return Ok(None);
743751
}
744752

745-
let Some(highest_processed_tenure) =
746-
Self::get_highest_nakamoto_tenure(headers_conn, sort_handle.sqlite())?
753+
// Note in the extend case, this will actually return the current tenure, not the parent as prev_tenure_consensus_hash will be the same as tenure_consensus_hash
754+
let Some(tenure) = Self::get_nakamoto_tenure_change_by_tenure_id(
755+
headers_conn,
756+
&tenure_payload.prev_tenure_consensus_hash,
757+
)?
747758
else {
748-
// no previous tenures. This is the first tenure change. It should point to an epoch
759+
// not building off of a previous Nakamoto tenure. This is the first tenure change. It should point to an epoch
749760
// 2.x block.
750761
return Self::check_first_nakamoto_tenure_change(headers_conn, tenure_payload);
751762
};
@@ -764,84 +775,34 @@ impl NakamotoChainState {
764775
);
765776
return Ok(None);
766777
}
767-
if tenure_payload.burn_view_consensus_hash
768-
== highest_processed_tenure.burn_view_consensus_hash
769-
{
770-
// if we're extending tenure within the same sortition, then the tenure and
771-
// prev_tenure consensus hashes must match that of the highest.
772-
if highest_processed_tenure.tenure_id_consensus_hash
773-
!= tenure_payload.tenure_consensus_hash
774-
|| highest_processed_tenure.tenure_id_consensus_hash
775-
!= tenure_payload.prev_tenure_consensus_hash
776-
{
777-
warn!("Invalid tenure-change: tenure extension within the same sortition tries to override the highest sortition";
778-
"tenure_consensus_hash" => %tenure_payload.tenure_consensus_hash,
779-
"prev_tenure_consensus_hash" => %tenure_payload.prev_tenure_consensus_hash,
780-
"highest_processed_tenure.consensus_hash" => %highest_processed_tenure.tenure_id_consensus_hash,
781-
"highest_processed_tenure.prev_consensus_hash" => %highest_processed_tenure.prev_tenure_id_consensus_hash
782-
);
783-
return Ok(None);
784-
}
785-
}
786778
}
787-
}
788-
789-
let Some(last_tenure_finish_block_id) = Self::get_nakamoto_tenure_finish_block_header(
790-
headers_conn,
791-
&highest_processed_tenure.tenure_id_consensus_hash,
792-
)?
793-
.map(|hdr| hdr.index_block_hash()) else {
794-
// last tenure doesn't exist (should be unreachable)
795-
warn!("Invalid tenure-change: no blocks found for highest processed tenure";
796-
"consensus_hash" => %highest_processed_tenure.tenure_id_consensus_hash,
797-
);
798-
return Ok(None);
799779
};
800780

801-
// must build atop the highest-processed tenure.
802-
// NOTE: for tenure-extensions, the second check is always false, since the tenure and
803-
// prev-tenure consensus hashes must be the same per the above check.
804-
if last_tenure_finish_block_id != tenure_payload.previous_tenure_end
805-
|| highest_processed_tenure.tenure_id_consensus_hash
806-
!= tenure_payload.prev_tenure_consensus_hash
807-
{
808-
// not continuous -- this tenure-change does not point to the end of the
809-
// last-processed tenure, or does not point to the last-processed tenure's sortition
810-
warn!("Invalid tenure-change: discontiguous";
811-
"tenure_consensus_hash" => %tenure_payload.tenure_consensus_hash,
812-
"prev_tenure_consensus_hash" => %tenure_payload.prev_tenure_consensus_hash,
813-
"highest_processed_tenure.consensus_hash" => %highest_processed_tenure.tenure_id_consensus_hash,
814-
"last_tenure_finish_block_id" => %last_tenure_finish_block_id,
815-
"tenure_payload.previous_tenure_end" => %tenure_payload.previous_tenure_end
816-
);
817-
return Ok(None);
818-
}
819-
820-
// The tenure-change must report the number of blocks _so far_ in the current tenure. If
821-
// there is a succession of tenure-extensions for a given tenure, then the reported tenure
781+
// The tenure-change must report the number of blocks _so far_ in the previous tenure (note if this is a TenureChangeCause::Extended, then its parent tenure will be its own tenure).
782+
// If there is a succession of tenure-extensions for a given tenure, then the reported tenure
822783
// length must report the number of blocks since the last _sortition-induced_ tenure
823784
// change.
824785
let tenure_len = Self::get_nakamoto_tenure_length(
825786
headers_conn,
826-
&highest_processed_tenure.tenure_id_consensus_hash,
787+
&tenure_payload.prev_tenure_consensus_hash,
827788
)?;
828789
if tenure_len != tenure_payload.previous_tenure_blocks {
829790
// invalid -- does not report the correct number of blocks in the past tenure
830791
warn!("Invalid tenure-change: wrong number of blocks";
831792
"tenure_consensus_hash" => %tenure_payload.tenure_consensus_hash,
832-
"highest_processed_tenure.consensus_hash" => %highest_processed_tenure.tenure_id_consensus_hash,
793+
"prev_tenure_consensus_hash" => %tenure_payload.prev_tenure_consensus_hash,
833794
"tenure_len" => tenure_len,
834795
"tenure_payload.previous_tenure_blocks" => tenure_payload.previous_tenure_blocks
835796
);
836797
return Ok(None);
837798
}
838799

839-
Ok(Some(highest_processed_tenure))
800+
Ok(Some(tenure))
840801
}
841802

842803
/// Advance the tenures table with a validated block's tenure data.
843804
/// This applies to both tenure-changes and tenure-extends.
844-
/// Returns the highest tenure-change height (this is parent_coinbase_height + 1 if there was a
805+
/// Returns the tenure-change height (this is parent_coinbase_height + 1 if there was a
845806
/// tenure-change tx, or just parent_coinbase_height if there was a tenure-extend tx or no tenure
846807
/// txs at all).
847808
/// TODO: unit test
@@ -869,7 +830,7 @@ impl NakamotoChainState {
869830
}
870831
};
871832

872-
let Some(highest_processed_tenure) =
833+
let Some(processed_tenure) =
873834
Self::check_nakamoto_tenure(headers_tx, sort_tx, &block.header, tenure_payload)?
874835
else {
875836
return Err(ChainstateError::InvalidStacksTransaction(
@@ -882,7 +843,7 @@ impl NakamotoChainState {
882843
headers_tx,
883844
&block.header,
884845
coinbase_height,
885-
highest_processed_tenure
846+
processed_tenure
886847
.tenure_index
887848
.checked_add(1)
888849
.expect("too many tenure-changes"),

testnet/stacks-node/src/tests/nakamoto_integrations.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3253,7 +3253,7 @@ fn forked_tenure_is_ignored() {
32533253
})
32543254
.unwrap();
32553255

3256-
let _block_tenure_a = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
3256+
let block_tenure_a = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
32573257
.unwrap()
32583258
.unwrap();
32593259

@@ -3279,6 +3279,9 @@ fn forked_tenure_is_ignored() {
32793279
// Unpause the broadcast of Tenure B's block, do not submit commits.
32803280
TEST_SKIP_COMMIT_OP.lock().unwrap().replace(true);
32813281
TEST_BROADCAST_STALL.lock().unwrap().replace(false);
3282+
let block_tenure_b = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
3283+
.unwrap()
3284+
.unwrap();
32823285

32833286
// Wait for a stacks block to be broadcasted
32843287
let start_time = Instant::now();
@@ -3305,6 +3308,22 @@ fn forked_tenure_is_ignored() {
33053308
.unwrap();
33063309

33073310
info!("Tenure C produced a block!");
3311+
let block_tenure_c = NakamotoChainState::get_canonical_block_header(chainstate.db(), &sortdb)
3312+
.unwrap()
3313+
.unwrap();
3314+
3315+
assert_ne!(block_tenure_b, block_tenure_c);
3316+
// TODO: I don't understand this. Shouldn't it be also one block past block tenure a's block height? Why is it the same as block tenure A's height?
3317+
assert_eq!(
3318+
block_tenure_b.stacks_block_height,
3319+
block_tenure_a.stacks_block_height
3320+
);
3321+
// Block C was built AFTER block A, but before block B, so it should be built off of block A
3322+
// therefore it will be one ahead of its block height
3323+
assert_eq!(
3324+
block_tenure_c.stacks_block_height,
3325+
block_tenure_a.stacks_block_height + 1
3326+
);
33083327

33093328
coord_channel
33103329
.lock()

0 commit comments

Comments
 (0)