Skip to content

Commit cf25665

Browse files
committed
Move block proposal timeout check to check_proposal and add unit tests
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 9844895 commit cf25665

File tree

5 files changed

+79
-41
lines changed

5 files changed

+79
-41
lines changed

stacks-signer/src/chainstate.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// You should have received a copy of the GNU General Public License
1414
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1515

16-
use std::time::Duration;
16+
use std::time::{Duration, UNIX_EPOCH};
1717

1818
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
1919
use blockstack_lib::chainstate::stacks::TenureChangePayload;
@@ -91,8 +91,8 @@ pub struct ProposalEvalConfig {
9191
impl From<&SignerConfig> for ProposalEvalConfig {
9292
fn from(value: &SignerConfig) -> Self {
9393
Self {
94-
first_proposal_burn_block_timing: value.first_proposal_burn_block_timing.clone(),
95-
block_proposal_timeout: value.block_proposal_timeout.clone(),
94+
first_proposal_burn_block_timing: value.first_proposal_burn_block_timing,
95+
block_proposal_timeout: value.block_proposal_timeout,
9696
}
9797
}
9898
}
@@ -150,12 +150,40 @@ impl<'a> ProposedBy<'a> {
150150
impl SortitionsView {
151151
/// Apply checks from the SortitionsView on the block proposal.
152152
pub fn check_proposal(
153-
&self,
153+
&mut self,
154154
client: &StacksClient,
155155
signer_db: &SignerDb,
156156
block: &NakamotoBlock,
157157
block_pk: &StacksPublicKey,
158158
) -> Result<bool, SignerChainstateError> {
159+
// If this is the first block in the tenure, check if it was proposed after the timeout
160+
if signer_db
161+
.get_last_signed_block_in_tenure(&block.header.consensus_hash)?
162+
.is_none()
163+
{
164+
if let Some(received_ts) =
165+
signer_db.get_burn_block_receive_time(&self.cur_sortition.burn_block_hash)?
166+
{
167+
let received_time = UNIX_EPOCH + Duration::from_secs(received_ts);
168+
let elapsed = std::time::SystemTime::now()
169+
.duration_since(received_time)
170+
.unwrap_or_else(|_| {
171+
panic!("Failed to calculate time since burn block received")
172+
});
173+
if elapsed >= self.config.block_proposal_timeout {
174+
warn!(
175+
"Miner proposed first block after block proposal timeout.";
176+
"proposed_block_consensus_hash" => %block.header.consensus_hash,
177+
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
178+
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
179+
"last_sortition_consensus_hash" => ?self.last_sortition.as_ref().map(|x| x.consensus_hash),
180+
"burn_block_received_time" => ?received_time,
181+
);
182+
self.cur_sortition.miner_status =
183+
SortitionMinerStatus::InvalidatedBeforeFirstBlock;
184+
}
185+
}
186+
}
159187
let bitvec_all_1s = block.header.pox_treatment.iter().all(|entry| entry);
160188
if !bitvec_all_1s {
161189
warn!(

stacks-signer/src/signerdb.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ impl SignerDb {
189189
})
190190
.optional();
191191
match result {
192-
Ok(x) => Ok(x.unwrap_or_else(|| 0)),
192+
Ok(x) => Ok(x.unwrap_or(0)),
193193
Err(e) => Err(DBError::from(e)),
194194
}
195195
}
@@ -294,7 +294,7 @@ impl SignerDb {
294294
tenure: &ConsensusHash,
295295
) -> Result<Option<BlockInfo>, DBError> {
296296
let query = "SELECT block_info FROM blocks WHERE consensus_hash = ? AND signed_over = 1 ORDER BY stacks_height ASC LIMIT 1";
297-
let result: Option<String> = query_row(&self.db, query, &[tenure])?;
297+
let result: Option<String> = query_row(&self.db, query, [tenure])?;
298298

299299
try_deserialize(result)
300300
}

stacks-signer/src/tests/chainstate.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ fn setup_test_environment(
8686
last_sortition,
8787
config: ProposalEvalConfig {
8888
first_proposal_burn_block_timing: Duration::from_secs(30),
89-
block_proposal_timeout: Duration::from_secs(30),
89+
block_proposal_timeout: Duration::from_secs(5),
9090
},
9191
};
9292

@@ -111,7 +111,7 @@ fn setup_test_environment(
111111
parent_block_id: StacksBlockId([0; 32]),
112112
tx_merkle_root: Sha512Trunc256Sum([0; 32]),
113113
state_index_root: TrieHash([0; 32]),
114-
timestamp: 11,
114+
timestamp: 3,
115115
miner_signature: MessageSignature::empty(),
116116
signer_signature: vec![],
117117
pox_treatment: BitVec::ones(1).unwrap(),
@@ -140,7 +140,7 @@ fn check_proposal_units() {
140140

141141
#[test]
142142
fn check_proposal_miner_pkh_mismatch() {
143-
let (stacks_client, signer_db, _block_pk, view, mut block) =
143+
let (stacks_client, signer_db, _block_pk, mut view, mut block) =
144144
setup_test_environment("miner_pkh_mismatch");
145145
block.header.consensus_hash = view.cur_sortition.consensus_hash;
146146
let different_block_pk = StacksPublicKey::from_private(&StacksPrivateKey::from_seed(&[2, 3]));
@@ -328,7 +328,7 @@ fn make_tenure_change_tx(payload: TenureChangePayload) -> StacksTransaction {
328328

329329
#[test]
330330
fn check_proposal_tenure_extend_invalid_conditions() {
331-
let (stacks_client, signer_db, block_pk, view, mut block) =
331+
let (stacks_client, signer_db, block_pk, mut view, mut block) =
332332
setup_test_environment("tenure_extend");
333333
block.header.consensus_hash = view.cur_sortition.consensus_hash;
334334
let mut extend_payload = make_tenure_change_payload();
@@ -351,3 +351,39 @@ fn check_proposal_tenure_extend_invalid_conditions() {
351351
.check_proposal(&stacks_client, &signer_db, &block, &block_pk)
352352
.unwrap());
353353
}
354+
355+
#[test]
356+
fn check_block_proposal_timeout() {
357+
let (stacks_client, mut signer_db, block_pk, mut view, mut curr_sortition_block) =
358+
setup_test_environment("block_proposal_timeout");
359+
curr_sortition_block.header.consensus_hash = view.cur_sortition.consensus_hash;
360+
let mut last_sortition_block = curr_sortition_block.clone();
361+
last_sortition_block.header.consensus_hash =
362+
view.last_sortition.as_ref().unwrap().consensus_hash;
363+
364+
// Ensure we have a burn height to compare against
365+
let burn_hash = view.cur_sortition.burn_block_hash;
366+
let burn_height = 1;
367+
let received_time = SystemTime::now();
368+
signer_db
369+
.insert_burn_block(&burn_hash, burn_height, &received_time)
370+
.unwrap();
371+
372+
assert!(view
373+
.check_proposal(&stacks_client, &signer_db, &curr_sortition_block, &block_pk)
374+
.unwrap());
375+
376+
assert!(!view
377+
.check_proposal(&stacks_client, &signer_db, &last_sortition_block, &block_pk)
378+
.unwrap());
379+
380+
// Sleep a bit to time out the block proposal
381+
std::thread::sleep(Duration::from_secs(5));
382+
assert!(!view
383+
.check_proposal(&stacks_client, &signer_db, &curr_sortition_block, &block_pk)
384+
.unwrap());
385+
386+
assert!(view
387+
.check_proposal(&stacks_client, &signer_db, &last_sortition_block, &block_pk)
388+
.unwrap());
389+
}

stacks-signer/src/v0/signer.rs

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1515
use std::fmt::Debug;
1616
use std::sync::mpsc::Sender;
17-
use std::time::{Duration, UNIX_EPOCH};
1817

1918
use blockstack_lib::net::api::postblock_proposal::BlockValidateResponse;
2019
use clarity::types::chainstate::StacksPrivateKey;
@@ -27,7 +26,7 @@ use slog::{slog_debug, slog_error, slog_info, slog_warn};
2726
use stacks_common::types::chainstate::StacksAddress;
2827
use stacks_common::{debug, error, info, warn};
2928

30-
use crate::chainstate::{ProposalEvalConfig, SortitionMinerStatus, SortitionsView};
29+
use crate::chainstate::{ProposalEvalConfig, SortitionsView};
3130
use crate::client::{SignerSlotID, StackerDB, StacksClient};
3231
use crate::config::SignerConfig;
3332
use crate::runloop::{RunLoopCommand, SignerResult};
@@ -312,33 +311,6 @@ impl Signer {
312311

313312
// Check if proposal can be rejected now if not valid against sortition view
314313
let block_response = if let Some(sortition_state) = sortition_state {
315-
// If this is the first block in the tenure, check if it was proposed after the timeout
316-
if let Ok(None) = self
317-
.signer_db
318-
.get_last_signed_block_in_tenure(&block_proposal.block.header.consensus_hash)
319-
{
320-
if let Ok(Some(received_ts)) = self
321-
.signer_db
322-
.get_burn_block_receive_time(&sortition_state.cur_sortition.burn_block_hash)
323-
{
324-
let received_time = UNIX_EPOCH + Duration::from_secs(received_ts);
325-
let elapsed = std::time::SystemTime::now()
326-
.duration_since(received_time)
327-
.unwrap_or_else(|_| {
328-
panic!("{self}: Failed to calculate time since burn block received")
329-
});
330-
if elapsed >= self.proposal_config.block_proposal_timeout {
331-
warn!(
332-
"{self}: miner proposed block after timeout.";
333-
"signer_sighash" => %signer_signature_hash,
334-
"block_id" => %block_proposal.block.block_id(),
335-
);
336-
sortition_state.cur_sortition.miner_status =
337-
SortitionMinerStatus::InvalidatedBeforeFirstBlock;
338-
}
339-
}
340-
}
341-
342314
match sortition_state.check_proposal(
343315
stacks_client,
344316
&self.signer_db,

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4655,7 +4655,8 @@ fn signer_chainstate() {
46554655
first_proposal_burn_block_timing: Duration::from_secs(0),
46564656
block_proposal_timeout: Duration::from_secs(100),
46574657
};
4658-
let sortitions_view = SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();
4658+
let mut sortitions_view =
4659+
SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();
46594660

46604661
// check the prior tenure's proposals again, confirming that the sortitions_view
46614662
// will reject them.
@@ -4769,7 +4770,8 @@ fn signer_chainstate() {
47694770
first_proposal_burn_block_timing: Duration::from_secs(0),
47704771
block_proposal_timeout: Duration::from_secs(100),
47714772
};
4772-
let sortitions_view = SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();
4773+
let mut sortitions_view =
4774+
SortitionsView::fetch_view(proposal_conf, &signer_client).unwrap();
47734775
let valid = sortitions_view
47744776
.check_proposal(
47754777
&signer_client,

0 commit comments

Comments
 (0)