Skip to content

Commit c7dc731

Browse files
committed
WIP: 2-phase commit block signing
Signed-off-by: Jacinta Ferrant <[email protected]>
1 parent 3dde4d8 commit c7dc731

File tree

8 files changed

+289
-37
lines changed

8 files changed

+289
-37
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
99

1010
### Added
1111

12+
- Added `SignerMessage::PreBlockCommit` for 2-phase commit block signing
1213
- Added field `vm_error` to EventObserver transaction outputs
1314
- Added new `ValidateRejectCode` values to the `/v3/block_proposal` endpoint
1415
- Added `StateMachineUpdateContent::V1` to support a vector of `StacksTransaction` expected to be replayed in subsequent Stacks blocks

libsigner/src/v0/messages.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ MessageSlotID {
8989
/// Block Response message from signers
9090
BlockResponse = 1,
9191
/// Signer State Machine Update
92-
StateMachineUpdate = 2
92+
StateMachineUpdate = 2,
93+
/// Block Pre-commit message from signers before they commit to a block response
94+
BlockPreCommit = 3
9395
});
9496

9597
define_u8_enum!(
@@ -132,7 +134,9 @@ SignerMessageTypePrefix {
132134
/// Mock block message from Epoch 2.5 miners
133135
MockBlock = 5,
134136
/// State machine update
135-
StateMachineUpdate = 6
137+
StateMachineUpdate = 6,
138+
/// Block Pre-commit message
139+
BlockPreCommit = 7
136140
});
137141

138142
#[cfg_attr(test, mutants::skip)]
@@ -155,7 +159,7 @@ impl MessageSlotID {
155159
#[cfg_attr(test, mutants::skip)]
156160
impl Display for MessageSlotID {
157161
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158-
write!(f, "{:?}({})", self, self.to_u8())
162+
write!(f, "{self:?}({})", self.to_u8())
159163
}
160164
}
161165

@@ -179,6 +183,7 @@ impl From<&SignerMessage> for SignerMessageTypePrefix {
179183
SignerMessage::MockSignature(_) => SignerMessageTypePrefix::MockSignature,
180184
SignerMessage::MockBlock(_) => SignerMessageTypePrefix::MockBlock,
181185
SignerMessage::StateMachineUpdate(_) => SignerMessageTypePrefix::StateMachineUpdate,
186+
SignerMessage::BlockPreCommit(_) => SignerMessageTypePrefix::BlockPreCommit,
182187
}
183188
}
184189
}
@@ -200,6 +205,8 @@ pub enum SignerMessage {
200205
MockBlock(MockBlock),
201206
/// A state machine update
202207
StateMachineUpdate(StateMachineUpdate),
208+
/// The pre commit message from signers for other signers to observe
209+
BlockPreCommit(Sha512Trunc256Sum),
203210
}
204211

205212
impl SignerMessage {
@@ -215,6 +222,7 @@ impl SignerMessage {
215222
| Self::MockBlock(_) => None,
216223
Self::BlockResponse(_) | Self::MockSignature(_) => Some(MessageSlotID::BlockResponse), // Mock signature uses the same slot as block response since its exclusively for epoch 2.5 testing
217224
Self::StateMachineUpdate(_) => Some(MessageSlotID::StateMachineUpdate),
225+
Self::BlockPreCommit(_) => Some(MessageSlotID::BlockPreCommit),
218226
}
219227
}
220228
}
@@ -234,6 +242,9 @@ impl StacksMessageCodec for SignerMessage {
234242
SignerMessage::StateMachineUpdate(state_machine_update) => {
235243
state_machine_update.consensus_serialize(fd)
236244
}
245+
SignerMessage::BlockPreCommit(signer_signature_hash) => {
246+
signer_signature_hash.consensus_serialize(fd)
247+
}
237248
}?;
238249
Ok(())
239250
}
@@ -271,6 +282,10 @@ impl StacksMessageCodec for SignerMessage {
271282
let state_machine_update = StacksMessageCodec::consensus_deserialize(fd)?;
272283
SignerMessage::StateMachineUpdate(state_machine_update)
273284
}
285+
SignerMessageTypePrefix::BlockPreCommit => {
286+
let signer_signature_hash = StacksMessageCodec::consensus_deserialize(fd)?;
287+
SignerMessage::BlockPreCommit(signer_signature_hash)
288+
}
274289
};
275290
Ok(message)
276291
}

