Skip to content

Commit 953388b

Browse files
committed
Update signerdb to have tenure_activity table and use in is_timed_out
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent a46254d commit 953388b

File tree

2 files changed

+105
-19
lines changed

2 files changed

+105
-19
lines changed

stacks-signer/src/chainstate.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,25 @@ impl SortitionState {
8989
if self.miner_status != SortitionMinerStatus::Valid {
9090
return Ok(false);
9191
}
92-
// if we've already seen a proposed block from this miner. It cannot have timed out.
93-
let has_blocks = signer_db.has_proposed_block_in_tenure(&self.consensus_hash)?;
94-
if has_blocks {
92+
// if we've already signed a block in this tenure, the miner can't have timed out.
93+
let has_block = signer_db.has_signed_block_in_tenure(&self.consensus_hash)?;
94+
if has_block {
9595
return Ok(false);
9696
}
9797
let Some(received_ts) = signer_db.get_burn_block_receive_time(&self.burn_block_hash)?
9898
else {
9999
return Ok(false);
100100
};
101101
let received_time = UNIX_EPOCH + Duration::from_secs(received_ts);
102-
let Ok(elapsed) = std::time::SystemTime::now().duration_since(received_time) else {
102+
let last_activity = signer_db
103+
.get_last_activity_time(&self.consensus_hash)?
104+
.map(|time| UNIX_EPOCH + Duration::from_secs(time))
105+
.unwrap_or(received_time);
106+
107+
let Ok(elapsed) = std::time::SystemTime::now().duration_since(last_activity) else {
103108
return Ok(false);
104109
};
105-
if elapsed > timeout {
106-
return Ok(true);
107-
}
108-
Ok(false)
110+
Ok(elapsed > timeout)
109111
}
110112
}
111113

stacks-signer/src/signerdb.rs

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,12 @@ CREATE TABLE IF NOT EXISTS block_validations_pending (
480480
PRIMARY KEY (signer_signature_hash)
481481
) STRICT;"#;
482482

483+
static CREATE_TENURE_ACTIVTY_TABLE: &str = r#"
484+
CREATE TABLE IF NOT EXISTS tenure_activity (
485+
consensus_hash TEXT NOT NULL PRIMARY KEY,
486+
last_activity_time INTEGER NOT NULL
487+
) STRICT;"#;
488+
483489
static SCHEMA_1: &[&str] = &[
484490
DROP_SCHEMA_0,
485491
CREATE_DB_CONFIG,
@@ -534,9 +540,14 @@ static SCHEMA_6: &[&str] = &[
534540
"INSERT OR REPLACE INTO db_config (version) VALUES (6);",
535541
];
536542

543+
static SCHEMA_7: &[&str] = &[
544+
CREATE_TENURE_ACTIVTY_TABLE,
545+
"INSERT OR REPLACE INTO db_config (version) VALUES (7);",
546+
];
547+
537548
impl SignerDb {
538549
/// The current schema version used in this build of the signer binary.
539-
pub const SCHEMA_VERSION: u32 = 6;
550+
pub const SCHEMA_VERSION: u32 = 7;
540551

541552
/// Create a new `SignerState` instance.
542553
/// This will create a new SQLite database at the given path
@@ -650,6 +661,20 @@ impl SignerDb {
650661
Ok(())
651662
}
652663

664+
/// Migrate from schema 6 to schema 7
665+
fn schema_7_migration(tx: &Transaction) -> Result<(), DBError> {
666+
if Self::get_schema_version(tx)? >= 7 {
667+
// no migration necessary
668+
return Ok(());
669+
}
670+
671+
for statement in SCHEMA_7.iter() {
672+
tx.execute_batch(statement)?;
673+
}
674+
675+
Ok(())
676+
}
677+
653678
/// Register custom scalar functions used by the database
654679
fn register_scalar_functions(&self) -> Result<(), DBError> {
655680
// Register helper function for determining if a block is a tenure change transaction
@@ -689,7 +714,8 @@ impl SignerDb {
689714
3 => Self::schema_4_migration(&sql_tx)?,
690715
4 => Self::schema_5_migration(&sql_tx)?,
691716
5 => Self::schema_6_migration(&sql_tx)?,
692-
6 => break,
717+
6 => Self::schema_7_migration(&sql_tx)?,
718+
7 => break,
693719
x => return Err(DBError::Other(format!(
694720
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}",
695721
Self::SCHEMA_VERSION,
@@ -746,10 +772,9 @@ impl SignerDb {
746772
try_deserialize(result)
747773
}
748774

749-
/// Return whether a block proposal has been stored for a tenure (identified by its consensus hash)
750-
/// Does not consider the block's state.
751-
pub fn has_proposed_block_in_tenure(&self, tenure: &ConsensusHash) -> Result<bool, DBError> {
752-
let query = "SELECT block_info FROM blocks WHERE consensus_hash = ? LIMIT 1";
775+
/// Return whether there was signed block in a tenure (identified by its consensus hash)
776+
pub fn has_signed_block_in_tenure(&self, tenure: &ConsensusHash) -> Result<bool, DBError> {
777+
let query = "SELECT block_info FROM blocks WHERE consensus_hash = ? AND signed_over = 1 ORDER BY stacks_height DESC LIMIT 1";
753778
let result: Option<String> = query_row(&self.db, query, [tenure])?;
754779

755780
Ok(result.is_some())
@@ -1112,6 +1137,30 @@ impl SignerDb {
11121137
self.remove_pending_block_validation(&block_info.signer_signature_hash())?;
11131138
Ok(())
11141139
}
1140+
/// Update the tenure (identified by consensus_hash) last activity timestamp
1141+
pub fn update_last_activity_time(
1142+
&mut self,
1143+
tenure: &ConsensusHash,
1144+
last_activity_time: u64,
1145+
) -> Result<(), DBError> {
1146+
debug!("Updating last activity for tenure"; "consensus_hash" => %tenure, "last_activity_time" => last_activity_time);
1147+
self.db.execute("INSERT OR REPLACE INTO tenure_activity (consensus_hash, last_activity_time) VALUES (?1, ?2)", params![tenure, u64_to_sql(last_activity_time)?])?;
1148+
Ok(())
1149+
}
1150+
1151+
/// Get the last activity timestamp for a tenure (identified by consensus_hash)
1152+
pub fn get_last_activity_time(&self, tenure: &ConsensusHash) -> Result<Option<u64>, DBError> {
1153+
let query =
1154+
"SELECT last_activity_time FROM tenure_activity WHERE consensus_hash = ? LIMIT 1";
1155+
let Some(last_activity_time_i64) = query_row::<i64, _>(&self.db, query, &[tenure])? else {
1156+
return Ok(None);
1157+
};
1158+
let last_activity_time = u64::try_from(last_activity_time_i64).map_err(|e| {
1159+
error!("Failed to parse db last_activity_time as u64: {e}");
1160+
DBError::Corruption
1161+
})?;
1162+
Ok(Some(last_activity_time))
1163+
}
11151164
}
11161165

11171166
fn try_deserialize<T>(s: Option<String>) -> Result<Option<T>, DBError>
@@ -1903,7 +1952,7 @@ mod tests {
19031952
}
19041953

19051954
#[test]
1906-
fn has_proposed_block() {
1955+
fn has_signed_block() {
19071956
let db_path = tmp_db_path();
19081957
let consensus_hash_1 = ConsensusHash([0x01; 20]);
19091958
let consensus_hash_2 = ConsensusHash([0x02; 20]);
@@ -1913,16 +1962,51 @@ mod tests {
19131962
b.block.header.chain_length = 1;
19141963
});
19151964

1916-
assert!(!db.has_proposed_block_in_tenure(&consensus_hash_1).unwrap());
1917-
assert!(!db.has_proposed_block_in_tenure(&consensus_hash_2).unwrap());
1965+
assert!(!db.has_signed_block_in_tenure(&consensus_hash_1).unwrap());
1966+
assert!(!db.has_signed_block_in_tenure(&consensus_hash_2).unwrap());
19181967

1968+
block_info.signed_over = true;
19191969
db.insert_block(&block_info).unwrap();
19201970

1971+
assert!(db.has_signed_block_in_tenure(&consensus_hash_1).unwrap());
1972+
assert!(!db.has_signed_block_in_tenure(&consensus_hash_2).unwrap());
1973+
19211974
block_info.block.header.chain_length = 2;
1975+
block_info.signed_over = false;
19221976

19231977
db.insert_block(&block_info).unwrap();
19241978

1925-
assert!(db.has_proposed_block_in_tenure(&consensus_hash_1).unwrap());
1926-
assert!(!db.has_proposed_block_in_tenure(&consensus_hash_2).unwrap());
1979+
assert!(db.has_signed_block_in_tenure(&consensus_hash_1).unwrap());
1980+
assert!(!db.has_signed_block_in_tenure(&consensus_hash_2).unwrap());
1981+
}
1982+
1983+
#[test]
1984+
fn update_last_activity() {
1985+
let db_path = tmp_db_path();
1986+
let consensus_hash_1 = ConsensusHash([0x01; 20]);
1987+
let consensus_hash_2 = ConsensusHash([0x02; 20]);
1988+
let mut db = SignerDb::new(db_path).expect("Failed to create signer db");
1989+
1990+
assert!(db
1991+
.get_last_activity_time(&consensus_hash_1)
1992+
.unwrap()
1993+
.is_none());
1994+
assert!(db
1995+
.get_last_activity_time(&consensus_hash_2)
1996+
.unwrap()
1997+
.is_none());
1998+
1999+
let time = get_epoch_time_secs();
2000+
db.update_last_activity_time(&consensus_hash_1, time)
2001+
.unwrap();
2002+
let retrieved_time = db
2003+
.get_last_activity_time(&consensus_hash_1)
2004+
.unwrap()
2005+
.unwrap();
2006+
assert_eq!(time, retrieved_time);
2007+
assert!(db
2008+
.get_last_activity_time(&consensus_hash_2)
2009+
.unwrap()
2010+
.is_none());
19272011
}
19282012
}

0 commit comments

Comments
 (0)