Skip to content

Commit 0e9b2cf

Browse files
committed
Merge branch 'develop' of https://github.com/stacks-network/stacks-core into fix/mark-any-miners-proposal-towards-activity
2 parents c197579 + 6621956 commit 0e9b2cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1484
-878
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ jobs:
132132
- tests::signer::v0::block_commit_delay
133133
- tests::signer::v0::continue_after_fast_block_no_sortition
134134
- tests::signer::v0::block_validation_response_timeout
135+
- tests::signer::v0::block_validation_pending_table
136+
- tests::signer::v0::new_tenure_while_validating_previous_scenario
135137
- tests::signer::v0::tenure_extend_after_bad_commit
136138
- tests::signer::v0::block_proposal_max_age_rejections
137139
- tests::signer::v0::global_acceptance_depends_on_block_announcement

clarity/src/vm/representations.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ guarded_string!(
8484
);
8585

8686
impl StacksMessageCodec for ClarityName {
87+
#[allow(clippy::needless_as_bytes)] // as_bytes isn't necessary, but verbosity is preferable in the codec impls
8788
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), codec_error> {
8889
// ClarityName can't be longer than vm::representations::MAX_STRING_LEN, which itself is
8990
// a u8, so we should be good here.
@@ -124,6 +125,7 @@ impl StacksMessageCodec for ClarityName {
124125
}
125126

126127
impl StacksMessageCodec for ContractName {
128+
#[allow(clippy::needless_as_bytes)] // as_bytes isn't necessary, but verbosity is preferable in the codec impls
127129
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), codec_error> {
128130
if self.as_bytes().len() < CONTRACT_MIN_NAME_LENGTH
129131
|| self.as_bytes().len() > CONTRACT_MAX_NAME_LENGTH

libsigner/src/v0/messages.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ pub struct PeerInfo {
283283
}
284284

285285
impl StacksMessageCodec for PeerInfo {
286+
#[allow(clippy::needless_as_bytes)] // as_bytes isn't necessary, but verbosity is preferable in the codec impls
286287
fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
287288
write_next(fd, &self.burn_block_height)?;
288289
write_next(fd, self.stacks_tip_consensus_hash.as_bytes())?;
@@ -687,6 +688,14 @@ impl BlockResponse {
687688
}
688689
}
689690

691+
/// The signer signature hash for the block response
692+
pub fn signer_signature_hash(&self) -> Sha512Trunc256Sum {
693+
match self {
694+
BlockResponse::Accepted(accepted) => accepted.signer_signature_hash,
695+
BlockResponse::Rejected(rejection) => rejection.signer_signature_hash,
696+
}
697+
}
698+
690699
/// Get the block accept data from the block response
691700
pub fn as_block_accepted(&self) -> Option<&BlockAccepted> {
692701
match self {

pox-locking/src/events.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ use clarity::vm::costs::LimitedCostTracker;
2020
use clarity::vm::errors::Error as ClarityError;
2121
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, ResponseData, TupleData};
2222
use clarity::vm::Value;
23-
#[cfg(test)]
23+
#[cfg(any(test, feature = "testing"))]
2424
use slog::slog_debug;
2525
use slog::slog_error;
26-
#[cfg(test)]
26+
#[cfg(any(test, feature = "testing"))]
2727
use stacks_common::debug;
2828
use stacks_common::types::StacksEpochId;
2929
use stacks_common::{error, test_debug};

pox-locking/src/events_24.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ use clarity::vm::contexts::GlobalContext;
1919
use clarity::vm::errors::Error as ClarityError;
2020
use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier, TupleData};
2121
use clarity::vm::Value;
22-
#[cfg(test)]
22+
#[cfg(any(test, feature = "testing"))]
2323
use slog::slog_debug;
2424
use slog::slog_error;
25-
#[cfg(test)]
25+
#[cfg(any(test, feature = "testing"))]
2626
use stacks_common::debug;
2727
use stacks_common::{error, test_debug};
2828

stacks-signer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1010
## Added
1111

1212
- Introduced the `block_proposal_max_age_secs` configuration option for signers, enabling them to automatically ignore block proposals that exceed the specified age in seconds.
13+
- When a new block proposal is received while the signer is waiting for an existing proposal to be validated, the signer will wait until the existing block is done validating before submitting the new one for validating. ([#5453](https://github.com/stacks-network/stacks-core/pull/5453))
1314

1415
## Changed
1516
- Improvements to the stale signer cleanup logic: deletes the prior signer if it has no remaining unprocessed blocks in its database

stacks-signer/src/chainstate.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,8 @@ impl SortitionsView {
587587
signer_db.block_lookup(&nakamoto_tip.signer_signature_hash())
588588
{
589589
if block_info.state != BlockState::GloballyAccepted {
590-
if let Err(e) = block_info.mark_globally_accepted() {
591-
warn!("Failed to update block info in db: {e}");
590+
if let Err(e) = signer_db.mark_block_globally_accepted(&mut block_info) {
591+
warn!("Failed to mark block as globally accepted: {e}");
592592
} else if let Err(e) = signer_db.insert_block(&block_info) {
593593
warn!("Failed to update block info in db: {e}");
594594
}

stacks-signer/src/signerdb.rs

Lines changed: 178 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ use blockstack_lib::util_lib::db::{
2424
query_row, query_rows, sqlite_open, table_exists, tx_begin_immediate, u64_to_sql,
2525
Error as DBError,
2626
};
27+
#[cfg(any(test, feature = "testing"))]
28+
use blockstack_lib::util_lib::db::{FromColumn, FromRow};
2729
use clarity::types::chainstate::{BurnchainHeaderHash, StacksAddress};
2830
use libsigner::BlockProposal;
2931
use rusqlite::functions::FunctionFlags;
@@ -209,7 +211,7 @@ impl BlockInfo {
209211

210212
/// Mark this block as valid, signed over, and records a group timestamp in the block info if it wasn't
211213
/// already set.
212-
pub fn mark_globally_accepted(&mut self) -> Result<(), String> {
214+
fn mark_globally_accepted(&mut self) -> Result<(), String> {
213215
self.move_to(BlockState::GloballyAccepted)?;
214216
self.valid = Some(true);
215217
self.signed_over = true;
@@ -225,7 +227,7 @@ impl BlockInfo {
225227
}
226228

227229
/// Mark the block as globally rejected and invalid
228-
pub fn mark_globally_rejected(&mut self) -> Result<(), String> {
230+
fn mark_globally_rejected(&mut self) -> Result<(), String> {
229231
self.move_to(BlockState::GloballyRejected)?;
230232
self.valid = Some(false);
231233
Ok(())
@@ -342,6 +344,10 @@ CREATE INDEX IF NOT EXISTS blocks_state ON blocks (state);
342344
CREATE INDEX IF NOT EXISTS blocks_signed_group ON blocks (signed_group);
343345
"#;
344346

347+
static CREATE_INDEXES_6: &str = r#"
348+
CREATE INDEX IF NOT EXISTS block_validations_pending_on_added_time ON block_validations_pending(added_time ASC);
349+
"#;
350+
345351
static CREATE_SIGNER_STATE_TABLE: &str = "
346352
CREATE TABLE IF NOT EXISTS signer_states (
347353
reward_cycle INTEGER PRIMARY KEY,
@@ -436,23 +442,23 @@ INSERT INTO temp_blocks (
436442
broadcasted,
437443
stacks_height,
438444
burn_block_height,
439-
valid,
445+
valid,
440446
state,
441-
signed_group,
447+
signed_group,
442448
signed_self,
443449
proposed_time,
444450
validation_time_ms,
445451
tenure_change
446452
)
447-
SELECT
453+
SELECT
448454
signer_signature_hash,
449455
reward_cycle,
450456
block_info,
451457
consensus_hash,
452458
signed_over,
453459
broadcasted,
454460
stacks_height,
455-
burn_block_height,
461+
burn_block_height,
456462
json_extract(block_info, '$.valid') AS valid,
457463
json_extract(block_info, '$.state') AS state,
458464
json_extract(block_info, '$.signed_group') AS signed_group,
@@ -466,6 +472,14 @@ DROP TABLE blocks;
466472
467473
ALTER TABLE temp_blocks RENAME TO blocks;"#;
468474

475+
static CREATE_BLOCK_VALIDATION_PENDING_TABLE: &str = r#"
476+
CREATE TABLE IF NOT EXISTS block_validations_pending (
477+
signer_signature_hash TEXT NOT NULL,
478+
-- the time at which the block was added to the pending table
479+
added_time INTEGER NOT NULL,
480+
PRIMARY KEY (signer_signature_hash)
481+
) STRICT;"#;
482+
469483
static SCHEMA_1: &[&str] = &[
470484
DROP_SCHEMA_0,
471485
CREATE_DB_CONFIG,
@@ -514,9 +528,15 @@ static SCHEMA_5: &[&str] = &[
514528
"INSERT INTO db_config (version) VALUES (5);",
515529
];
516530

531+
static SCHEMA_6: &[&str] = &[
532+
CREATE_BLOCK_VALIDATION_PENDING_TABLE,
533+
CREATE_INDEXES_6,
534+
"INSERT OR REPLACE INTO db_config (version) VALUES (6);",
535+
];
536+
517537
impl SignerDb {
518538
/// The current schema version used in this build of the signer binary.
519-
pub const SCHEMA_VERSION: u32 = 5;
539+
pub const SCHEMA_VERSION: u32 = 6;
520540

521541
/// Create a new `SignerState` instance.
522542
/// This will create a new SQLite database at the given path
@@ -616,6 +636,20 @@ impl SignerDb {
616636
Ok(())
617637
}
618638

639+
/// Migrate from schema 5 to schema 6
640+
fn schema_6_migration(tx: &Transaction) -> Result<(), DBError> {
641+
if Self::get_schema_version(tx)? >= 6 {
642+
// no migration necessary
643+
return Ok(());
644+
}
645+
646+
for statement in SCHEMA_6.iter() {
647+
tx.execute_batch(statement)?;
648+
}
649+
650+
Ok(())
651+
}
652+
619653
/// Register custom scalar functions used by the database
620654
fn register_scalar_functions(&self) -> Result<(), DBError> {
621655
// Register helper function for determining if a block is a tenure change transaction
@@ -654,7 +688,8 @@ impl SignerDb {
654688
2 => Self::schema_3_migration(&sql_tx)?,
655689
3 => Self::schema_4_migration(&sql_tx)?,
656690
4 => Self::schema_5_migration(&sql_tx)?,
657-
5 => break,
691+
5 => Self::schema_6_migration(&sql_tx)?,
692+
6 => break,
658693
x => return Err(DBError::Other(format!(
659694
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}",
660695
Self::SCHEMA_VERSION,
@@ -958,6 +993,43 @@ impl SignerDb {
958993
Ok(Some(broadcasted))
959994
}
960995

996+
/// Get a pending block validation, sorted by the time at which it was added to the pending table.
997+
/// If found, remove it from the pending table.
998+
pub fn get_and_remove_pending_block_validation(
999+
&self,
1000+
) -> Result<Option<Sha512Trunc256Sum>, DBError> {
1001+
let qry = "DELETE FROM block_validations_pending WHERE signer_signature_hash = (SELECT signer_signature_hash FROM block_validations_pending ORDER BY added_time ASC LIMIT 1) RETURNING signer_signature_hash";
1002+
let args = params![];
1003+
let mut stmt = self.db.prepare(qry)?;
1004+
let sighash: Option<String> = stmt.query_row(args, |row| row.get(0)).optional()?;
1005+
Ok(sighash.and_then(|sighash| Sha512Trunc256Sum::from_hex(&sighash).ok()))
1006+
}
1007+
1008+
/// Remove a pending block validation
1009+
pub fn remove_pending_block_validation(
1010+
&self,
1011+
sighash: &Sha512Trunc256Sum,
1012+
) -> Result<(), DBError> {
1013+
self.db.execute(
1014+
"DELETE FROM block_validations_pending WHERE signer_signature_hash = ?1",
1015+
params![sighash.to_string()],
1016+
)?;
1017+
Ok(())
1018+
}
1019+
1020+
/// Insert a pending block validation
1021+
pub fn insert_pending_block_validation(
1022+
&self,
1023+
sighash: &Sha512Trunc256Sum,
1024+
ts: u64,
1025+
) -> Result<(), DBError> {
1026+
self.db.execute(
1027+
"INSERT INTO block_validations_pending (signer_signature_hash, added_time) VALUES (?1, ?2)",
1028+
params![sighash.to_string(), u64_to_sql(ts)?],
1029+
)?;
1030+
Ok(())
1031+
}
1032+
9611033
/// Return the start time (epoch time in seconds) and the processing time in milliseconds of the tenure (idenfitied by consensus_hash).
9621034
fn get_tenure_times(&self, tenure: &ConsensusHash) -> Result<(u64, u64), DBError> {
9631035
let query = "SELECT tenure_change, proposed_time, validation_time_ms FROM blocks WHERE consensus_hash = ?1 AND state = ?2 ORDER BY stacks_height DESC";
@@ -1020,6 +1092,26 @@ impl SignerDb {
10201092
);
10211093
tenure_extend_timestamp
10221094
}
1095+
1096+
/// Mark a block as globally accepted. This removes the block from the pending
1097+
/// validations table. This does **not** update the block's state in SignerDb.
1098+
pub fn mark_block_globally_accepted(&self, block_info: &mut BlockInfo) -> Result<(), DBError> {
1099+
block_info
1100+
.mark_globally_accepted()
1101+
.map_err(DBError::Other)?;
1102+
self.remove_pending_block_validation(&block_info.signer_signature_hash())?;
1103+
Ok(())
1104+
}
1105+
1106+
/// Mark a block as globally rejected. This removes the block from the pending
1107+
/// validations table. This does **not** update the block's state in SignerDb.
1108+
pub fn mark_block_globally_rejected(&self, block_info: &mut BlockInfo) -> Result<(), DBError> {
1109+
block_info
1110+
.mark_globally_rejected()
1111+
.map_err(DBError::Other)?;
1112+
self.remove_pending_block_validation(&block_info.signer_signature_hash())?;
1113+
Ok(())
1114+
}
10231115
}
10241116

10251117
fn try_deserialize<T>(s: Option<String>) -> Result<Option<T>, DBError>
@@ -1032,6 +1124,50 @@ where
10321124
.map_err(DBError::SerializationError)
10331125
}
10341126

1127+
/// For tests, a struct to represent a pending block validation
1128+
#[cfg(any(test, feature = "testing"))]
1129+
pub struct PendingBlockValidation {
1130+
/// The signer signature hash of the block
1131+
pub signer_signature_hash: Sha512Trunc256Sum,
1132+
/// The time at which the block was added to the pending table
1133+
pub added_time: u64,
1134+
}
1135+
1136+
#[cfg(any(test, feature = "testing"))]
1137+
impl FromRow<PendingBlockValidation> for PendingBlockValidation {
1138+
fn from_row(row: &rusqlite::Row) -> Result<Self, DBError> {
1139+
let signer_signature_hash = Sha512Trunc256Sum::from_column(row, "signer_signature_hash")?;
1140+
let added_time = row.get_unwrap(1);
1141+
Ok(PendingBlockValidation {
1142+
signer_signature_hash,
1143+
added_time,
1144+
})
1145+
}
1146+
}
1147+
1148+
#[cfg(any(test, feature = "testing"))]
1149+
impl SignerDb {
1150+
/// For tests, fetch all pending block validations
1151+
pub fn get_all_pending_block_validations(
1152+
&self,
1153+
) -> Result<Vec<PendingBlockValidation>, DBError> {
1154+
let qry = "SELECT signer_signature_hash, added_time FROM block_validations_pending ORDER BY added_time ASC";
1155+
query_rows(&self.db, qry, params![])
1156+
}
1157+
1158+
/// For tests, check if a pending block validation exists
1159+
pub fn has_pending_block_validation(
1160+
&self,
1161+
sighash: &Sha512Trunc256Sum,
1162+
) -> Result<bool, DBError> {
1163+
let qry = "SELECT signer_signature_hash FROM block_validations_pending WHERE signer_signature_hash = ?1";
1164+
let args = params![sighash.to_string()];
1165+
let sighash_opt: Option<String> = query_row(&self.db, qry, args)?;
1166+
Ok(sighash_opt.is_some())
1167+
}
1168+
}
1169+
1170+
/// Tests for SignerDb
10351171
#[cfg(test)]
10361172
mod tests {
10371173
use std::fs;
@@ -1733,6 +1869,40 @@ mod tests {
17331869
);
17341870
}
17351871

1872+
#[test]
1873+
fn test_get_and_remove_pending_block_validation() {
1874+
let db_path = tmp_db_path();
1875+
let db = SignerDb::new(db_path).expect("Failed to create signer db");
1876+
1877+
let pending_hash = db.get_and_remove_pending_block_validation().unwrap();
1878+
assert!(pending_hash.is_none());
1879+
1880+
db.insert_pending_block_validation(&Sha512Trunc256Sum([0x01; 32]), 1000)
1881+
.unwrap();
1882+
db.insert_pending_block_validation(&Sha512Trunc256Sum([0x02; 32]), 2000)
1883+
.unwrap();
1884+
db.insert_pending_block_validation(&Sha512Trunc256Sum([0x03; 32]), 3000)
1885+
.unwrap();
1886+
1887+
let pending_hash = db.get_and_remove_pending_block_validation().unwrap();
1888+
assert_eq!(pending_hash, Some(Sha512Trunc256Sum([0x01; 32])));
1889+
1890+
let pendings = db.get_all_pending_block_validations().unwrap();
1891+
assert_eq!(pendings.len(), 2);
1892+
1893+
let pending_hash = db.get_and_remove_pending_block_validation().unwrap();
1894+
assert_eq!(pending_hash, Some(Sha512Trunc256Sum([0x02; 32])));
1895+
1896+
let pendings = db.get_all_pending_block_validations().unwrap();
1897+
assert_eq!(pendings.len(), 1);
1898+
1899+
let pending_hash = db.get_and_remove_pending_block_validation().unwrap();
1900+
assert_eq!(pending_hash, Some(Sha512Trunc256Sum([0x03; 32])));
1901+
1902+
let pendings = db.get_all_pending_block_validations().unwrap();
1903+
assert_eq!(pendings.len(), 0);
1904+
}
1905+
17361906
#[test]
17371907
fn has_proposed_block() {
17381908
let db_path = tmp_db_path();

0 commit comments

Comments
 (0)