@@ -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,10 @@ 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 =
778
+ "SELECT block_info FROM blocks WHERE consensus_hash = ? AND signed_over = 1 LIMIT 1" ;
753
779
let result: Option < String > = query_row ( & self . db , query, [ tenure] ) ?;
754
780
755
781
Ok ( result. is_some ( ) )
@@ -1112,6 +1138,30 @@ impl SignerDb {
1112
1138
self . remove_pending_block_validation ( & block_info. signer_signature_hash ( ) ) ?;
1113
1139
Ok ( ( ) )
1114
1140
}
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
+ }
1115
1165
}
1116
1166
1117
1167
fn try_deserialize < T > ( s : Option < String > ) -> Result < Option < T > , DBError >
@@ -1903,7 +1953,7 @@ mod tests {
1903
1953
}
1904
1954
1905
1955
#[ test]
1906
- fn has_proposed_block ( ) {
1956
+ fn has_signed_block ( ) {
1907
1957
let db_path = tmp_db_path ( ) ;
1908
1958
let consensus_hash_1 = ConsensusHash ( [ 0x01 ; 20 ] ) ;
1909
1959
let consensus_hash_2 = ConsensusHash ( [ 0x02 ; 20 ] ) ;
@@ -1913,16 +1963,59 @@ mod tests {
1913
1963
b. block . header . chain_length = 1 ;
1914
1964
} ) ;
1915
1965
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( ) ) ;
1918
1968
1969
+ block_info. signed_over = true ;
1919
1970
db. insert_block ( & block_info) . unwrap ( ) ;
1920
1971
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;
1921
1976
block_info. block . header . chain_length = 2 ;
1977
+ block_info. signed_over = false ;
1922
1978
1923
1979
db. insert_block ( & block_info) . unwrap ( ) ;
1924
1980
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( ) ) ;
1927
2020
}
1928
2021
}
0 commit comments