Skip to content

Commit bbab9e9

Browse files
authored
Merge pull request #6284 from obycode/feat/signature-management
Do not count accept and reject from same signer
2 parents 0accad8 + 7f76bce commit bbab9e9

File tree

3 files changed

+304
-34
lines changed

3 files changed

+304
-34
lines changed

stacks-signer/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1313
- Introduced `capitulate_miner_view_timeout_secs`: the duration (in seconds) for the signer to wait between updating the local state machine viewpoint and capitulating to other signers' miner views.
1414
- Added codepath to enable signers to evaluate block proposals and miner activity against global signer state for improved consistency and correctness. Currently feature gated behind the `SUPPORTED_SIGNER_PROTOCOL_VERSION`
1515

16+
### Changed
17+
18+
- Do not count both a block acceptance and a block rejection for the same signer/block. Also ignore repeated responses (mainly for logging purposes).
19+
1620
## [3.1.0.0.13.0]
1721

1822
### Changed

stacks-signer/src/signerdb.rs

Lines changed: 283 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use stacks_common::types::chainstate::ConsensusHash;
4040
use stacks_common::util::get_epoch_time_secs;
4141
use stacks_common::util::hash::Sha512Trunc256Sum;
4242
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};
4444

4545
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4646
/// 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
649649
CREATE INDEX idx_signer_addr_reward_cycle ON signer_state_machine_updates(reward_cycle, signer_addr, received_time DESC);
650650
"#;
651651

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+
652688
static SCHEMA_1: &[&str] = &[
653689
DROP_SCHEMA_0,
654690
CREATE_DB_CONFIG,
@@ -756,6 +792,10 @@ static SCHEMA_16: &[&str] = &[
756792
ADD_SIGNER_STATE_MACHINE_UPDATES_BURN_BLOCK_CONSENSUS_HASH_INDEX,
757793
ADD_SIGNER_STATE_MACHINE_UPDATES_RECEIVED_TIME_INDEX,
758794
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,
759799
"INSERT INTO db_config (version) VALUES (16);",
760800
];
761801

@@ -1287,20 +1327,38 @@ impl SignerDb {
12871327
pub fn add_block_signature(
12881328
&self,
12891329
block_sighash: &Sha512Trunc256Sum,
1330+
signer_addr: &StacksAddress,
12901331
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);";
12931340
let args = params![
12941341
block_sighash,
1342+
signer_addr.to_string(),
12951343
serde_json::to_string(signature).map_err(DBError::SerializationError)?
12961344
];
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)
13041362
}
13051363

13061364
/// Get all signatures for a block
@@ -1323,22 +1381,63 @@ impl SignerDb {
13231381
block_sighash: &Sha512Trunc256Sum,
13241382
addr: &StacksAddress,
13251383
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+
}
13391397

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+
}
13421441
}
13431442

13441443
/// Get all signer addresses that rejected the block (and their reject codes)
@@ -2090,19 +2189,175 @@ pub mod tests {
20902189
let db = SignerDb::new(db_path).expect("Failed to create signer db");
20912190

20922191
let block_id = Sha512Trunc256Sum::from_data("foo".as_bytes());
2192+
let address1 = StacksAddress::burn_address(false);
2193+
let address2 = StacksAddress::burn_address(true);
20932194
let sig1 = MessageSignature([0x11; 65]);
20942195
let sig2 = MessageSignature([0x22; 65]);
20952196

20962197
assert_eq!(db.get_block_signatures(&block_id).unwrap(), vec![]);
20972198

2098-
db.add_block_signature(&block_id, &sig1).unwrap();
2199+
db.add_block_signature(&block_id, &address1, &sig1).unwrap();
20992200
assert_eq!(db.get_block_signatures(&block_id).unwrap(), vec![sig1]);
21002201

2101-
db.add_block_signature(&block_id, &sig2).unwrap();
2202+
db.add_block_signature(&block_id, &address2, &sig2).unwrap();
21022203
assert_eq!(
21032204
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![]
21052240
);
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());
21062361
}
21072362

21082363
#[test]

0 commit comments

Comments
 (0)