@@ -480,6 +480,12 @@ CREATE TABLE IF NOT EXISTS block_validations_pending (
480
480
PRIMARY KEY (signer_signature_hash)
481
481
) STRICT;"# ;
482
482
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
+
483
489
static SCHEMA_1 : & [ & str ] = & [
484
490
DROP_SCHEMA_0 ,
485
491
CREATE_DB_CONFIG ,
@@ -534,9 +540,14 @@ static SCHEMA_6: &[&str] = &[
534
540
"INSERT OR REPLACE INTO db_config (version) VALUES (6);" ,
535
541
] ;
536
542
543
+ static SCHEMA_7 : & [ & str ] = & [
544
+ CREATE_TENURE_ACTIVTY_TABLE ,
545
+ "INSERT OR REPLACE INTO db_config (version) VALUES (7);" ,
546
+ ] ;
547
+
537
548
impl SignerDb {
538
549
/// 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 ;
540
551
541
552
/// Create a new `SignerState` instance.
542
553
/// This will create a new SQLite database at the given path
@@ -650,6 +661,20 @@ impl SignerDb {
650
661
Ok ( ( ) )
651
662
}
652
663
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
+
653
678
/// Register custom scalar functions used by the database
654
679
fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
655
680
// Register helper function for determining if a block is a tenure change transaction
@@ -689,7 +714,8 @@ impl SignerDb {
689
714
3 => Self :: schema_4_migration ( & sql_tx) ?,
690
715
4 => Self :: schema_5_migration ( & sql_tx) ?,
691
716
5 => Self :: schema_6_migration ( & sql_tx) ?,
692
- 6 => break ,
717
+ 6 => Self :: schema_7_migration ( & sql_tx) ?,
718
+ 7 => break ,
693
719
x => return Err ( DBError :: Other ( format ! (
694
720
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
695
721
Self :: SCHEMA_VERSION ,
@@ -746,10 +772,9 @@ impl SignerDb {
746
772
try_deserialize ( result)
747
773
}
748
774
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" ;
753
778
let result: Option < String > = query_row ( & self . db , query, [ tenure] ) ?;
754
779
755
780
Ok ( result. is_some ( ) )
@@ -1112,6 +1137,30 @@ impl SignerDb {
1112
1137
self . remove_pending_block_validation ( & block_info. signer_signature_hash ( ) ) ?;
1113
1138
Ok ( ( ) )
1114
1139
}
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
+ }
1115
1164
}
1116
1165
1117
1166
fn try_deserialize < T > ( s : Option < String > ) -> Result < Option < T > , DBError >
@@ -1903,7 +1952,7 @@ mod tests {
1903
1952
}
1904
1953
1905
1954
#[ test]
1906
- fn has_proposed_block ( ) {
1955
+ fn has_signed_block ( ) {
1907
1956
let db_path = tmp_db_path ( ) ;
1908
1957
let consensus_hash_1 = ConsensusHash ( [ 0x01 ; 20 ] ) ;
1909
1958
let consensus_hash_2 = ConsensusHash ( [ 0x02 ; 20 ] ) ;
@@ -1913,16 +1962,51 @@ mod tests {
1913
1962
b. block . header . chain_length = 1 ;
1914
1963
} ) ;
1915
1964
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( ) ) ;
1918
1967
1968
+ block_info. signed_over = true ;
1919
1969
db. insert_block ( & block_info) . unwrap ( ) ;
1920
1970
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
+
1921
1974
block_info. block . header . chain_length = 2 ;
1975
+ block_info. signed_over = false ;
1922
1976
1923
1977
db. insert_block ( & block_info) . unwrap ( ) ;
1924
1978
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( ) ) ;
1927
2011
}
1928
2012
}
0 commit comments