@@ -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,42 @@ 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_V16 : & 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 signed the block
664
+ signer_addr TEXT NOT NULL,
665
+ -- signature itself
666
+ signature TEXT NOT NULL,
667
+ PRIMARY KEY (signer_signature_hash, signer_addr)
668
+ ) STRICT;"# ;
669
+
670
+ static DROP_BLOCK_REJECTION_SIGNER_ADDRS : & str = r#"
671
+ DROP TABLE IF EXISTS block_rejection_signer_addrs;
672
+ "# ;
673
+
674
+ static CREATE_BLOCK_REJECTION_SIGNER_ADDRS_V16 : & str = r#"
675
+ CREATE TABLE IF NOT EXISTS block_rejection_signer_addrs (
676
+ -- The block sighash commits to all of the stacks and burnchain state as of its parent,
677
+ -- as well as the tenure itself so there's no need to include the reward cycle. Just
678
+ -- the sighash is sufficient to uniquely identify the block across all burnchain, PoX,
679
+ -- and stacks forks.
680
+ signer_signature_hash TEXT NOT NULL,
681
+ -- the signer address that rejected the block
682
+ signer_addr TEXT NOT NULL,
683
+ -- the reject reason code
684
+ reject_code INTEGER NOT NULL,
685
+ PRIMARY KEY (signer_signature_hash, signer_addr)
686
+ ) STRICT;"# ;
687
+
652
688
static SCHEMA_1 : & [ & str ] = & [
653
689
DROP_SCHEMA_0 ,
654
690
CREATE_DB_CONFIG ,
@@ -756,6 +792,10 @@ static SCHEMA_16: &[&str] = &[
756
792
ADD_SIGNER_STATE_MACHINE_UPDATES_BURN_BLOCK_CONSENSUS_HASH_INDEX ,
757
793
ADD_SIGNER_STATE_MACHINE_UPDATES_RECEIVED_TIME_INDEX ,
758
794
ADD_SIGNER_STATE_MACHINE_UPDATES_SIGNER_ADDR_REWARD_CYCLE_INDEX ,
795
+ DROP_BLOCK_SIGNATURES_TABLE ,
796
+ CREATE_BLOCK_SIGNATURES_TABLE_V16 ,
797
+ DROP_BLOCK_REJECTION_SIGNER_ADDRS ,
798
+ CREATE_BLOCK_REJECTION_SIGNER_ADDRS_V16 ,
759
799
"INSERT INTO db_config (version) VALUES (16);" ,
760
800
] ;
761
801
@@ -1287,20 +1327,38 @@ impl SignerDb {
1287
1327
pub fn add_block_signature (
1288
1328
& self ,
1289
1329
block_sighash : & Sha512Trunc256Sum ,
1330
+ signer_addr : & StacksAddress ,
1290
1331
signature : & MessageSignature ,
1291
- ) -> Result < ( ) , DBError > {
1292
- let qry = "INSERT OR REPLACE INTO block_signatures (signer_signature_hash, signature) VALUES (?1, ?2);" ;
1332
+ ) -> Result < bool , DBError > {
1333
+ // Remove any block rejection entry for this signer and block hash
1334
+ let del_qry = "DELETE FROM block_rejection_signer_addrs WHERE signer_signature_hash = ?1 AND signer_addr = ?2" ;
1335
+ let del_args = params ! [ block_sighash, signer_addr. to_string( ) ] ;
1336
+ self . db . execute ( del_qry, del_args) ?;
1337
+
1338
+ // Insert the block signature
1339
+ let qry = "INSERT OR IGNORE INTO block_signatures (signer_signature_hash, signer_addr, signature) VALUES (?1, ?2, ?3);" ;
1293
1340
let args = params ! [
1294
1341
block_sighash,
1342
+ signer_addr. to_string( ) ,
1295
1343
serde_json:: to_string( signature) . map_err( DBError :: SerializationError ) ?
1296
1344
] ;
1297
-
1298
- debug ! ( "Inserting block signature." ;
1299
- "signer_signature_hash" => %block_sighash,
1300
- "signature" => %signature) ;
1301
-
1302
- self . db . execute ( qry, args) ?;
1303
- Ok ( ( ) )
1345
+ let rows_added = self . db . execute ( qry, args) ?;
1346
+
1347
+ let is_new_signature = rows_added > 0 ;
1348
+ if is_new_signature {
1349
+ debug ! ( "Added block signature." ;
1350
+ "signer_signature_hash" => %block_sighash,
1351
+ "signer_address" => %signer_addr,
1352
+ "signature" => %signature
1353
+ ) ;
1354
+ } else {
1355
+ debug ! ( "Duplicate block signature." ;
1356
+ "signer_signature_hash" => %block_sighash,
1357
+ "signer_address" => %signer_addr,
1358
+ "signature" => %signature
1359
+ ) ;
1360
+ }
1361
+ Ok ( is_new_signature)
1304
1362
}
1305
1363
1306
1364
/// Get all signatures for a block
@@ -1323,22 +1381,63 @@ impl SignerDb {
1323
1381
block_sighash : & Sha512Trunc256Sum ,
1324
1382
addr : & StacksAddress ,
1325
1383
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
- ] ;
1333
-
1334
- debug ! ( "Inserting block rejection." ;
1335
- "signer_signature_hash " => %block_sighash ,
1336
- "signer_address" => %addr ,
1337
- "reject_reason" => %reject_reason
1338
- ) ;
1384
+ ) -> Result < bool , DBError > {
1385
+ // If this signer/block already has a signature, do not allow a rejection
1386
+ let sig_qry = "SELECT EXISTS(SELECT 1 FROM block_signatures WHERE signer_signature_hash = ?1 AND signer_addr = ?2)" ;
1387
+ let sig_args = params ! [ block_sighash, addr . to_string ( ) ] ;
1388
+ let exists = self . db . query_row ( sig_qry , sig_args , |row| row . get ( 0 ) ) ? ;
1389
+ if exists {
1390
+ warn ! ( "Cannot add block rejection because a signature already exists." ;
1391
+ "signer_signature_hash" => %block_sighash ,
1392
+ "signer_address" => %addr ,
1393
+ "reject_reason " => %reject_reason
1394
+ ) ;
1395
+ return Ok ( false ) ;
1396
+ }
1339
1397
1340
- self . db . execute ( qry, args) ?;
1341
- Ok ( ( ) )
1398
+ // Check if a row exists for this sighash/signer combo
1399
+ let qry = "SELECT reject_code FROM block_rejection_signer_addrs WHERE signer_signature_hash = ?1 AND signer_addr = ?2 LIMIT 1" ;
1400
+ let args = params ! [ block_sighash, addr. to_string( ) ] ;
1401
+ let existing_code: Option < i64 > =
1402
+ self . db . query_row ( qry, args, |row| row. get ( 0 ) ) . optional ( ) ?;
1403
+
1404
+ let reject_code = RejectReasonPrefix :: from ( reject_reason) as i64 ;
1405
+
1406
+ match existing_code {
1407
+ Some ( code) if code == reject_code => {
1408
+ // Row exists with same reject_reason, do nothing
1409
+ debug ! ( "Duplicate block rejection." ;
1410
+ "signer_signature_hash" => %block_sighash,
1411
+ "signer_address" => %addr,
1412
+ "reject_reason" => %reject_reason
1413
+ ) ;
1414
+ Ok ( false )
1415
+ }
1416
+ Some ( _) => {
1417
+ // Row exists but with different reject_reason, update it
1418
+ let update_qry = "UPDATE block_rejection_signer_addrs SET reject_code = ?1 WHERE signer_signature_hash = ?2 AND signer_addr = ?3" ;
1419
+ let update_args = params ! [ reject_code, block_sighash, addr. to_string( ) ] ;
1420
+ self . db . execute ( update_qry, update_args) ?;
1421
+ debug ! ( "Updated block rejection reason." ;
1422
+ "signer_signature_hash" => %block_sighash,
1423
+ "signer_address" => %addr,
1424
+ "reject_reason" => %reject_reason
1425
+ ) ;
1426
+ Ok ( true )
1427
+ }
1428
+ None => {
1429
+ // Row does not exist, insert it
1430
+ let insert_qry = "INSERT INTO block_rejection_signer_addrs (signer_signature_hash, signer_addr, reject_code) VALUES (?1, ?2, ?3)" ;
1431
+ let insert_args = params ! [ block_sighash, addr. to_string( ) , reject_code] ;
1432
+ self . db . execute ( insert_qry, insert_args) ?;
1433
+ debug ! ( "Inserted block rejection." ;
1434
+ "signer_signature_hash" => %block_sighash,
1435
+ "signer_address" => %addr,
1436
+ "reject_reason" => %reject_reason
1437
+ ) ;
1438
+ Ok ( true )
1439
+ }
1440
+ }
1342
1441
}
1343
1442
1344
1443
/// Get all signer addresses that rejected the block (and their reject codes)
@@ -2090,19 +2189,175 @@ pub mod tests {
2090
2189
let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2091
2190
2092
2191
let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2192
+ let address1 = StacksAddress :: burn_address ( false ) ;
2193
+ let address2 = StacksAddress :: burn_address ( true ) ;
2093
2194
let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2094
2195
let sig2 = MessageSignature ( [ 0x22 ; 65 ] ) ;
2095
2196
2096
2197
assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2097
2198
2098
- db. add_block_signature ( & block_id, & sig1) . unwrap ( ) ;
2199
+ db. add_block_signature ( & block_id, & address1 , & sig1) . unwrap ( ) ;
2099
2200
assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2100
2201
2101
- db. add_block_signature ( & block_id, & sig2) . unwrap ( ) ;
2202
+ db. add_block_signature ( & block_id, & address2 , & sig2) . unwrap ( ) ;
2102
2203
assert_eq ! (
2103
2204
db. get_block_signatures( & block_id) . unwrap( ) ,
2104
- vec![ sig1, sig2]
2205
+ vec![ sig2, sig1]
2206
+ ) ;
2207
+ }
2208
+
2209
+ #[ test]
2210
+ fn duplicate_block_signatures ( ) {
2211
+ let db_path = tmp_db_path ( ) ;
2212
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2213
+
2214
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2215
+ let address = StacksAddress :: burn_address ( false ) ;
2216
+ let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2217
+ let sig2 = MessageSignature ( [ 0x22 ; 65 ] ) ;
2218
+
2219
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2220
+
2221
+ assert ! ( db. add_block_signature( & block_id, & address, & sig1) . unwrap( ) ) ;
2222
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2223
+
2224
+ assert ! ( !db. add_block_signature( & block_id, & address, & sig2) . unwrap( ) ) ;
2225
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2226
+ }
2227
+
2228
+ #[ test]
2229
+ fn add_and_get_block_rejections ( ) {
2230
+ let db_path = tmp_db_path ( ) ;
2231
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2232
+
2233
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2234
+ let address1 = StacksAddress :: burn_address ( false ) ;
2235
+ let address2 = StacksAddress :: burn_address ( true ) ;
2236
+
2237
+ assert_eq ! (
2238
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2239
+ vec![ ]
2105
2240
) ;
2241
+
2242
+ assert ! ( db
2243
+ . add_block_rejection_signer_addr(
2244
+ & block_id,
2245
+ & address1,
2246
+ & RejectReason :: DuplicateBlockFound ,
2247
+ )
2248
+ . unwrap( ) ) ;
2249
+ assert_eq ! (
2250
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2251
+ vec![ ( address1, RejectReasonPrefix :: DuplicateBlockFound ) ]
2252
+ ) ;
2253
+
2254
+ assert ! ( db
2255
+ . add_block_rejection_signer_addr(
2256
+ & block_id,
2257
+ & address2,
2258
+ & RejectReason :: InvalidParentBlock
2259
+ )
2260
+ . unwrap( ) ) ;
2261
+ assert_eq ! (
2262
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2263
+ vec![
2264
+ ( address2, RejectReasonPrefix :: InvalidParentBlock ) ,
2265
+ ( address1, RejectReasonPrefix :: DuplicateBlockFound ) ,
2266
+ ]
2267
+ ) ;
2268
+ }
2269
+
2270
+ #[ test]
2271
+ fn duplicate_block_rejections ( ) {
2272
+ let db_path = tmp_db_path ( ) ;
2273
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2274
+
2275
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2276
+ let address = StacksAddress :: burn_address ( false ) ;
2277
+
2278
+ assert_eq ! (
2279
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2280
+ vec![ ]
2281
+ ) ;
2282
+
2283
+ assert ! ( db
2284
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidParentBlock )
2285
+ . unwrap( ) ) ;
2286
+ assert_eq ! (
2287
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2288
+ vec![ ( address, RejectReasonPrefix :: InvalidParentBlock ) ]
2289
+ ) ;
2290
+
2291
+ assert ! ( db
2292
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidMiner )
2293
+ . unwrap( ) ) ;
2294
+ assert_eq ! (
2295
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2296
+ vec![ ( address, RejectReasonPrefix :: InvalidMiner ) ]
2297
+ ) ;
2298
+
2299
+ assert ! ( !db
2300
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidMiner )
2301
+ . unwrap( ) ) ;
2302
+ assert_eq ! (
2303
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2304
+ vec![ ( address, RejectReasonPrefix :: InvalidMiner ) ]
2305
+ ) ;
2306
+ }
2307
+
2308
+ #[ test]
2309
+ fn reject_then_accept ( ) {
2310
+ let db_path = tmp_db_path ( ) ;
2311
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2312
+
2313
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2314
+ let address = StacksAddress :: burn_address ( false ) ;
2315
+ let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2316
+
2317
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2318
+
2319
+ assert ! ( db
2320
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidParentBlock )
2321
+ . unwrap( ) ) ;
2322
+ assert_eq ! (
2323
+ db. get_block_rejection_signer_addrs( & block_id) . unwrap( ) ,
2324
+ vec![ ( address, RejectReasonPrefix :: InvalidParentBlock ) ]
2325
+ ) ;
2326
+
2327
+ assert ! ( db. add_block_signature( & block_id, & address, & sig1) . unwrap( ) ) ;
2328
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2329
+ assert ! ( db
2330
+ . get_block_rejection_signer_addrs( & block_id)
2331
+ . unwrap( )
2332
+ . is_empty( ) ) ;
2333
+ }
2334
+
2335
+ #[ test]
2336
+ fn accept_then_reject ( ) {
2337
+ let db_path = tmp_db_path ( ) ;
2338
+ let db = SignerDb :: new ( db_path) . expect ( "Failed to create signer db" ) ;
2339
+
2340
+ let block_id = Sha512Trunc256Sum :: from_data ( "foo" . as_bytes ( ) ) ;
2341
+ let address = StacksAddress :: burn_address ( false ) ;
2342
+ let sig1 = MessageSignature ( [ 0x11 ; 65 ] ) ;
2343
+
2344
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ ] ) ;
2345
+
2346
+ assert ! ( db. add_block_signature( & block_id, & address, & sig1) . unwrap( ) ) ;
2347
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2348
+ assert ! ( db
2349
+ . get_block_rejection_signer_addrs( & block_id)
2350
+ . unwrap( )
2351
+ . is_empty( ) ) ;
2352
+
2353
+ assert ! ( !db
2354
+ . add_block_rejection_signer_addr( & block_id, & address, & RejectReason :: InvalidParentBlock )
2355
+ . unwrap( ) ) ;
2356
+ assert_eq ! ( db. get_block_signatures( & block_id) . unwrap( ) , vec![ sig1] ) ;
2357
+ assert ! ( db
2358
+ . get_block_rejection_signer_addrs( & block_id)
2359
+ . unwrap( )
2360
+ . is_empty( ) ) ;
2106
2361
}
2107
2362
2108
2363
#[ test]
0 commit comments