Skip to content

Commit 8475ca7

Browse files
authored
Merge branch 'develop' into fix/tenure-extend-with-other-transactions
2 parents c7286c4 + 5655256 commit 8475ca7

File tree

15 files changed

+993
-316
lines changed

15 files changed

+993
-316
lines changed

clarity/src/vm/contexts.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1639,7 +1639,7 @@ impl<'a, 'hooks> GlobalContext<'a, 'hooks> {
16391639
);
16401640
f(&mut exec_env)
16411641
};
1642-
self.roll_back().map_err(crate::vm::errors::Error::from)?;
1642+
self.roll_back()?;
16431643

16441644
match result {
16451645
Ok(return_value) => Ok(return_value),

contrib/tools/block-replay.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ configure_replay_slices() {
117117

118118
## create a copy of the linked db with <number of CORES><number of RESERVED CORES>
119119
## decrement by 1 since we already have ${SLICE_DIR}0
120-
for ((i=1;i<=$( CORES - RESERVED - 1);i++)); do
120+
for ((i=1;i<=$(( CORES - RESERVED - 1));i++)); do
121121
echo "Copying ${SLICE_DIR}0 -> ${COLYELLOW}${SLICE_DIR}${i}${COLRESET}"
122122
cp -R "${SLICE_DIR}0" "${SLICE_DIR}${i}" || {
123123
echo "${COLRED}Error${COLRESET} copying ${SLICE_DIR}0 -> ${SLICE_DIR}${i}"

libsigner/src/v0/messages.rs

Lines changed: 382 additions & 34 deletions
Large diffs are not rendered by default.

stacks-common/src/deps_common/bech32/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl<'a> Bech32Writer<'a> {
168168

169169
fn polymod_step(&mut self, v: u5) {
170170
let b = (self.chk >> 25) as u8;
171-
self.chk = (self.chk & 0x01ff_ffff) << 5 ^ (u32::from(*v.as_ref()));
171+
self.chk = ((self.chk & 0x01ff_ffff) << 5) ^ (u32::from(*v.as_ref()));
172172

173173
for (i, item) in GEN.iter().enumerate() {
174174
if (b >> i) & 1 == 1 {
@@ -616,7 +616,7 @@ fn polymod(values: &[u5]) -> u32 {
616616
let mut b: u8;
617617
for v in values {
618618
b = (chk >> 25) as u8;
619-
chk = (chk & 0x01ff_ffff) << 5 ^ (u32::from(*v.as_ref()));
619+
chk = ((chk & 0x01ff_ffff) << 5) ^ (u32::from(*v.as_ref()));
620620

621621
for (i, item) in GEN.iter().enumerate() {
622622
if (b >> i) & 1 == 1 {

stacks-common/src/util/uint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ mod tests {
733733
#[test]
734734
pub fn hex_codec() {
735735
let init =
736-
Uint256::from_u64(0xDEADBEEFDEADBEEF) << 64 | Uint256::from_u64(0x0102030405060708);
736+
(Uint256::from_u64(0xDEADBEEFDEADBEEF) << 64) | Uint256::from_u64(0x0102030405060708);
737737

738738
// little-endian representation
739739
let hex_init = "0807060504030201efbeaddeefbeadde00000000000000000000000000000000";

stacks-signer/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1717
- Increase default `block_proposal_timeout_ms` from 10 minutes to 4 hours. Until #5729 is implemented, there is no value in rejecting a late block from a miner, since a late block is better than no block at all.
1818
- Signers no longer view any block proposal by a miner in their DB as indicative of valid miner activity.
1919
- Various index improvements to the signer's database to improve performance.
20+
- Add new reject codes to the signer response for better visibility into why a block was rejected.
21+
- When allowing a reorg within the `reorg_attempts_activity_timeout_ms`, the signer will now watch the responses from other signers and if >30% of them reject this reorg attempt, then the signer will mark the miner as invalid, reject further attempts to reorg and allow the previous miner to extend their tenure.
2022

2123
## [3.1.0.0.5.0]
2224

stacks-signer/src/chainstate.rs

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
1919
use blockstack_lib::chainstate::stacks::TenureChangePayload;
2020
use blockstack_lib::net::api::getsortition::SortitionInfo;
2121
use blockstack_lib::util_lib::db::Error as DBError;
22+
use libsigner::v0::messages::RejectReason;
2223
use slog::{slog_info, slog_warn};
2324
use stacks_common::types::chainstate::{BurnchainHeaderHash, ConsensusHash, StacksPublicKey};
2425
use stacks_common::util::get_epoch_time_secs;
@@ -40,6 +41,12 @@ pub enum SignerChainstateError {
4041
ClientError(#[from] ClientError),
4142
}
4243

44+
impl From<SignerChainstateError> for RejectReason {
45+
fn from(error: SignerChainstateError) -> Self {
46+
RejectReason::ConnectivityIssues(error.to_string())
47+
}
48+
}
49+
4350
/// Captures this signer's current view of a sortition's miner.
4451
#[derive(PartialEq, Eq, Debug)]
4552
pub enum SortitionMinerStatus {
@@ -201,7 +208,7 @@ impl SortitionsView {
201208
block: &NakamotoBlock,
202209
block_pk: &StacksPublicKey,
203210
reset_view_if_wrong_consensus_hash: bool,
204-
) -> Result<bool, SignerChainstateError> {
211+
) -> Result<(), RejectReason> {
205212
if self
206213
.cur_sortition
207214
.is_timed_out(self.config.block_proposal_timeout, signer_db)?
@@ -213,7 +220,16 @@ impl SortitionsView {
213220
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
214221
);
215222
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+
{
217233
// Check if the current sortition is aligned with the expected tenure:
218234
// - If the tip is in the current tenure, we are in the process of mining this tenure.
219235
// - If the tip is not in the current tenure, then we’re starting a new tenure,
@@ -243,6 +259,12 @@ impl SortitionsView {
243259
);
244260
self.cur_sortition.miner_status =
245261
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+
}
246268
}
247269
}
248270
}
@@ -266,7 +288,7 @@ impl SortitionsView {
266288
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
267289
"last_sortition_consensus_hash" => ?self.last_sortition.as_ref().map(|x| x.consensus_hash),
268290
);
269-
return Ok(false);
291+
return Err(RejectReason::InvalidBitvec);
270292
}
271293

272294
let block_pkh = Hash160::from_data(&block_pk.to_bytes_compressed());
@@ -293,7 +315,8 @@ impl SortitionsView {
293315
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
294316
"last_sortition_consensus_hash" => ?self.last_sortition.as_ref().map(|x| x.consensus_hash),
295317
);
296-
self.reset_view(client)?;
318+
self.reset_view(client)
319+
.map_err(SignerChainstateError::from)?;
297320
return self.check_proposal(client, signer_db, block, block_pk, false);
298321
}
299322
warn!(
@@ -303,7 +326,7 @@ impl SortitionsView {
303326
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
304327
"last_sortition_consensus_hash" => ?self.last_sortition.as_ref().map(|x| x.consensus_hash),
305328
);
306-
return Ok(false);
329+
return Err(RejectReason::SortitionViewMismatch);
307330
};
308331

309332
if proposed_by.state().miner_pkh != block_pkh {
@@ -315,7 +338,7 @@ impl SortitionsView {
315338
"proposed_block_pubkey_hash" => %block_pkh,
316339
"sortition_winner_pubkey_hash" => %proposed_by.state().miner_pkh,
317340
);
318-
return Ok(false);
341+
return Err(RejectReason::PubkeyHashMismatch);
319342
}
320343

321344
// check that this miner is the most recent sortition
@@ -327,7 +350,7 @@ impl SortitionsView {
327350
"proposed_block_consensus_hash" => %block.header.consensus_hash,
328351
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
329352
);
330-
return Ok(false);
353+
return Err(RejectReason::InvalidMiner);
331354
}
332355
}
333356
ProposedBy::LastSortition(last_sortition) => {
@@ -343,27 +366,26 @@ impl SortitionsView {
343366
"current_sortition_miner_status" => ?self.cur_sortition.miner_status,
344367
"last_sortition" => %last_sortition.consensus_hash
345368
);
346-
return Ok(false);
369+
return Err(RejectReason::NotLatestSortitionWinner);
347370
}
348371
}
349372
};
350373

351374
if let Some(tenure_change) = block.get_tenure_change_tx_payload() {
352-
if !self.validate_tenure_change_payload(
375+
self.validate_tenure_change_payload(
353376
&proposed_by,
354377
tenure_change,
355378
block,
356379
signer_db,
357380
client,
358-
)? {
359-
return Ok(false);
360-
}
381+
)?;
361382
} else {
362383
// check if the new block confirms the last block in the current tenure
363384
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)?;
365387
if !confirms_latest_in_tenure {
366-
return Ok(false);
388+
return Err(RejectReason::InvalidParentBlock);
367389
}
368390
}
369391

@@ -389,11 +411,11 @@ impl SortitionsView {
389411
"extend_timestamp" => extend_timestamp,
390412
"epoch_time" => epoch_time,
391413
);
392-
return Ok(false);
414+
return Err(RejectReason::InvalidTenureExtend);
393415
}
394416
}
395417

396-
Ok(true)
418+
Ok(())
397419
}
398420

399421
fn check_parent_tenure_choice(
@@ -471,7 +493,7 @@ impl SortitionsView {
471493
0
472494
};
473495
if Duration::from_secs(proposal_to_sortition)
474-
<= *first_proposal_burn_block_timing
496+
< *first_proposal_burn_block_timing
475497
{
476498
info!(
477499
"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 {
577599
"proposed_chain_length" => block.header.chain_length,
578600
"expected_at_least" => info.block.header.chain_length + 1,
579601
);
580-
if info.signed_group.map_or(true, |signed_time| {
602+
if info.signed_group.is_none_or(|signed_time| {
581603
signed_time + reorg_attempts_activity_timeout.as_secs() > get_epoch_time_secs()
582604
}) {
583605
// 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 {
649671
block: &NakamotoBlock,
650672
signer_db: &mut SignerDb,
651673
client: &StacksClient,
652-
) -> Result<bool, SignerChainstateError> {
674+
) -> Result<(), RejectReason> {
653675
// Ensure that the tenure change block confirms the expected parent block
654676
let confirms_expected_parent = Self::check_tenure_change_confirms_parent(
655677
tenure_change,
@@ -658,9 +680,10 @@ impl SortitionsView {
658680
client,
659681
self.config.tenure_last_block_proposal_timeout,
660682
self.config.reorg_attempts_activity_timeout,
661-
)?;
683+
)
684+
.map_err(SignerChainstateError::from)?;
662685
if !confirms_expected_parent {
663-
return Ok(false);
686+
return Err(RejectReason::InvalidParentBlock);
664687
}
665688
// now, we have to check if the parent tenure was a valid choice.
666689
let is_valid_parent_tenure = Self::check_parent_tenure_choice(
@@ -671,21 +694,23 @@ impl SortitionsView {
671694
&self.config.first_proposal_burn_block_timing,
672695
)?;
673696
if !is_valid_parent_tenure {
674-
return Ok(false);
697+
return Err(RejectReason::ReorgNotAllowed);
675698
}
676699
let last_in_current_tenure = signer_db
677700
.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+
})?;
679704
if let Some(last_in_current_tenure) = last_in_current_tenure {
680705
warn!(
681706
"Miner block proposal contains a tenure change, but we've already signed a block in this tenure. Considering proposal invalid.";
682707
"proposed_block_consensus_hash" => %block.header.consensus_hash,
683708
"proposed_block_signer_sighash" => %block.header.signer_signature_hash(),
684709
"last_in_tenure_signer_sighash" => %last_in_current_tenure.block.header.signer_signature_hash(),
685710
);
686-
return Ok(false);
711+
return Err(RejectReason::DuplicateBlockFound);
687712
}
688-
Ok(true)
713+
Ok(())
689714
}
690715

691716
fn confirms_latest_block_in_same_tenure(

stacks-signer/src/client/stackerdb.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ mod tests {
281281
use clarity::util::hash::{MerkleTree, Sha512Trunc256Sum};
282282
use clarity::util::secp256k1::MessageSignature;
283283
use libsigner::v0::messages::{
284-
BlockRejection, BlockResponse, BlockResponseData, RejectCode, SignerMessage,
284+
BlockRejection, BlockResponse, BlockResponseData, RejectCode, RejectReason, SignerMessage,
285285
SignerMessageMetadata,
286286
};
287287
use rand::{thread_rng, RngCore};
@@ -332,7 +332,10 @@ mod tests {
332332
chain_id: thread_rng().next_u32(),
333333
signature: MessageSignature::empty(),
334334
metadata: SignerMessageMetadata::empty(),
335-
response_data: BlockResponseData::new(thread_rng().next_u64()),
335+
response_data: BlockResponseData::new(
336+
thread_rng().next_u64(),
337+
RejectReason::RejectedInPriorRound,
338+
),
336339
};
337340
let signer_message = SignerMessage::BlockResponse(BlockResponse::Rejected(block_reject));
338341
let ack = StackerDBChunkAckData {

0 commit comments

Comments
 (0)