Skip to content

Commit 5655256

Browse files
authored
Merge pull request #5829 from stacks-network/feat/capitulate-reorg
Feat/capitulate reorg
2 parents 14d439f + 32abc0b commit 5655256

File tree

10 files changed

+396
-54
lines changed

10 files changed

+396
-54
lines changed

clarity/src/vm/contexts.rs

+1-1
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),

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

+2-2
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

+1-1
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

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
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.
2020
- 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.
2122

2223
## [3.1.0.0.5.0]
2324

stacks-signer/src/chainstate.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ impl SortitionsView {
220220
"current_sortition_consensus_hash" => ?self.cur_sortition.consensus_hash,
221221
);
222222
self.cur_sortition.miner_status = SortitionMinerStatus::InvalidatedBeforeFirstBlock;
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+
}
223229
} else if let Some(tip) = signer_db
224230
.get_canonical_tip()
225231
.map_err(SignerChainstateError::from)?
@@ -253,6 +259,12 @@ impl SortitionsView {
253259
);
254260
self.cur_sortition.miner_status =
255261
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+
}
256268
}
257269
}
258270
}
@@ -481,7 +493,7 @@ impl SortitionsView {
481493
0
482494
};
483495
if Duration::from_secs(proposal_to_sortition)
484-
<= *first_proposal_burn_block_timing
496+
< *first_proposal_burn_block_timing
485497
{
486498
info!(
487499
"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.";
@@ -587,7 +599,7 @@ impl SortitionsView {
587599
"proposed_chain_length" => block.header.chain_length,
588600
"expected_at_least" => info.block.header.chain_length + 1,
589601
);
590-
if info.signed_group.map_or(true, |signed_time| {
602+
if info.signed_group.is_none_or(|signed_time| {
591603
signed_time + reorg_attempts_activity_timeout.as_secs() > get_epoch_time_secs()
592604
}) {
593605
// 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).

stacks-signer/src/signerdb.rs

+68-10
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ use blockstack_lib::util_lib::db::{
2727
#[cfg(any(test, feature = "testing"))]
2828
use blockstack_lib::util_lib::db::{FromColumn, FromRow};
2929
use clarity::types::chainstate::{BurnchainHeaderHash, StacksAddress};
30+
use clarity::types::Address;
31+
use libsigner::v0::messages::{RejectReason, RejectReasonPrefix};
3032
use libsigner::BlockProposal;
3133
use rusqlite::functions::FunctionFlags;
3234
use rusqlite::{
@@ -500,6 +502,11 @@ CREATE TABLE IF NOT EXISTS tenure_activity (
500502
last_activity_time INTEGER NOT NULL
501503
) STRICT;"#;
502504

505+
static ADD_REJECT_CODE: &str = r#"
506+
ALTER TABLE block_rejection_signer_addrs
507+
ADD COLUMN reject_code INTEGER;
508+
"#;
509+
503510
static SCHEMA_1: &[&str] = &[
504511
DROP_SCHEMA_0,
505512
CREATE_DB_CONFIG,
@@ -564,9 +571,14 @@ static SCHEMA_8: &[&str] = &[
564571
"INSERT INTO db_config (version) VALUES (8);",
565572
];
566573

574+
static SCHEMA_9: &[&str] = &[
575+
ADD_REJECT_CODE,
576+
"INSERT INTO db_config (version) VALUES (9);",
577+
];
578+
567579
impl SignerDb {
568580
/// The current schema version used in this build of the signer binary.
569-
pub const SCHEMA_VERSION: u32 = 8;
581+
pub const SCHEMA_VERSION: u32 = 9;
570582

571583
/// Create a new `SignerState` instance.
572584
/// This will create a new SQLite database at the given path
@@ -708,6 +720,20 @@ impl SignerDb {
708720
Ok(())
709721
}
710722

723+
/// Migrate from schema 9 to schema 9
724+
fn schema_9_migration(tx: &Transaction) -> Result<(), DBError> {
725+
if Self::get_schema_version(tx)? >= 9 {
726+
// no migration necessary
727+
return Ok(());
728+
}
729+
730+
for statement in SCHEMA_9.iter() {
731+
tx.execute_batch(statement)?;
732+
}
733+
734+
Ok(())
735+
}
736+
711737
/// Register custom scalar functions used by the database
712738
fn register_scalar_functions(&self) -> Result<(), DBError> {
713739
// Register helper function for determining if a block is a tenure change transaction
@@ -749,7 +775,8 @@ impl SignerDb {
749775
5 => Self::schema_6_migration(&sql_tx)?,
750776
6 => Self::schema_7_migration(&sql_tx)?,
751777
7 => Self::schema_8_migration(&sql_tx)?,
752-
8 => break,
778+
8 => Self::schema_9_migration(&sql_tx)?,
779+
9 => break,
753780
x => return Err(DBError::Other(format!(
754781
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}",
755782
Self::SCHEMA_VERSION,
@@ -998,27 +1025,58 @@ impl SignerDb {
9981025
&self,
9991026
block_sighash: &Sha512Trunc256Sum,
10001027
addr: &StacksAddress,
1028+
reject_reason: &RejectReason,
10011029
) -> Result<(), DBError> {
1002-
let qry = "INSERT OR REPLACE INTO block_rejection_signer_addrs (signer_signature_hash, signer_addr) VALUES (?1, ?2);";
1003-
let args = params![block_sighash, addr.to_string(),];
1030+
let qry = "INSERT OR REPLACE INTO block_rejection_signer_addrs (signer_signature_hash, signer_addr, reject_code) VALUES (?1, ?2, ?3);";
1031+
let args = params![
1032+
block_sighash,
1033+
addr.to_string(),
1034+
RejectReasonPrefix::from(reject_reason) as i64
1035+
];
10041036

10051037
debug!("Inserting block rejection.";
1006-
"block_sighash" => %block_sighash,
1007-
"signer_address" => %addr);
1038+
"block_sighash" => %block_sighash,
1039+
"signer_address" => %addr,
1040+
"reject_reason" => %reject_reason
1041+
);
10081042

10091043
self.db.execute(qry, args)?;
10101044
Ok(())
10111045
}
10121046

1013-
/// Get all signer addresses that rejected the block
1047+
/// Get all signer addresses that rejected the block (and their reject codes)
10141048
pub fn get_block_rejection_signer_addrs(
10151049
&self,
10161050
block_sighash: &Sha512Trunc256Sum,
1017-
) -> Result<Vec<StacksAddress>, DBError> {
1051+
) -> Result<Vec<(StacksAddress, RejectReasonPrefix)>, DBError> {
10181052
let qry =
1019-
"SELECT signer_addr FROM block_rejection_signer_addrs WHERE signer_signature_hash = ?1";
1053+
"SELECT signer_addr, reject_code FROM block_rejection_signer_addrs WHERE signer_signature_hash = ?1";
10201054
let args = params![block_sighash];
1021-
query_rows(&self.db, qry, args)
1055+
let mut stmt = self.db.prepare(qry)?;
1056+
1057+
let rows = stmt.query_map(args, |row| {
1058+
let addr: String = row.get(0)?;
1059+
let addr = StacksAddress::from_string(&addr).ok_or(SqliteError::InvalidColumnType(
1060+
0,
1061+
"signer_addr".into(),
1062+
rusqlite::types::Type::Text,
1063+
))?;
1064+
let reject_code: i64 = row.get(1)?;
1065+
1066+
let reject_code = u8::try_from(reject_code)
1067+
.map_err(|_| {
1068+
SqliteError::InvalidColumnType(
1069+
1,
1070+
"reject_code".into(),
1071+
rusqlite::types::Type::Integer,
1072+
)
1073+
})
1074+
.map(RejectReasonPrefix::from)?;
1075+
1076+
Ok((addr, reject_code))
1077+
})?;
1078+
1079+
rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
10221080
}
10231081

10241082
/// Mark a block as having been broadcasted and therefore GloballyAccepted

stacks-signer/src/tests/chainstate.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ fn check_proposal_reorg_timing_bad() {
287287

288288
#[test]
289289
fn check_proposal_reorg_timing_ok() {
290-
let result = reorg_timing_testing("reorg_timing_okay", 30, 30);
290+
let result = reorg_timing_testing("reorg_timing_okay", 30, 29);
291291
result.expect("Proposal should validate okay, because the reorg occurred in a block whose proposed time was close to the sortition");
292292
}
293293

0 commit comments

Comments
 (0)