Skip to content

Commit 9b0d666

Browse files
feat: Use chain lock sigs in Masternode list diffs instead of requesting them (#64)
* quorum sigs from message * quorum sigs from message * quorum sigs from message * more work * more work * more work * more work * more work * work to get this working * change * change * clean up * fixes * bls8 * fmt * bump to version 39
1 parent 99425df commit 9b0d666

24 files changed

+682
-251
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["dash", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc
33
resolver = "2"
44

55
[workspace.package]
6-
version = "0.38.0"
6+
version = "0.39.0"
77

88
[patch.crates-io.dashcore_hashes]
99
path = "hashes"

dash/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,9 @@ actual-serde = { package = "serde", version = "1.0.103", default-features = fals
5959
base64-compat = { version = "1.0.0", optional = true }
6060
bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true }
6161
hex_lit = "0.1.1"
62-
6362
anyhow = { version= "1.0" }
6463
hex = { version= "0.4" }
65-
bincode = { version= "2.0.0-rc.3", optional = true }
64+
bincode = { version= "=2.0.0-rc.3", optional = true }
6665
bitflags = "2.6.0"
6766
blsful = { version = "3.0.0-pre8", optional = true }
6867
serde_repr = "0.1.19"

dash/src/network/message_qrinfo.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ impl_consensus_encoding!(GetQRInfo, base_block_hashes, block_request_hash, extra
3333
///
3434
/// Note: The “compact size” integers that prefix some arrays are handled by your consensus encoding routines.
3535
#[derive(PartialEq, Eq, Clone, Debug)]
36+
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
37+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
38+
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
3639
pub struct QRInfo {
3740
// Quorum snapshots for heights h-c, h-2c, h-3c.
3841
pub quorum_snapshot_at_h_minus_c: QuorumSnapshot,
@@ -322,8 +325,15 @@ mod tests {
322325
fn deserialize_qr_info() {
323326
let block_hex = include_str!("../../tests/data/test_DML_diffs/QR_INFO_0_2224359.hex");
324327
let data = hex::decode(block_hex).expect("decode hex");
325-
let qr_info: RawNetworkMessage = deserialize(&data).expect("deserialize QR_INFO");
326-
327-
assert_matches!(qr_info, RawNetworkMessage { magic, payload: NetworkMessage::QRInfo(_) } if magic == 3177909439);
328+
let network_qr_info: RawNetworkMessage = deserialize(&data).expect("deserialize QR_INFO");
329+
330+
let RawNetworkMessage {
331+
magic,
332+
payload: NetworkMessage::QRInfo(qr_info),
333+
} = network_qr_info
334+
else {
335+
panic!("expected qr_info message");
336+
};
337+
assert_eq!(magic, 3177909439);
328338
}
329339
}

dash/src/sml/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ pub enum SmlError {
4343
/// Error indicating that a required feature is not turned on.
4444
#[error("Feature not turned on: {0}")]
4545
FeatureNotTurnedOn(String),
46+
47+
/// Error indicating that an invalid index was provided in the signature set.
48+
#[error("Invalid index in quorum signature set: {0}")]
49+
InvalidIndexInSignatureSet(u16),
50+
51+
/// Error indicating the quorum signature set is incomplete (some slots were not filled).
52+
#[error("Incomplete quorum signature set; not all slots were filled")]
53+
IncompleteSignatureSet,
4654
}

dash/src/sml/masternode_list/apply_diff.rs

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
use crate::bls_sig_utils::BLSSignature;
12
use crate::network::message_sml::MnListDiff;
23
use crate::prelude::CoreBlockHeight;
34
use crate::sml::error::SmlError;
5+
use crate::sml::llmq_entry_verification::{
6+
LLMQEntryVerificationSkipStatus, LLMQEntryVerificationStatus,
7+
};
48
use crate::sml::masternode_list::MasternodeList;
9+
use crate::sml::quorum_entry::qualified_quorum_entry::{
10+
QualifiedQuorumEntry, VerifyingChainLockSignaturesType,
11+
};
512

613
impl MasternodeList {
714
/// Applies an `MnListDiff` to update the current masternode list.
@@ -32,7 +39,8 @@ impl MasternodeList {
3239
&self,
3340
diff: MnListDiff,
3441
diff_end_height: CoreBlockHeight,
35-
) -> Result<MasternodeList, SmlError> {
42+
previous_chain_lock_sigs: Option<[BLSSignature; 3]>,
43+
) -> Result<(MasternodeList, Option<BLSSignature>), SmlError> {
3644
// Ensure the base block hash matches
3745
if self.block_hash != diff.base_block_hash {
3846
return Err(SmlError::BaseBlockHashMismatch {
@@ -67,12 +75,73 @@ impl MasternodeList {
6775
}
6876
}
6977

78+
// Build a vector of optional signatures with slots matching new_quorums length
79+
let mut quorum_sig_lookup: Vec<Option<&BLSSignature>> = vec![None; diff.new_quorums.len()];
80+
81+
// Fill each slot with the corresponding signature
82+
for quorum_sig_obj in &diff.quorums_chainlock_signatures {
83+
for &index in &quorum_sig_obj.index_set {
84+
if let Some(slot) = quorum_sig_lookup.get_mut(index as usize) {
85+
*slot = Some(&quorum_sig_obj.signature);
86+
} else {
87+
return Err(SmlError::InvalidIndexInSignatureSet(index));
88+
}
89+
}
90+
}
91+
92+
// Verify all slots have been filled
93+
if quorum_sig_lookup.iter().any(Option::is_none) {
94+
return Err(SmlError::IncompleteSignatureSet);
95+
}
96+
97+
let mut rotating_sig = None;
98+
7099
// Add or update new quorums
71-
for new_quorum in diff.new_quorums {
72-
updated_quorums
73-
.entry(new_quorum.llmq_type)
74-
.or_default()
75-
.insert(new_quorum.quorum_hash, new_quorum.into());
100+
for (idx, new_quorum) in diff.new_quorums.into_iter().enumerate() {
101+
updated_quorums.entry(new_quorum.llmq_type).or_default().insert(
102+
new_quorum.quorum_hash,
103+
{
104+
let commitment_hash = new_quorum.calculate_commitment_hash();
105+
let entry_hash = new_quorum.calculate_entry_hash();
106+
let verifying_chain_lock_signature =
107+
if new_quorum.llmq_type.is_rotating_quorum_type() {
108+
if rotating_sig.is_none() {
109+
if let Some(sig) = quorum_sig_lookup[idx] {
110+
rotating_sig = Some(*sig)
111+
} else {
112+
return Err(SmlError::IncompleteSignatureSet);
113+
}
114+
}
115+
if let Some(previous_chain_lock_sigs) = previous_chain_lock_sigs {
116+
if let Some(sig) = quorum_sig_lookup[idx] {
117+
Some(VerifyingChainLockSignaturesType::Rotating([
118+
previous_chain_lock_sigs[0],
119+
previous_chain_lock_sigs[1],
120+
previous_chain_lock_sigs[2],
121+
*sig,
122+
]))
123+
} else {
124+
return Err(SmlError::IncompleteSignatureSet);
125+
}
126+
} else {
127+
None
128+
}
129+
} else {
130+
quorum_sig_lookup[idx]
131+
.copied()
132+
.map(VerifyingChainLockSignaturesType::NonRotating)
133+
};
134+
QualifiedQuorumEntry {
135+
quorum_entry: new_quorum,
136+
verified: LLMQEntryVerificationStatus::Skipped(
137+
LLMQEntryVerificationSkipStatus::NotMarkedForVerification,
138+
), // Default to unverified
139+
commitment_hash,
140+
entry_hash,
141+
verifying_chain_lock_signature,
142+
}
143+
},
144+
);
76145
}
77146

78147
// Create and return the new MasternodeList
@@ -83,6 +152,6 @@ impl MasternodeList {
83152
diff_end_height,
84153
);
85154

86-
Ok(builder.build())
155+
Ok((builder.build(), rotating_sig))
87156
}
88157
}

dash/src/sml/masternode_list/from_diff.rs

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::collections::BTreeMap;
22

3+
use crate::bls_sig_utils::BLSSignature;
34
use crate::network::message_sml::MnListDiff;
45
use crate::sml::error::SmlError;
56
use crate::sml::llmq_entry_verification::{
67
LLMQEntryVerificationSkipStatus, LLMQEntryVerificationStatus,
78
};
89
use crate::sml::masternode_list::MasternodeList;
9-
use crate::sml::quorum_entry::qualified_quorum_entry::QualifiedQuorumEntry;
10+
use crate::sml::quorum_entry::qualified_quorum_entry::{
11+
QualifiedQuorumEntry, VerifyingChainLockSignaturesType,
12+
};
1013
use crate::{BlockHash, Network};
1114

1215
pub trait TryFromWithBlockHashLookup<T>: Sized {
@@ -85,24 +88,50 @@ impl TryFromWithBlockHashLookup<MnListDiff> for MasternodeList {
8588
.map(|entry| (entry.pro_reg_tx_hash.reverse(), entry.into()))
8689
.collect::<BTreeMap<_, _>>();
8790

88-
let quorums = diff.new_quorums.into_iter().fold(BTreeMap::new(), |mut map, quorum| {
89-
map.entry(quorum.llmq_type.into()).or_insert_with(BTreeMap::new).insert(
90-
quorum.quorum_hash,
91-
{
92-
let entry_hash = quorum.calculate_entry_hash();
93-
let commitment_hash = quorum.calculate_commitment_hash();
94-
QualifiedQuorumEntry {
95-
quorum_entry: quorum,
96-
verified: LLMQEntryVerificationStatus::Skipped(
97-
LLMQEntryVerificationSkipStatus::NotMarkedForVerification,
98-
),
99-
commitment_hash,
100-
entry_hash,
101-
}
102-
},
103-
);
104-
map
105-
});
91+
// Build a vector of optional signatures with slots matching new_quorums length
92+
let mut quorum_sig_lookup: Vec<Option<&BLSSignature>> = vec![None; diff.new_quorums.len()];
93+
94+
// Fill each slot with the corresponding signature
95+
for quorum_sig_obj in &diff.quorums_chainlock_signatures {
96+
for &index in &quorum_sig_obj.index_set {
97+
if let Some(slot) = quorum_sig_lookup.get_mut(index as usize) {
98+
*slot = Some(&quorum_sig_obj.signature);
99+
} else {
100+
return Err(SmlError::InvalidIndexInSignatureSet(index));
101+
}
102+
}
103+
}
104+
105+
// Verify all slots have been filled
106+
if quorum_sig_lookup.iter().any(Option::is_none) {
107+
return Err(SmlError::IncompleteSignatureSet);
108+
}
109+
110+
let quorums = diff.new_quorums.into_iter().enumerate().fold(
111+
BTreeMap::new(),
112+
|mut map, (idx, quorum)| {
113+
map.entry(quorum.llmq_type.into()).or_insert_with(BTreeMap::new).insert(
114+
quorum.quorum_hash,
115+
{
116+
let entry_hash = quorum.calculate_entry_hash();
117+
let commitment_hash = quorum.calculate_commitment_hash();
118+
119+
QualifiedQuorumEntry {
120+
quorum_entry: quorum,
121+
verified: LLMQEntryVerificationStatus::Skipped(
122+
LLMQEntryVerificationSkipStatus::NotMarkedForVerification,
123+
),
124+
commitment_hash,
125+
entry_hash,
126+
verifying_chain_lock_signature: quorum_sig_lookup[idx]
127+
.copied()
128+
.map(VerifyingChainLockSignaturesType::NonRotating),
129+
}
130+
},
131+
);
132+
map
133+
},
134+
);
106135

107136
// Construct `MasternodeList`
108137
Ok(MasternodeList {

dash/src/sml/masternode_list/merkle_roots.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ impl MasternodeList {
6666
// we need to check that the coinbase is in the transaction hashes we got back
6767
// and is in the merkle block
6868
if let Some(mn_merkle_root) = self.masternode_merkle_root {
69-
//println!("has_valid_mn_list_root: {} == {}", tx.merkle_root_mn_list, mn_merkle_root);
7069
coinbase_payload.merkle_root_masternode_list == mn_merkle_root
7170
} else {
7271
false

dash/src/sml/masternode_list_engine/message_request_verification.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -365,15 +365,15 @@ mod tests {
365365
.expect("expected to decode")
366366
.0;
367367

368-
let lock_data = hex::decode("010133c404bebbf34c153816d26553bcec8c9b876354a68b952ab2c1c514c04baf9800000000578de219b47300c1d43985e1ee7af2faa773b87729df3cb48f2c522937c7b070d674ea572a713d6b07deef085b9ce97e1e354055b91b0bbd0b00000000000000a846486b3f75da24e3d04b62eadd7dffe589736f13ab222c208d0f3880dce5d287b8542dc1e1f0e271749e70e939262704f8611aafcdeb20ed70c5bc78fdf737a1bd6409d061fcf6a591b117ada7ba92567959544c090a05cdd955268d22be6b").expect("expected valid hex");
368+
let lock_data = hex::decode("01018d53e7997ead57409750942af0d5e0aafc06f852a9a52308f4781b6a8220298f00000000c6f9d8c63dd15937ea70aaddb7890daad42c91bf6818e2bf76d183d6f2d9215b4b5f84978fad9dde7ab52bdcc0674be891e9029cc1ef0cb01200000000000000a27c98836c4c04653ab81eb4e07ddfc2c8c2c1036b75247969c05a4f25451cd78913a971f1899d9f2bddec9cf8e0104004f72f20c2856453e5aa3bcd2a8200670ec28feda38f67cc400fc72ef1966956656ec0765478c9d16e9a9e470c07f9ed").expect("expected valid hex");
369369
let lock: InstantLock = deserialize(lock_data.as_slice()).expect("expected to deserialize");
370370
let request_id = lock.request_id().expect("expected to make request id");
371371
assert_eq!(
372372
hex::encode(request_id),
373-
"94dd5c8d946cb34dda43ebf424b385ae898159827385a48d8b1fae15dbf21a12"
373+
"481ca36cf80fde8fda333915e33c27014dad65fa9f3b54bc4d8bc45be7c81ddf"
374374
);
375375
let quorum_hash: QuorumHash = QuorumHash::from_slice(
376-
hex::decode("0000000000000019756ecc9c9c5f476d3f66876b1dcfa5dde1ea82f0d99334a2")
376+
hex::decode("00000000000000197368b224f2f01031991dd07aad0b43b2293a51fce8853ba0")
377377
.expect("expected bytes")
378378
.as_slice(),
379379
)
@@ -382,14 +382,14 @@ mod tests {
382382

383383
let (quorum, _, index) =
384384
mn_list_engine.is_lock_quorum(&lock).expect("expected to get quorum");
385-
assert_eq!(index, 4);
385+
assert_eq!(index, 23);
386386
assert_eq!(quorum.quorum_entry.quorum_hash, quorum_hash);
387387

388388
let sign_id =
389389
lock.sign_id(LLMQType::Llmqtype60_75, quorum_hash, None).expect("expected sign id");
390390
assert_eq!(
391391
hex::encode(sign_id),
392-
"0eaeba3a982f59144913b8d8150b2dfbb2dd2ba43bbcb54a3964a0d8d7ead62b"
392+
"6fcbf58004b118d865a448bf89d9299c64d4ecedd754dabec655090224de91cd"
393393
);
394394
mn_list_engine.verify_is_lock(&lock).expect("expected to verify is lock");
395395
}
@@ -406,18 +406,18 @@ mod tests {
406406

407407
let height = mn_list_engine.latest_masternode_list().expect("height").known_height;
408408

409-
assert_eq!(height, 2229204);
409+
assert_eq!(height, 2243493);
410410

411411
let chain_lock = ChainLock {
412-
block_height: 2229204,
413-
block_hash: BlockHash::from_slice(hex::decode("000000000000000c0568e1ffe5d14ed34cd19fccda425bc5923fc119c131ee0c").unwrap().as_slice()).unwrap().reverse(),
414-
signature: BLSSignature::from_hex("b6366341f97e1fd5a8dbc81e7f6c5c67acbed02bac2b7e10316f5c97fd07767e3544214386312687b7646d1b92539acc1069ecb3640e37d97fd2e5fdb2d37a3b6bf16b511bf22a41aa87038145caaeb38485d58d72b31add18deaf7e8c07684e").unwrap(),
412+
block_height: 2243495,
413+
block_hash: BlockHash::from_slice(hex::decode("000000000000000d88580463cafe168b2f465f40f01916ad95fe9be459c26491").unwrap().as_slice()).unwrap().reverse(),
414+
signature: BLSSignature::from_hex("a6bc4dcf7afb042e0b0258a994f5a77856971a32a3ad3ee89d21e1011a77211070bec7c2ef50c293722cbae135b904640b482479f836120e0be7d42ce332a7c58096d8d8006920ef3dbcc47b5f7ed00aeb68d58bc514f4401bd72b247bf23699").unwrap(),
415415
};
416416

417417
let request_id = chain_lock.request_id().expect("expected to make request id");
418418
assert_eq!(
419419
hex::encode(request_id),
420-
"88346d49ddf5c06528f3ede253e01a8ab54048935ff944d88bb149aaeef956f2"
420+
"969ab4a945632f5fba1331f3d2556d317682142cf8aaa6544e407e683c61a177"
421421
);
422422

423423
let quorum = mn_list_engine
@@ -426,7 +426,7 @@ mod tests {
426426
.expect("expected");
427427

428428
let expected_quorum_hash: QuorumHash = QuorumHash::from_slice(
429-
hex::decode("000000000000001de4e594585627fb5e2612ef983c913ee13add9a454e5faa6d")
429+
hex::decode("0000000000000012b00cefc19c02e991e84b67c0dc2bb57ade9dad8f97845f4b")
430430
.expect("expected bytes")
431431
.as_slice(),
432432
)
@@ -440,15 +440,15 @@ mod tests {
440440
// let's do another to make sure it wasn't a 1/4 fluke
441441

442442
let chain_lock = ChainLock {
443-
block_height: 2229203,
444-
block_hash: BlockHash::from_slice(hex::decode("0000000000000009ea873df6f4ab3bb1ea744e5630928cb8a4b4bd68aa16b18a").unwrap().as_slice()).unwrap().reverse(),
445-
signature: BLSSignature::from_hex("8723bf0a12abf41830936d8702ae57bb4f5ef7f816522f72e6da414081402d50a747a6307c91b15c0ee97fcfda60a7c50cdafa394c06b77299d9b9e4826fa1625c5436a2e3dc2e98df071dce92bbfc4445c87f7015b1914b17954b139dd8eafe").unwrap(),
443+
block_height: 2243496,
444+
block_hash: BlockHash::from_slice(hex::decode("000000000000001f9ff71c513c0ccef0c7c392f0df8bcb3c7c5764dcc1f4c89b").unwrap().as_slice()).unwrap().reverse(),
445+
signature: BLSSignature::from_hex("88270e60bee7dd9cea3c0a1b85e51d52f01e55a35033ef0434979b9121bc07ed8e45adae1f99e4d8fa2ea760920d844e1383030103b1c503cee45a2fcddc5cd7e73d1823d199e8231fadee2b3cadb1c6fc2ea255b988334b47d35ce865275699").unwrap(),
446446
};
447447

448448
let request_id = chain_lock.request_id().expect("expected to make request id");
449449
assert_eq!(
450450
hex::encode(request_id),
451-
"10a808c18307e3993dd4aa996d032b6b5d9d30a5d75ddf64fa10ea35ee63b2bb"
451+
"675aed91d6098cdf575cc09bfd1ff4f750acde1e793f385c3c72bbb400068d28"
452452
);
453453

454454
let quorum = mn_list_engine
@@ -457,7 +457,7 @@ mod tests {
457457
.expect("expected");
458458

459459
let expected_quorum_hash: QuorumHash = QuorumHash::from_slice(
460-
hex::decode("000000000000000bbd0b1bb95540351e7ee99c5b08efde076b3d712a57ea74d6")
460+
hex::decode("000000000000000c0e633b441b9e9c130732746c56ca3884220bab23b6c7ec6a")
461461
.expect("expected bytes")
462462
.as_slice(),
463463
)

0 commit comments

Comments
 (0)