stacks-signer/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added `SignerMessage::BlockPreCommit` message handling; signers now collect until a threshold is reached before issuing a block signature, implementing a proper 2-phase commit.
13+
1014
### Changed
1115

1216
- Upgraded `SUPPORTED_SIGNER_PROTOCOL_VERSION` to 1

stacks-signer/src/monitoring/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ pub mod actions {
7171
BLOCK_RESPONSES_SENT.with_label_values(&[label_value]).inc();
7272
}
7373

74+
/// Increment the block pre-commit sent counter
75+
pub fn increment_block_pre_commits_sent() {
76+
BLOCK_PRE_COMMITS_SENT.inc();
77+
}
78+
7479
/// Increment the number of block proposals received
7580
pub fn increment_block_proposals_received() {
7681
BLOCK_PROPOSALS_RECEIVED.inc();
@@ -173,6 +178,9 @@ pub mod actions {
173178
/// Increment the block responses sent counter
174179
pub fn increment_block_responses_sent(_accepted: bool) {}
175180

181+
/// Increment the block pre-commits sent counter
182+
pub fn increment_block_pre_commits_sent() {}
183+
176184
/// Increment the number of block proposals received
177185
pub fn increment_block_proposals_received() {}
178186

stacks-signer/src/monitoring/prometheus.rs

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ lazy_static! {
4343
&["response_type"]
4444
)
4545
.unwrap();
46+
pub static ref BLOCK_PRE_COMMITS_SENT: IntCounter = register_int_counter!(opts!(
47+
"stacks_signer_block_pre_commits_sent",
48+
"The number of block pre-commits sent by the signer"
49+
))
50+
.unwrap();
4651
pub static ref BLOCK_PROPOSALS_RECEIVED: IntCounter = register_int_counter!(opts!(
4752
"stacks_signer_block_proposals_received",
4853
"The number of block proposals received by the signer"

stacks-signer/src/signerdb.rs

+113-2
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,18 @@ CREATE TABLE IF NOT EXISTS signer_state_machine_updates (
532532
PRIMARY KEY (signer_addr, reward_cycle)
533533
) STRICT;"#;
534534

535+
static CREATE_BLOCK_PRE_COMMITS_TABLE: &str = r#"
536+
CREATE TABLE IF NOT EXISTS block_pre_commits (
537+
-- The block sighash commits to all of the stacks and burnchain state as of its parent,
538+
-- as well as the tenure itself so there's no need to include the reward cycle. Just
539+
-- the sighash is sufficient to uniquely identify the block across all burnchain, PoX,
540+
-- and stacks forks.
541+
signer_signature_hash TEXT NOT NULL,
542+
-- signer address committing to sign the block
543+
signer_addr TEXT NOT NULL,
544+
PRIMARY KEY (signer_signature_hash, signer_addr)
545+
) STRICT;"#;
546+
535547
static SCHEMA_1: &[&str] = &[
536548
DROP_SCHEMA_0,
537549
CREATE_DB_CONFIG,
@@ -613,9 +625,14 @@ static SCHEMA_11: &[&str] = &[
613625
"INSERT INTO db_config (version) VALUES (11);",
614626
];
615627

628+
static SCHEMA_12: &[&str] = &[
629+
CREATE_BLOCK_PRE_COMMITS_TABLE,
630+
"INSERT INTO db_config (version) VALUES (12);",
631+
];
632+
616633
impl SignerDb {
617634
/// The current schema version used in this build of the signer binary.
618-
pub const SCHEMA_VERSION: u32 = 11;
635+
pub const SCHEMA_VERSION: u32 = 12;
619636

620637
/// Create a new `SignerState` instance.
621638
/// This will create a new SQLite database at the given path
@@ -799,6 +816,20 @@ impl SignerDb {
799816
Ok(())
800817
}
801818

819+
/// Migrate from schema 11 to schema 12
820+
fn schema_12_migration(tx: &Transaction) -> Result<(), DBError> {
821+
if Self::get_schema_version(tx)? >= 12 {
822+
// no migration necessary
823+
return Ok(());
824+
}
825+
826+
for statement in SCHEMA_12.iter() {
827+
tx.execute_batch(statement)?;
828+
}
829+
830+
Ok(())
831+
}
832+
802833
/// Register custom scalar functions used by the database
803834
fn register_scalar_functions(&self) -> Result<(), DBError> {
804835
// Register helper function for determining if a block is a tenure change transaction
@@ -843,7 +874,8 @@ impl SignerDb {
843874
8 => Self::schema_9_migration(&sql_tx)?,
844875
9 => Self::schema_10_migration(&sql_tx)?,
845876
10 => Self::schema_11_migration(&sql_tx)?,
846-
11 => break,
877+
11 => Self::schema_12_migration(&sql_tx)?,
878+
12 => break,
847879
x => return Err(DBError::Other(format!(
848880
"Database schema is newer than supported by this binary. Expected version = {}, Database version = {x}",
849881
Self::SCHEMA_VERSION,
@@ -1412,6 +1444,39 @@ impl SignerDb {
14121444
}
14131445
Ok(result)
14141446
}
1447+
1448+
/// Record an observed block pre commit
1449+
pub fn add_block_pre_commit(
1450+
&self,
1451+
block_sighash: &Sha512Trunc256Sum,
1452+
address: &StacksAddress,
1453+
) -> Result<(), DBError> {
1454+
let qry = "INSERT OR REPLACE INTO block_pre_commits (signer_signature_hash, signer_addr) VALUES (?1, ?2);";
1455+
let args = params![block_sighash, address.to_string()];
1456+
1457+
debug!("Inserting block pre commit.";
1458+
"signer_signature_hash" => %block_sighash,
1459+
"signer_addr" => %address);
1460+
1461+
self.db.execute(qry, args)?;
1462+
Ok(())
1463+
}
1464+
1465+
/// Get all pre committers for a block
1466+
pub fn get_block_pre_committers(
1467+
&self,
1468+
block_sighash: &Sha512Trunc256Sum,
1469+
) -> Result<Vec<StacksAddress>, DBError> {
1470+
let qry = "SELECT signer_addr FROM block_pre_commits WHERE signer_signature_hash = ?1";
1471+
let args = params![block_sighash];
1472+
let addrs_txt: Vec<String> = query_rows(&self.db, qry, args)?;
1473+
1474+
let res: Result<Vec<_>, _> = addrs_txt
1475+
.into_iter()
1476+
.map(|addr| StacksAddress::from_string(&addr).ok_or(DBError::Corruption))
1477+
.collect();
1478+
res
1479+
}
14151480
}
14161481

14171482
fn try_deserialize<T>(s: Option<String>) -> Result<Option<T>, DBError>
@@ -2515,4 +2580,50 @@ pub mod tests {
25152580
assert_eq!(updates.get(&address_2), None);
25162581
assert_eq!(updates.get(&address_3), Some(&update_3));
25172582
}
2583+
2584+
#[test]
2585+
fn insert_and_get_state_block_pre_commits() {
2586+
let db_path = tmp_db_path();
2587+
let db = SignerDb::new(db_path).expect("Failed to create signer db");
2588+
let block_sighash1 = Sha512Trunc256Sum([1u8; 32]);
2589+
let address1 = StacksAddress::p2pkh(
2590+
false,
2591+
&StacksPublicKey::from_private(&StacksPrivateKey::random()),
2592+
);
2593+
let block_sighash2 = Sha512Trunc256Sum([2u8; 32]);
2594+
let address2 = StacksAddress::p2pkh(
2595+
false,
2596+
&StacksPublicKey::from_private(&StacksPrivateKey::random()),
2597+
);
2598+
let address3 = StacksAddress::p2pkh(
2599+
false,
2600+
&StacksPublicKey::from_private(&StacksPrivateKey::random()),
2601+
);
2602+
assert!(db
2603+
.get_block_pre_committers(&block_sighash1)
2604+
.unwrap()
2605+
.is_empty());
2606+
2607+
db.add_block_pre_commit(&block_sighash1, &address1).unwrap();
2608+
assert_eq!(
2609+
db.get_block_pre_committers(&block_sighash1).unwrap(),
2610+
vec![address1]
2611+
);
2612+
2613+
db.add_block_pre_commit(&block_sighash1, &address2).unwrap();
2614+
assert_eq!(
2615+
db.get_block_pre_committers(&block_sighash1).unwrap(),
2616+
vec![address2, address1]
2617+
);
2618+
2619+
db.add_block_pre_commit(&block_sighash2, &address3).unwrap();
2620+
assert_eq!(
2621+
db.get_block_pre_committers(&block_sighash1).unwrap(),
2622+
vec![address2, address1]
2623+
);
2624+
assert_eq!(
2625+
db.get_block_pre_committers(&block_sighash2).unwrap(),
2626+
vec![address3]
2627+
);
2628+
}
25182629
}

0 commit comments

Comments
 (0)