@@ -21,12 +21,12 @@ use std::time::{Duration, SystemTime};
2121
2222use blockstack_lib:: chainstate:: nakamoto:: NakamotoBlock ;
2323use blockstack_lib:: chainstate:: stacks:: TransactionPayload ;
24+ #[ cfg( any( test, feature = "testing" ) ) ]
25+ use blockstack_lib:: util_lib:: db:: FromColumn ;
2426use blockstack_lib:: util_lib:: db:: {
2527 query_row, query_rows, sqlite_open, table_exists, tx_begin_immediate, u64_to_sql,
26- Error as DBError ,
28+ Error as DBError , FromRow ,
2729} ;
28- #[ cfg( any( test, feature = "testing" ) ) ]
29- use blockstack_lib:: util_lib:: db:: { FromColumn , FromRow } ;
3030use clarity:: types:: chainstate:: { BurnchainHeaderHash , StacksAddress } ;
3131use clarity:: types:: Address ;
3232use libsigner:: v0:: messages:: { RejectReason , RejectReasonPrefix , StateMachineUpdate } ;
@@ -72,6 +72,34 @@ impl StacksMessageCodec for NakamotoBlockVote {
7272 }
7373}
7474
75+ #[ derive( Serialize , Deserialize , Debug , PartialEq ) ]
76+ /// Struct for storing information about a burn block
77+ pub struct BurnBlockInfo {
78+ /// The hash of the burn block
79+ pub block_hash : BurnchainHeaderHash ,
80+ /// The height of the burn block
81+ pub block_height : u64 ,
82+ /// The consensus hash of the burn block
83+ pub consensus_hash : ConsensusHash ,
84+ /// The hash of the parent burn block
85+ pub parent_burn_block_hash : BurnchainHeaderHash ,
86+ }
87+
88+ impl FromRow < BurnBlockInfo > for BurnBlockInfo {
89+ fn from_row ( row : & rusqlite:: Row ) -> Result < Self , DBError > {
90+ let block_hash: BurnchainHeaderHash = row. get ( 0 ) ?;
91+ let block_height: u64 = row. get ( 1 ) ?;
92+ let consensus_hash: ConsensusHash = row. get ( 2 ) ?;
93+ let parent_burn_block_hash: BurnchainHeaderHash = row. get ( 3 ) ?;
94+ Ok ( BurnBlockInfo {
95+ block_hash,
96+ block_height,
97+ consensus_hash,
98+ parent_burn_block_hash,
99+ } )
100+ }
101+ }
102+
75103#[ derive( Serialize , Deserialize , Debug , PartialEq , Default ) ]
76104/// Store extra version-specific info in `BlockInfo`
77105pub enum ExtraBlockInfo {
@@ -566,6 +594,15 @@ CREATE TABLE IF NOT EXISTS signer_state_machine_updates (
566594 PRIMARY KEY (signer_addr, reward_cycle)
567595) STRICT;"# ;
568596
597+ static ADD_PARENT_BURN_BLOCK_HASH : & str = r#"
598+ ALTER TABLE burn_blocks
599+ ADD COLUMN parent_burn_block_hash TEXT;
600+ "# ;
601+
602+ static ADD_PARENT_BURN_BLOCK_HASH_INDEX : & str = r#"
603+ CREATE INDEX IF NOT EXISTS burn_blocks_parent_burn_block_hash_idx on burn_blocks (parent_burn_block_hash);
604+ "# ;
605+
569606static SCHEMA_1 : & [ & str ] = & [
570607 DROP_SCHEMA_0 ,
571608 CREATE_DB_CONFIG ,
@@ -652,6 +689,12 @@ static SCHEMA_12: &[&str] = &[
652689 "INSERT OR REPLACE INTO db_config (version) VALUES (12);" ,
653690] ;
654691
692+ static SCHEMA_13 : & [ & str ] = & [
693+ ADD_PARENT_BURN_BLOCK_HASH ,
694+ ADD_PARENT_BURN_BLOCK_HASH_INDEX ,
695+ "INSERT INTO db_config (version) VALUES (13);" ,
696+ ] ;
697+
655698impl SignerDb {
656699 /// The current schema version used in this build of the signer binary.
657700 pub const SCHEMA_VERSION : u32 = 12 ;
@@ -852,6 +895,20 @@ impl SignerDb {
852895 Ok ( ( ) )
853896 }
854897
898+ /// Migrate from schema 12 to schema 13
899+ fn schema_13_migration ( tx : & Transaction ) -> Result < ( ) , DBError > {
900+ if Self :: get_schema_version ( tx) ? >= 13 {
901+ // no migration necessary
902+ return Ok ( ( ) ) ;
903+ }
904+
905+ for statement in SCHEMA_13 . iter ( ) {
906+ tx. execute_batch ( statement) ?;
907+ }
908+
909+ Ok ( ( ) )
910+ }
911+
855912 /// Register custom scalar functions used by the database
856913 fn register_scalar_functions ( & self ) -> Result < ( ) , DBError > {
857914 // Register helper function for determining if a block is a tenure change transaction
@@ -897,7 +954,8 @@ impl SignerDb {
897954 9 => Self :: schema_10_migration ( & sql_tx) ?,
898955 10 => Self :: schema_11_migration ( & sql_tx) ?,
899956 11 => Self :: schema_12_migration ( & sql_tx) ?,
900- 12 => break ,
957+ 12 => Self :: schema_13_migration ( & sql_tx) ?,
958+ 13 => break ,
901959 x => return Err ( DBError :: Other ( format ! (
902960 "Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}" ,
903961 Self :: SCHEMA_VERSION ,
@@ -1032,19 +1090,27 @@ impl SignerDb {
10321090 consensus_hash : & ConsensusHash ,
10331091 burn_height : u64 ,
10341092 received_time : & SystemTime ,
1093+ parent_burn_block_hash : & BurnchainHeaderHash ,
10351094 ) -> Result < ( ) , DBError > {
10361095 let received_ts = received_time
10371096 . duration_since ( std:: time:: UNIX_EPOCH )
10381097 . map_err ( |e| DBError :: Other ( format ! ( "Bad system time: {e}" ) ) ) ?
10391098 . as_secs ( ) ;
1040- debug ! ( "Inserting burn block info" ; "burn_block_height" => burn_height, "burn_hash" => %burn_hash, "received" => received_ts, "ch" => %consensus_hash) ;
1099+ debug ! ( "Inserting burn block info" ;
1100+ "burn_block_height" => burn_height,
1101+ "burn_hash" => %burn_hash,
1102+ "received" => received_ts,
1103+ "ch" => %consensus_hash,
1104+ "parent_burn_block_hash" => %parent_burn_block_hash
1105+ ) ;
10411106 self . db . execute (
1042- "INSERT OR REPLACE INTO burn_blocks (block_hash, consensus_hash, block_height, received_time) VALUES (?1, ?2, ?3, ?4)" ,
1107+ "INSERT OR REPLACE INTO burn_blocks (block_hash, consensus_hash, block_height, received_time, parent_burn_block_hash ) VALUES (?1, ?2, ?3, ?4, ?5 )" ,
10431108 params ! [
10441109 burn_hash,
10451110 consensus_hash,
10461111 u64_to_sql( burn_height) ?,
10471112 u64_to_sql( received_ts) ?,
1113+ parent_burn_block_hash,
10481114 ] ,
10491115 ) ?;
10501116 Ok ( ( ) )
@@ -1084,6 +1150,26 @@ impl SignerDb {
10841150 Ok ( Some ( receive_time) )
10851151 }
10861152
1153+ /// Lookup the burn block for a given burn block hash.
1154+ pub fn get_burn_block_by_hash (
1155+ & self ,
1156+ burn_block_hash : & BurnchainHeaderHash ,
1157+ ) -> Result < BurnBlockInfo , DBError > {
1158+ let query =
1159+ "SELECT block_hash, block_height, consensus_hash, parent_burn_block_hash FROM burn_blocks WHERE block_hash = ?" ;
1160+ let args = params ! [ burn_block_hash] ;
1161+
1162+ query_row ( & self . db , query, args) ?. ok_or ( DBError :: NotFoundError )
1163+ }
1164+
1165+ /// Lookup the burn block for a given consensus hash.
1166+ pub fn get_burn_block_by_ch ( & self , ch : & ConsensusHash ) -> Result < BurnBlockInfo , DBError > {
1167+ let query = "SELECT block_hash, block_height, consensus_hash, parent_burn_block_hash FROM burn_blocks WHERE consensus_hash = ?" ;
1168+ let args = params ! [ ch] ;
1169+
1170+ query_row ( & self . db , query, args) ?. ok_or ( DBError :: NotFoundError )
1171+ }
1172+
10871173 /// Insert or replace a block into the database.
10881174 /// Preserves the `broadcast` column if replacing an existing block.
10891175 pub fn insert_block ( & mut self , block_info : & BlockInfo ) -> Result < ( ) , DBError > {
@@ -1717,8 +1803,14 @@ pub mod tests {
17171803 . duration_since ( SystemTime :: UNIX_EPOCH )
17181804 . unwrap ( )
17191805 . as_secs ( ) ;
1720- db. insert_burn_block ( & test_burn_hash, & test_consensus_hash, 10 , & stime)
1721- . unwrap ( ) ;
1806+ db. insert_burn_block (
1807+ & test_burn_hash,
1808+ & test_consensus_hash,
1809+ 10 ,
1810+ & stime,
1811+ & test_burn_hash,
1812+ )
1813+ . unwrap ( ) ;
17221814
17231815 let stored_time = db
17241816 . get_burn_block_receive_time ( & test_burn_hash)
0 commit comments