@@ -40,7 +40,7 @@ use stacks_common::types::chainstate::ConsensusHash;
40
40
use stacks_common:: util:: get_epoch_time_secs;
41
41
use stacks_common:: util:: hash:: Sha512Trunc256Sum ;
42
42
use stacks_common:: util:: secp256k1:: MessageSignature ;
43
- use stacks_common:: { debug, define_u8_enum, error} ;
43
+ use stacks_common:: { debug, define_u8_enum, error, warn } ;
44
44
45
45
#[ derive( Debug , Clone , PartialEq , Serialize , Deserialize ) ]
46
46
/// A vote across the signer set for a block
@@ -649,6 +649,28 @@ static ADD_SIGNER_STATE_MACHINE_UPDATES_SIGNER_ADDR_REWARD_CYCLE_INDEX: &str = r
649
649
CREATE INDEX idx_signer_addr_reward_cycle ON signer_state_machine_updates(reward_cycle, signer_addr, received_time DESC);
650
650
"# ;
651
651
652
+ static DROP_BLOCK_SIGNATURES_TABLE : & str = r#"
653
+ DROP TABLE IF EXISTS block_signatures;
654
+ "# ;
655
+
656
+ static CREATE_BLOCK_SIGNATURES_TABLE_V17 : & str = r#"
657
+ CREATE TABLE IF NOT EXISTS block_signatures (
658
+ -- The block sighash commits to all of the stacks and burnchain state as of its parent,
659
+ -- as well as the tenure itself so there's no need to include the reward cycle. Just
660
+ -- the sighash is sufficient to uniquely identify the block across all burnchain, PoX,
661
+ -- and stacks forks.
662
+ signer_signature_hash TEXT NOT NULL,
663
+ -- the signer address that rejected the block
664
+ signer_addr TEXT NOT NULL,
665
+ -- signature itself
666
+ signature TEXT NOT NULL,
667
+ PRIMARY KEY (signer_addr, signer_signature_hash)
668
+ ) STRICT;"# ;
669
+
670
+ static CREATE_BLOCK_SIGNATURES_INDEX : & str = r#"
671
+ CREATE INDEX IF NOT EXISTS idx_block_signatures_by_sighash ON block_signatures(signer_signature_hash);
672
+ "# ;
673
+
652
674
static SCHEMA_1 : & [ & str ] = & [
653
675
DROP_SCHEMA_0 ,
654
676
CREATE_DB_CONFIG ,
@@ -759,6 +781,13 @@ static SCHEMA_16: &[&str] = &[
759
781
"INSERT INTO db_config (version) VALUES (16);" ,
760
782
] ;
761
783
784
+ static SCHEMA_17 : & [ & str ] = & [
785
+ DROP_BLOCK_SIGNATURES_TABLE ,
786
+ CREATE_BLOCK_SIGNATURES_TABLE_V17 ,
787
+ CREATE_BLOCK_SIGNATURES_INDEX ,
788
+ "INSERT INTO db_config (version) VALUES (17);" ,
789
+ ] ;
790
+
762
791
struct Migration {
763
792
version : u32 ,
764
793
statements : & ' static [ & ' static str ] ,
@@ -829,11 +858,15 @@ static MIGRATIONS: &[Migration] = &[
829
858
version : 16 ,
830
859
statements : SCHEMA_16 ,
831
860
} ,
861
+ Migration {
862
+ version : 17 ,
863
+ statements : SCHEMA_17 ,
864
+ } ,
832
865
] ;
833
866
834
867
impl SignerDb {
835
868
/// The current schema version used in this build of the signer binary.
836
- pub const SCHEMA_VERSION : u32 = 16 ;
869
+ pub const SCHEMA_VERSION : u32 = 17 ;
837
870
838
871
/// Create a new `SignerState` instance.
839
872
/// This will create a new SQLite database at the given path
@@ -1287,20 +1320,28 @@ impl SignerDb {
1287
1320
pub fn add_block_signature (
1288
1321
& self ,
1289
1322
block_sighash : & Sha512Trunc256Sum ,
1323
+ signer_addr : & StacksAddress ,
1290
1324
signature : & MessageSignature ,
1291
- ) -> Result < ( ) , DBError > {
1292
- let qry = "INSERT OR REPLACE INTO block_signatures (signer_signature_hash, signature) VALUES (?1, ?2);" ;
1325
+ ) -> Result < bool , DBError > {
1326
+ let qry = "INSERT OR IGNORE INTO block_signatures (signer_signature_hash, signer_addr, signature) VALUES (?1, ?2, ?3 );" ;
1293
1327
let args = params ! [
1294
1328
block_sighash,
1329
+ signer_addr. to_string( ) ,
1295
1330
serde_json:: to_string( signature) . map_err( DBError :: SerializationError ) ?
1296
1331
] ;
1297
1332
1298
1333
debug ! ( "Inserting block signature." ;
1299
1334
"signer_signature_hash" => %block_sighash,
1300
1335
"signature" => %signature) ;
1301
1336
1302
- self . db . execute ( qry, args) ?;
1303
- Ok ( ( ) )
1337
+ let rows_added = self . db . execute ( qry, args) ?;
1338
+
1339
+ // Remove any block rejection entry for this signer and block hash
1340
+ let del_qry = "DELETE FROM block_rejection_signer_addrs WHERE signer_signature_hash = ?1 AND signer_addr = ?2" ;
1341
+ let del_args = params ! [ block_sighash, signer_addr. to_string( ) ] ;
1342
+ self . db . execute ( del_qry, del_args) ?;
1343
+
1344
+ Ok ( rows_added > 0 )
1304
1345
}
1305
1346
1306
1347
/// Get all signatures for a block
@@ -1323,22 +1364,58 @@ impl SignerDb {
1323
1364
block_sighash : & Sha512Trunc256Sum ,
1324
1365
addr : & StacksAddress ,
1325
1366
reject_reason : & RejectReason ,
1326
- ) -> Result < ( ) , DBError > {
1327
- let qry = "INSERT OR REPLACE INTO block_rejection_signer_addrs (signer_signature_hash, signer_addr, reject_code) VALUES (?1, ?2, ?3);" ;
1328
- let args = params ! [
1329
- block_sighash,
1330
- addr. to_string( ) ,
1331
- RejectReasonPrefix :: from( reject_reason) as i64
1332
- ] ;
1367
+ ) -> Result < bool , DBError > {
1368
+ // If this signer/block already has a signature, do not allow a rejection
1369
+ let sig_qry = "SELECT 1 FROM block_signatures WHERE signer_signature_hash = ?1 AND signer_addr = ?2 LIMIT 1" ;
1370
+ let sig_args = params ! [ block_sighash, addr. to_string( ) ] ;
1371
+ let has_signature: Option < i64 > = self
1372
+ . db
1373
+ . query_row ( sig_qry, sig_args, |row| row. get ( 0 ) )
1374
+ . optional ( ) ?;
1375
+ if has_signature. is_some ( ) {
1376
+ warn ! ( "Cannot add block rejection for signer {} and block {} because a signature already exists." ,
1377
+ addr, block_sighash) ;
1378
+ return Ok ( false ) ;
1379
+ }
1333
1380
1334
- debug ! ( "Inserting block rejection." ;
1335
- " signer_signature_hash" => %block_sighash ,
1336
- "signer_address" => %addr ,
1337
- "reject_reason" => %reject_reason
1338
- ) ;
1381
+ // Check if a row exists for this sighash/signer combo
1382
+ let qry = "SELECT reject_code FROM block_rejection_signer_addrs WHERE signer_signature_hash = ?1 AND signer_addr = ?2 LIMIT 1" ;
1383
+ let args = params ! [ block_sighash , addr . to_string ( ) ] ;
1384
+ let existing_code : Option < i64 > =
1385
+ self . db . query_row ( qry , args , |row| row . get ( 0 ) ) . optional ( ) ? ;
1339
1386
1340
- self . db . execute ( qry, args) ?;
1341
- Ok ( ( ) )
1387
+ let reject_code = RejectReasonPrefix :: from ( reject_reason) as i64 ;
1388
+
1389
+ match existing_code {
1390
+ Some ( code) if code == reject_code => {
1391
+ // Row exists with same reject_reason, do nothing
1392
+ Ok ( false )
1393
+ }
1394
+ Some ( _) => {
1395
+ // Row exists but with different reject_reason, update it
1396
+ let update_qry = "UPDATE block_rejection_signer_addrs SET reject_code = ?1 WHERE signer_signature_hash = ?2 AND signer_addr = ?3" ;
1397
+ let update_args = params ! [ reject_code, block_sighash, addr. to_string( ) ] ;
1398
+ self . db . execute ( update_qry, update_args) ?;
1399
+ debug ! ( "Updated block rejection reason." ;
1400
+ "signer_signature_hash" => %block_sighash,
1401
+ "signer_address" => %addr,
1402
+ "reject_reason" => %reject_reason
1403
+ ) ;
1404
+ Ok ( true )
1405
+ }
1406
+ None => {
1407
+ // Row does not exist, insert it
1408
+ let insert_qry = "INSERT INTO block_rejection_signer_addrs (signer_signature_hash, signer_addr, reject_code) VALUES (?1, ?2, ?3)" ;
1409
+ let insert_args = params ! [ block_sighash, addr. to_string( ) , reject_code] ;
1410
+ self . db . execute ( insert_qry, insert_args) ?;
1411
+ debug ! ( "Inserted block rejection." ;
1412
+ "signer_signature_hash" => %block_sighash,
1413
+ "signer_address" => %addr,
1414
+ "reject_reason" => %reject_reason
1415
+ ) ;
1416
+ Ok ( true )
1417
+ }
1418
+ }
1342
1419
}
1343
1420
1344
1421
/// Get all signer addresses that rejected the block (and their reject codes)
@@ -2090,21 +2167,177 @@ pub mod tests {
2090
2167
let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2091
2168
2092
2169
let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2170
+ let address1 = StacksAddress :: burn_address ( false ) ;
2171
+ let address2 = StacksAddress :: burn_address ( true ) ;
2093
2172
let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2094
2173
let sig2 = MessageSignature ( [ 0x22 ; 65 ] ) ;
2095
2174
2096
2175
assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2097
2176
2098
- db. add_block_signature ( & block_id, & sig1) . unwrap ( ) ;
2177
+ db. add_block_signature ( & block_id, & address1 , & sig1) . unwrap ( ) ;
2099
2178
assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2100
2179
2101
- db. add_block_signature ( & block_id, & sig2) . unwrap ( ) ;
2180
+ db. add_block_signature ( & block_id, & address2 , & sig2) . unwrap ( ) ;
2102
2181
assert_eq ! (
2103
2182
db. get_block_signatures( & block_id) . unwrap( ) ,
2104
2183
vec![ sig1, sig2]
2105
2184
) ;
2106
2185
}
2107
2186
2187
+ #[ test]
2188
+ fn duplicate_block_signatures ( ) {
2189
+ let db_path = tmp_db_path ( ) ;
2190
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2191
+
2192
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2193
+ let address = StacksAddress :: burn_address ( false ) ;
2194
+ let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2195
+ let sig2 = MessageSignature ( [ 0x22 ; 65 ] ) ;
2196
+
2197
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2198
+
2199
+ assert ! ( db. add_block_signature( & block_id, & address, & sig1) . unwrap( ) ) ;
2200
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2201
+
2202
+ assert ! ( !db. add_block_signature( & block_id, & address, & sig2) . unwrap( ) ) ;
2203
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2204
+ }
2205
+
2206
+ #[ test]
2207
+ fn add_and_get_block_rejections ( ) {
2208
+ let db_path = tmp_db_path ( ) ;
2209
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2210
+
2211
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2212
+ let address1 = StacksAddress :: burn_address ( false ) ;
2213
+ let address2 = StacksAddress :: burn_address ( true ) ;
2214
+
2215
+ assert_eq ! (
2216
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2217
+ vec![ ]
2218
+ ) ;
2219
+
2220
+ assert ! ( db
2221
+ . add_block_rejection_signer_addr(
2222
+ & block_id,
2223
+ & address1,
2224
+ & RejectReason :: DuplicateBlockFound ,
2225
+ )
2226
+ . unwrap( ) ) ;
2227
+ assert_eq ! (
2228
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2229
+ vec![ ( address1, RejectReasonPrefix :: DuplicateBlockFound ) ]
2230
+ ) ;
2231
+
2232
+ assert ! ( db
2233
+ . add_block_rejection_signer_addr(
2234
+ & block_id,
2235
+ & address2,
2236
+ & RejectReason :: InvalidParentBlock
2237
+ )
2238
+ . unwrap( ) ) ;
2239
+ assert_eq ! (
2240
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2241
+ vec![
2242
+ ( address1, RejectReasonPrefix :: DuplicateBlockFound ) ,
2243
+ ( address2, RejectReasonPrefix :: InvalidParentBlock )
2244
+ ]
2245
+ ) ;
2246
+ }
2247
+
2248
+ #[ test]
2249
+ fn duplicate_block_rejections ( ) {
2250
+ let db_path = tmp_db_path ( ) ;
2251
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2252
+
2253
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2254
+ let address = StacksAddress :: burn_address ( false ) ;
2255
+
2256
+ assert_eq ! (
2257
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2258
+ vec![ ]
2259
+ ) ;
2260
+
2261
+ assert ! ( db
2262
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidParentBlock )
2263
+ . unwrap( ) ) ;
2264
+ assert_eq ! (
2265
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2266
+ vec![ ( address, RejectReasonPrefix :: InvalidParentBlock ) ]
2267
+ ) ;
2268
+
2269
+ assert ! ( db
2270
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidMiner )
2271
+ . unwrap( ) ) ;
2272
+ assert_eq ! (
2273
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2274
+ vec![ ( address, RejectReasonPrefix :: InvalidMiner ) ]
2275
+ ) ;
2276
+
2277
+ assert ! ( !db
2278
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidMiner )
2279
+ . unwrap( ) ) ;
2280
+ assert_eq ! (
2281
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2282
+ vec![ ( address, RejectReasonPrefix :: InvalidMiner ) ]
2283
+ ) ;
2284
+ }
2285
+
2286
+ #[ test]
2287
+ fn reject_then_accept ( ) {
2288
+ let db_path = tmp_db_path ( ) ;
2289
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2290
+
2291
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2292
+ let address = StacksAddress :: burn_address ( false ) ;
2293
+ let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2294
+
2295
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2296
+
2297
+ assert ! ( db
2298
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidParentBlock )
2299
+ . unwrap( ) ) ;
2300
+ assert_eq ! (
2301
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2302
+ vec![ ( address, RejectReasonPrefix :: InvalidParentBlock ) ]
2303
+ ) ;
2304
+
2305
+ assert ! ( db. add_block_signature( & block_id, & address, & sig1) . unwrap( ) ) ;
2306
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2307
+ assert ! ( db
2308
+ . get_block_rejection_signer_addrs( & block_id)
2309
+ . unwrap( )
2310
+ . is_empty( ) ) ;
2311
+ }
2312
+
2313
+ #[ test]
2314
+ fn accept_then_reject ( ) {
2315
+ let db_path = tmp_db_path ( ) ;
2316
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2317
+
2318
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2319
+ let address = StacksAddress :: burn_address ( false ) ;
2320
+ let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2321
+
2322
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2323
+
2324
+ assert ! ( db. add_block_signature( & block_id, & address, & sig1) . unwrap( ) ) ;
2325
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2326
+ assert ! ( db
2327
+ . get_block_rejection_signer_addrs( & block_id)
2328
+ . unwrap( )
2329
+ . is_empty( ) ) ;
2330
+
2331
+ assert ! ( !db
2332
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidParentBlock )
2333
+ . unwrap( ) ) ;
2334
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2335
+ assert ! ( db
2336
+ . get_block_rejection_signer_addrs( & block_id)
2337
+ . unwrap( )
2338
+ . is_empty( ) ) ;
2339
+ }
2340
+
2108
2341
#[ test]
2109
2342
fn test_and_set_block_broadcasted ( ) {
2110
2343
let db_path = tmp_db_path ( ) ;
0 commit comments