Skip to content

Commit 7f9b0e0

Browse files
committed
Merge branch 'fix/signers-verify-reward-cycle' of https://github.com/stacks-network/stacks-core into fix/signers-verify-reward-cycle
2 parents 644094d + 2099df4 commit 7f9b0e0

File tree

5 files changed

+62
-115
lines changed

5 files changed

+62
-115
lines changed

stacks-signer/src/chainstate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ impl SortitionsView {
539539
///
540540
/// The rationale here is that the signer DB can be out-of-sync with the node. For example,
541541
/// the signer may have been added to an already-running node.
542-
fn check_tenure_change_confirms_parent(
542+
pub fn check_tenure_change_confirms_parent(
543543
tenure_change: &TenureChangePayload,
544544
block: &NakamotoBlock,
545545
signer_db: &mut SignerDb,

stacks-signer/src/signerdb.rs

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -733,18 +733,6 @@ impl SignerDb {
733733
try_deserialize(result)
734734
}
735735

736-
/// Return the last accepted block the signer (highest stacks height). It will tie break a match based on which was more recently signed.
737-
pub fn get_signer_last_accepted_block(&self) -> Result<Option<BlockInfo>, DBError> {
738-
let query = "SELECT block_info FROM blocks WHERE state IN (?1, ?2) ORDER BY stacks_height DESC, signed_group DESC, signed_self DESC LIMIT 1";
739-
let args = params![
740-
&BlockState::GloballyAccepted.to_string(),
741-
&BlockState::LocallyAccepted.to_string()
742-
];
743-
let result: Option<String> = query_row(&self.db, query, args)?;
744-
745-
try_deserialize(result)
746-
}
747-
748736
/// Return the last accepted block in a tenure (identified by its consensus hash).
749737
pub fn get_last_accepted_block(
750738
&self,
@@ -1746,69 +1734,4 @@ mod tests {
17461734
< block_infos[0].proposed_time
17471735
);
17481736
}
1749-
1750-
#[test]
1751-
fn signer_last_accepted_block() {
1752-
let db_path = tmp_db_path();
1753-
let mut db = SignerDb::new(db_path).expect("Failed to create signer db");
1754-
1755-
let (mut block_info_1, _block_proposal_1) = create_block_override(|b| {
1756-
b.block.header.miner_signature = MessageSignature([0x01; 65]);
1757-
b.block.header.chain_length = 1;
1758-
b.burn_height = 1;
1759-
});
1760-
1761-
let (mut block_info_2, _block_proposal_2) = create_block_override(|b| {
1762-
b.block.header.miner_signature = MessageSignature([0x02; 65]);
1763-
b.block.header.chain_length = 2;
1764-
b.burn_height = 1;
1765-
});
1766-
1767-
let (mut block_info_3, _block_proposal_3) = create_block_override(|b| {
1768-
b.block.header.miner_signature = MessageSignature([0x02; 65]);
1769-
b.block.header.chain_length = 2;
1770-
b.burn_height = 4;
1771-
});
1772-
block_info_3
1773-
.mark_locally_accepted(false)
1774-
.expect("Failed to mark block as locally accepted");
1775-
1776-
db.insert_block(&block_info_1)
1777-
.expect("Unable to insert block into db");
1778-
db.insert_block(&block_info_2)
1779-
.expect("Unable to insert block into db");
1780-
1781-
assert!(db.get_signer_last_accepted_block().unwrap().is_none());
1782-
1783-
block_info_1
1784-
.mark_globally_accepted()
1785-
.expect("Failed to mark block as globally accepted");
1786-
db.insert_block(&block_info_1)
1787-
.expect("Unable to insert block into db");
1788-
1789-
assert_eq!(
1790-
db.get_signer_last_accepted_block().unwrap().unwrap(),
1791-
block_info_1
1792-
);
1793-
1794-
block_info_2
1795-
.mark_globally_accepted()
1796-
.expect("Failed to mark block as globally accepted");
1797-
block_info_2.signed_self = Some(get_epoch_time_secs());
1798-
db.insert_block(&block_info_2)
1799-
.expect("Unable to insert block into db");
1800-
1801-
assert_eq!(
1802-
db.get_signer_last_accepted_block().unwrap().unwrap(),
1803-
block_info_2
1804-
);
1805-
1806-
db.insert_block(&block_info_3)
1807-
.expect("Unable to insert block into db");
1808-
1809-
assert_eq!(
1810-
db.get_signer_last_accepted_block().unwrap().unwrap(),
1811-
block_info_3
1812-
);
1813-
}
18141737
}

stacks-signer/src/v0/signer.rs

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -589,60 +589,80 @@ impl Signer {
589589
}
590590
}
591591

592-
/// WARNING: Do NOT call this function PRIOR to check_proposal or block_proposal validation succeeds.
592+
/// WARNING: This is an incomplete check. Do NOT call this function PRIOR to check_proposal or block_proposal validation succeeds.
593593
///
594594
/// Re-verify a block's chain length against the last signed block within signerdb.
595595
/// This is required in case a block has been approved since the initial checks of the block validation endpoint.
596596
fn check_block_against_signer_db_state(
597-
&self,
597+
&mut self,
598+
stacks_client: &StacksClient,
598599
proposed_block: &NakamotoBlock,
599600
) -> Option<BlockResponse> {
600601
let signer_signature_hash = proposed_block.header.signer_signature_hash();
601602
let proposed_block_consensus_hash = proposed_block.header.consensus_hash;
603+
// If this is a tenure change block, ensure that it confirms the correct number of blocks from the parent tenure.
604+
if let Some(tenure_change) = proposed_block.get_tenure_change_tx_payload() {
605+
// Ensure that the tenure change block confirms the expected parent block
606+
match SortitionsView::check_tenure_change_confirms_parent(
607+
tenure_change,
608+
proposed_block,
609+
&mut self.signer_db,
610+
stacks_client,
611+
self.proposal_config.tenure_last_block_proposal_timeout,
612+
) {
613+
Ok(true) => {}
614+
Ok(false) => {
615+
return Some(
616+
self.create_block_rejection(
617+
RejectCode::SortitionViewMismatch,
618+
proposed_block,
619+
),
620+
)
621+
}
622+
Err(e) => {
623+
warn!("{self}: Error checking block proposal: {e}";
624+
"signer_sighash" => %signer_signature_hash,
625+
"block_id" => %proposed_block.block_id()
626+
);
627+
return Some(
628+
self.create_block_rejection(RejectCode::ConnectivityIssues, proposed_block),
629+
);
630+
}
631+
}
632+
}
602633

603-
match self.signer_db.get_signer_last_accepted_block() {
634+
// Ensure that the block is the last block in the chain of its current tenure.
635+
match self
636+
.signer_db
637+
.get_last_accepted_block(&proposed_block_consensus_hash)
638+
{
604639
Ok(Some(last_block_info)) => {
605640
if proposed_block.header.chain_length <= last_block_info.block.header.chain_length {
606-
// We do not allow reorgs at any time within the same consensus hash OR of globally accepted blocks
607-
let non_reorgable_block = last_block_info.block.header.consensus_hash
608-
== proposed_block_consensus_hash
609-
|| last_block_info.state == BlockState::GloballyAccepted;
610-
// Is the reorg timeout requirement exceeded?
611-
let reorg_timeout_exceeded = last_block_info
612-
.signed_self
613-
.map(|signed_over_time| {
614-
signed_over_time.saturating_add(
615-
self.proposal_config
616-
.tenure_last_block_proposal_timeout
617-
.as_secs(),
618-
) <= get_epoch_time_secs()
619-
})
620-
.unwrap_or(false);
621-
if non_reorgable_block || !reorg_timeout_exceeded {
622-
warn!(
623-
"Miner's block proposal does not confirm as many blocks as we expect";
624-
"proposed_block_consensus_hash" => %proposed_block_consensus_hash,
625-
"proposed_block_signer_sighash" => %signer_signature_hash,
626-
"proposed_chain_length" => proposed_block.header.chain_length,
627-
"expected_at_least" => last_block_info.block.header.chain_length + 1,
628-
);
629-
return Some(self.create_block_rejection(
630-
RejectCode::SortitionViewMismatch,
631-
proposed_block,
632-
));
633-
}
641+
warn!(
642+
"Miner's block proposal does not confirm as many blocks as we expect";
643+
"proposed_block_consensus_hash" => %proposed_block_consensus_hash,
644+
"proposed_block_signer_sighash" => %signer_signature_hash,
645+
"proposed_chain_length" => proposed_block.header.chain_length,
646+
"expected_at_least" => last_block_info.block.header.chain_length + 1,
647+
);
648+
return Some(self.create_block_rejection(
649+
RejectCode::SortitionViewMismatch,
650+
proposed_block,
651+
));
634652
}
635-
None
636653
}
637-
Ok(_) => None,
654+
Ok(_) => {}
638655
Err(e) => {
639656
warn!("{self}: Failed to check block against signer db: {e}";
640657
"signer_sighash" => %signer_signature_hash,
641658
"block_id" => %proposed_block.block_id()
642659
);
643-
Some(self.create_block_rejection(RejectCode::ConnectivityIssues, proposed_block))
660+
return Some(
661+
self.create_block_rejection(RejectCode::ConnectivityIssues, proposed_block),
662+
);
644663
}
645664
}
665+
None
646666
}
647667

648668
/// Handle the block validate ok response. Returns our block response if we have one
@@ -674,7 +694,9 @@ impl Signer {
674694
return None;
675695
}
676696

677-
if let Some(block_response) = self.check_block_against_signer_db_state(&block_info.block) {
697+
if let Some(block_response) =
698+
self.check_block_against_signer_db_state(stacks_client, &block_info.block)
699+
{
678700
// The signer db state has changed. We no longer view this block as valid. Override the validation response.
679701
if let Err(e) = block_info.mark_locally_rejected() {
680702
if !block_info.has_reached_consensus() {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8850,7 +8850,8 @@ fn mock_mining() {
88508850

88518851
info!("Booting follower-thread, waiting for the follower to sync to the chain tip");
88528852

8853-
wait_for(120, || {
8853+
// use a high timeout for avoiding problem with github workflow
8854+
wait_for(600, || {
88548855
let Some(miner_node_info) = get_chain_info_opt(&naka_conf) else {
88558856
return Ok(false);
88568857
};

testnet/stacks-node/src/tests/signer/v0.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4974,7 +4974,8 @@ fn partial_tenure_fork() {
49744974
info!("-------- Waiting miner 2 to catch up to miner 1 --------");
49754975

49764976
// Wait for miner 2 to catch up to miner 1
4977-
wait_for(60, || {
4977+
// (note: use a high timeout to avoid potential failing on github workflow)
4978+
wait_for(600, || {
49784979
let info_1 = get_chain_info(&conf);
49794980
let info_2 = get_chain_info(&conf_node_2);
49804981
Ok(info_1.stacks_tip_height == info_2.stacks_tip_height)

0 commit comments

Comments
 (0)