@@ -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+
483489static 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+
537548impl 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,10 @@ 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 =
778+ "SELECT block_info FROM blocks WHERE consensus_hash = ? AND signed_over = 1 LIMIT 1" ;
753779 let result: Option < String > = query_row ( & self . db , query, [ tenure] ) ?;
754780
755781 Ok ( result. is_some ( ) )
@@ -1112,6 +1138,30 @@ impl SignerDb {
11121138 self . remove_pending_block_validation ( & block_info. signer_signature_hash ( ) ) ?;
11131139 Ok ( ( ) )
11141140 }
1141+ /// Update the tenure (identified by consensus_hash) last activity timestamp
1142+ pub fn update_last_activity_time (
1143+ & mut self ,
1144+ tenure : & ConsensusHash ,
1145+ last_activity_time : u64 ,
1146+ ) -> Result < ( ) , DBError > {
1147+ debug ! ( "Updating last activity for tenure" ; "consensus_hash" => %tenure, "last_activity_time" => last_activity_time) ;
1148+ 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) ?] ) ?;
1149+ Ok ( ( ) )
1150+ }
1151+
1152+ /// Get the last activity timestamp for a tenure (identified by consensus_hash)
1153+ pub fn get_last_activity_time ( & self , tenure : & ConsensusHash ) -> Result < Option < u64 > , DBError > {
1154+ let query =
1155+ "SELECT last_activity_time FROM tenure_activity WHERE consensus_hash = ? LIMIT 1" ;
1156+ let Some ( last_activity_time_i64) = query_row :: < i64 , _ > ( & self . db , query, & [ tenure] ) ? else {
1157+ return Ok ( None ) ;
1158+ } ;
1159+ let last_activity_time = u64:: try_from ( last_activity_time_i64) . map_err ( |e| {
1160+ error ! ( "Failed to parse db last_activity_time as u64: {e}" ) ;
1161+ DBError :: Corruption
1162+ } ) ?;
1163+ Ok ( Some ( last_activity_time) )
1164+ }
11151165}
11161166
11171167fn try_deserialize < T > ( s : Option < String > ) -> Result < Option < T > , DBError >
@@ -1903,7 +1953,7 @@ mod tests {
19031953 }
19041954
19051955 #[ test]
1906- fn has_proposed_block ( ) {
1956+ fn has_signed_block ( ) {
19071957 let db_path = tmp_db_path ( ) ;
19081958 let consensus_hash_1 = ConsensusHash ( [ 0x01 ; 20 ] ) ;
19091959 let consensus_hash_2 = ConsensusHash ( [ 0x02 ; 20 ] ) ;
@@ -1913,16 +1963,59 @@ mod tests {
19131963 b. block . header . chain_length = 1 ;
19141964 } ) ;
19151965
1916- assert ! ( !db. has_proposed_block_in_tenure ( & consensus_hash_1) . unwrap( ) ) ;
1917- assert ! ( !db. has_proposed_block_in_tenure ( & consensus_hash_2) . unwrap( ) ) ;
1966+ assert ! ( !db. has_signed_block_in_tenure ( & consensus_hash_1) . unwrap( ) ) ;
1967+ assert ! ( !db. has_signed_block_in_tenure ( & consensus_hash_2) . unwrap( ) ) ;
19181968
1969+ block_info. signed_over = true ;
19191970 db. insert_block ( & block_info) . unwrap ( ) ;
19201971
1972+ assert ! ( db. has_signed_block_in_tenure( & consensus_hash_1) . unwrap( ) ) ;
1973+ assert ! ( !db. has_signed_block_in_tenure( & consensus_hash_2) . unwrap( ) ) ;
1974+
1975+ block_info. block . header . consensus_hash = consensus_hash_2;
19211976 block_info. block . header . chain_length = 2 ;
1977+ block_info. signed_over = false ;
19221978
19231979 db. insert_block ( & block_info) . unwrap ( ) ;
19241980
1925- assert ! ( db. has_proposed_block_in_tenure( & consensus_hash_1) . unwrap( ) ) ;
1926- assert ! ( !db. has_proposed_block_in_tenure( & consensus_hash_2) . unwrap( ) ) ;
1981+ assert ! ( db. has_signed_block_in_tenure( & consensus_hash_1) . unwrap( ) ) ;
1982+ assert ! ( !db. has_signed_block_in_tenure( & consensus_hash_2) . unwrap( ) ) ;
1983+
1984+ block_info. signed_over = true ;
1985+
1986+ db. insert_block ( & block_info) . unwrap ( ) ;
1987+
1988+ assert ! ( db. has_signed_block_in_tenure( & consensus_hash_1) . unwrap( ) ) ;
1989+ assert ! ( db. has_signed_block_in_tenure( & consensus_hash_2) . unwrap( ) ) ;
1990+ }
1991+
1992+ #[ test]
1993+ fn update_last_activity ( ) {
1994+ let db_path = tmp_db_path ( ) ;
1995+ let consensus_hash_1 = ConsensusHash ( [ 0x01 ; 20 ] ) ;
1996+ let consensus_hash_2 = ConsensusHash ( [ 0x02 ; 20 ] ) ;
1997+ let mut db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
1998+
1999+ assert ! ( db
2000+ . get_last_activity_time( & consensus_hash_1)
2001+ . unwrap( )
2002+ . is_none( ) ) ;
2003+ assert ! ( db
2004+ . get_last_activity_time( & consensus_hash_2)
2005+ . unwrap( )
2006+ . is_none( ) ) ;
2007+
2008+ let time = get_epoch_time_secs ( ) ;
2009+ db. update_last_activity_time ( & consensus_hash_1, time)
2010+ . unwrap ( ) ;
2011+ let retrieved_time = db
2012+ . get_last_activity_time ( & consensus_hash_1)
2013+ . unwrap ( )
2014+ . unwrap ( ) ;
2015+ assert_eq ! ( time, retrieved_time) ;
2016+ assert ! ( db
2017+ . get_last_activity_time( & consensus_hash_2)
2018+ . unwrap( )
2019+ . is_none( ) ) ;
19272020 }
19282021}
0 commit comments