Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["dash", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc
resolver = "2"

[workspace.package]
version = "0.38.0"
version = "0.39.0"

[patch.crates-io.dashcore_hashes]
path = "hashes"
Expand Down
3 changes: 1 addition & 2 deletions dash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,9 @@ actual-serde = { package = "serde", version = "1.0.103", default-features = fals
base64-compat = { version = "1.0.0", optional = true }
bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true }
hex_lit = "0.1.1"

anyhow = { version= "1.0" }
hex = { version= "0.4" }
bincode = { version= "2.0.0-rc.3", optional = true }
bincode = { version= "=2.0.0-rc.3", optional = true }
bitflags = "2.6.0"
blsful = { version = "3.0.0-pre8", optional = true }
serde_repr = "0.1.19"
Expand Down
16 changes: 13 additions & 3 deletions dash/src/network/message_qrinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ impl_consensus_encoding!(GetQRInfo, base_block_hashes, block_request_hash, extra
///
/// Note: The “compact size” integers that prefix some arrays are handled by your consensus encoding routines.
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "bincode", derive(Encode, Decode))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct QRInfo {
// Quorum snapshots for heights h-c, h-2c, h-3c.
pub quorum_snapshot_at_h_minus_c: QuorumSnapshot,
Expand Down Expand Up @@ -322,8 +325,15 @@ mod tests {
fn deserialize_qr_info() {
let block_hex = include_str!("../../tests/data/test_DML_diffs/QR_INFO_0_2224359.hex");
let data = hex::decode(block_hex).expect("decode hex");
let qr_info: RawNetworkMessage = deserialize(&data).expect("deserialize QR_INFO");

assert_matches!(qr_info, RawNetworkMessage { magic, payload: NetworkMessage::QRInfo(_) } if magic == 3177909439);
let network_qr_info: RawNetworkMessage = deserialize(&data).expect("deserialize QR_INFO");

let RawNetworkMessage {
magic,
payload: NetworkMessage::QRInfo(qr_info),
} = network_qr_info
else {
panic!("expected qr_info message");
};
assert_eq!(magic, 3177909439);
}
}
8 changes: 8 additions & 0 deletions dash/src/sml/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,12 @@ pub enum SmlError {
/// Error indicating that a required feature is not turned on.
#[error("Feature not turned on: {0}")]
FeatureNotTurnedOn(String),

/// Error indicating that an invalid index was provided in the signature set.
#[error("Invalid index in quorum signature set: {0}")]
InvalidIndexInSignatureSet(u16),

/// Error indicating the quorum signature set is incomplete (some slots were not filled).
#[error("Incomplete quorum signature set; not all slots were filled")]
IncompleteSignatureSet,
}
83 changes: 76 additions & 7 deletions dash/src/sml/masternode_list/apply_diff.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use crate::bls_sig_utils::BLSSignature;
use crate::network::message_sml::MnListDiff;
use crate::prelude::CoreBlockHeight;
use crate::sml::error::SmlError;
use crate::sml::llmq_entry_verification::{
LLMQEntryVerificationSkipStatus, LLMQEntryVerificationStatus,
};
use crate::sml::masternode_list::MasternodeList;
use crate::sml::quorum_entry::qualified_quorum_entry::{
QualifiedQuorumEntry, VerifyingChainLockSignaturesType,
};

impl MasternodeList {
/// Applies an `MnListDiff` to update the current masternode list.
Expand Down Expand Up @@ -32,7 +39,8 @@ impl MasternodeList {
&self,
diff: MnListDiff,
diff_end_height: CoreBlockHeight,
) -> Result<MasternodeList, SmlError> {
previous_chain_lock_sigs: Option<[BLSSignature; 3]>,
) -> Result<(MasternodeList, Option<BLSSignature>), SmlError> {
// Ensure the base block hash matches
if self.block_hash != diff.base_block_hash {
return Err(SmlError::BaseBlockHashMismatch {
Expand Down Expand Up @@ -67,12 +75,73 @@ impl MasternodeList {
}
}

// Build a vector of optional signatures with slots matching new_quorums length
let mut quorum_sig_lookup: Vec<Option<&BLSSignature>> = vec![None; diff.new_quorums.len()];

// Fill each slot with the corresponding signature
for quorum_sig_obj in &diff.quorums_chainlock_signatures {
for &index in &quorum_sig_obj.index_set {
if let Some(slot) = quorum_sig_lookup.get_mut(index as usize) {
*slot = Some(&quorum_sig_obj.signature);
} else {
return Err(SmlError::InvalidIndexInSignatureSet(index));
}
}
}

// Verify all slots have been filled
if quorum_sig_lookup.iter().any(Option::is_none) {
return Err(SmlError::IncompleteSignatureSet);
}

let mut rotating_sig = None;

// Add or update new quorums
for new_quorum in diff.new_quorums {
updated_quorums
.entry(new_quorum.llmq_type)
.or_default()
.insert(new_quorum.quorum_hash, new_quorum.into());
for (idx, new_quorum) in diff.new_quorums.into_iter().enumerate() {
updated_quorums.entry(new_quorum.llmq_type).or_default().insert(
new_quorum.quorum_hash,
{
let commitment_hash = new_quorum.calculate_commitment_hash();
let entry_hash = new_quorum.calculate_entry_hash();
let verifying_chain_lock_signature =
if new_quorum.llmq_type.is_rotating_quorum_type() {
if rotating_sig.is_none() {
if let Some(sig) = quorum_sig_lookup[idx] {
rotating_sig = Some(*sig)
} else {
return Err(SmlError::IncompleteSignatureSet);
}
}
if let Some(previous_chain_lock_sigs) = previous_chain_lock_sigs {
if let Some(sig) = quorum_sig_lookup[idx] {
Some(VerifyingChainLockSignaturesType::Rotating([
previous_chain_lock_sigs[0],
previous_chain_lock_sigs[1],
previous_chain_lock_sigs[2],
*sig,
]))
} else {
return Err(SmlError::IncompleteSignatureSet);
}
} else {
None
}
} else {
quorum_sig_lookup[idx]
.copied()
.map(VerifyingChainLockSignaturesType::NonRotating)
};
QualifiedQuorumEntry {
quorum_entry: new_quorum,
verified: LLMQEntryVerificationStatus::Skipped(
LLMQEntryVerificationSkipStatus::NotMarkedForVerification,
), // Default to unverified
commitment_hash,
entry_hash,
verifying_chain_lock_signature,
}
},
);
}

// Create and return the new MasternodeList
Expand All @@ -83,6 +152,6 @@ impl MasternodeList {
diff_end_height,
);

Ok(builder.build())
Ok((builder.build(), rotating_sig))
}
}
67 changes: 48 additions & 19 deletions dash/src/sml/masternode_list/from_diff.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::collections::BTreeMap;

use crate::bls_sig_utils::BLSSignature;
use crate::network::message_sml::MnListDiff;
use crate::sml::error::SmlError;
use crate::sml::llmq_entry_verification::{
LLMQEntryVerificationSkipStatus, LLMQEntryVerificationStatus,
};
use crate::sml::masternode_list::MasternodeList;
use crate::sml::quorum_entry::qualified_quorum_entry::QualifiedQuorumEntry;
use crate::sml::quorum_entry::qualified_quorum_entry::{
QualifiedQuorumEntry, VerifyingChainLockSignaturesType,
};
use crate::{BlockHash, Network};

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

let quorums = diff.new_quorums.into_iter().fold(BTreeMap::new(), |mut map, quorum| {
map.entry(quorum.llmq_type.into()).or_insert_with(BTreeMap::new).insert(
quorum.quorum_hash,
{
let entry_hash = quorum.calculate_entry_hash();
let commitment_hash = quorum.calculate_commitment_hash();
QualifiedQuorumEntry {
quorum_entry: quorum,
verified: LLMQEntryVerificationStatus::Skipped(
LLMQEntryVerificationSkipStatus::NotMarkedForVerification,
),
commitment_hash,
entry_hash,
}
},
);
map
});
// Build a vector of optional signatures with slots matching new_quorums length
let mut quorum_sig_lookup: Vec<Option<&BLSSignature>> = vec![None; diff.new_quorums.len()];

// Fill each slot with the corresponding signature
for quorum_sig_obj in &diff.quorums_chainlock_signatures {
for &index in &quorum_sig_obj.index_set {
if let Some(slot) = quorum_sig_lookup.get_mut(index as usize) {
*slot = Some(&quorum_sig_obj.signature);
} else {
return Err(SmlError::InvalidIndexInSignatureSet(index));
}
}
}

// Verify all slots have been filled
if quorum_sig_lookup.iter().any(Option::is_none) {
return Err(SmlError::IncompleteSignatureSet);
}

let quorums = diff.new_quorums.into_iter().enumerate().fold(
BTreeMap::new(),
|mut map, (idx, quorum)| {
map.entry(quorum.llmq_type.into()).or_insert_with(BTreeMap::new).insert(
quorum.quorum_hash,
{
let entry_hash = quorum.calculate_entry_hash();
let commitment_hash = quorum.calculate_commitment_hash();

QualifiedQuorumEntry {
quorum_entry: quorum,
verified: LLMQEntryVerificationStatus::Skipped(
LLMQEntryVerificationSkipStatus::NotMarkedForVerification,
),
commitment_hash,
entry_hash,
verifying_chain_lock_signature: quorum_sig_lookup[idx]
.copied()
.map(VerifyingChainLockSignaturesType::NonRotating),
}
},
);
map
},
);

// Construct `MasternodeList`
Ok(MasternodeList {
Expand Down
1 change: 0 additions & 1 deletion dash/src/sml/masternode_list/merkle_roots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ impl MasternodeList {
// we need to check that the coinbase is in the transaction hashes we got back
// and is in the merkle block
if let Some(mn_merkle_root) = self.masternode_merkle_root {
//println!("has_valid_mn_list_root: {} == {}", tx.merkle_root_mn_list, mn_merkle_root);
coinbase_payload.merkle_root_masternode_list == mn_merkle_root
} else {
false
Expand Down
32 changes: 16 additions & 16 deletions dash/src/sml/masternode_list_engine/message_request_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,15 +365,15 @@ mod tests {
.expect("expected to decode")
.0;

let lock_data = hex::decode("010133c404bebbf34c153816d26553bcec8c9b876354a68b952ab2c1c514c04baf9800000000578de219b47300c1d43985e1ee7af2faa773b87729df3cb48f2c522937c7b070d674ea572a713d6b07deef085b9ce97e1e354055b91b0bbd0b00000000000000a846486b3f75da24e3d04b62eadd7dffe589736f13ab222c208d0f3880dce5d287b8542dc1e1f0e271749e70e939262704f8611aafcdeb20ed70c5bc78fdf737a1bd6409d061fcf6a591b117ada7ba92567959544c090a05cdd955268d22be6b").expect("expected valid hex");
let lock_data = hex::decode("01018d53e7997ead57409750942af0d5e0aafc06f852a9a52308f4781b6a8220298f00000000c6f9d8c63dd15937ea70aaddb7890daad42c91bf6818e2bf76d183d6f2d9215b4b5f84978fad9dde7ab52bdcc0674be891e9029cc1ef0cb01200000000000000a27c98836c4c04653ab81eb4e07ddfc2c8c2c1036b75247969c05a4f25451cd78913a971f1899d9f2bddec9cf8e0104004f72f20c2856453e5aa3bcd2a8200670ec28feda38f67cc400fc72ef1966956656ec0765478c9d16e9a9e470c07f9ed").expect("expected valid hex");
let lock: InstantLock = deserialize(lock_data.as_slice()).expect("expected to deserialize");
let request_id = lock.request_id().expect("expected to make request id");
assert_eq!(
hex::encode(request_id),
"94dd5c8d946cb34dda43ebf424b385ae898159827385a48d8b1fae15dbf21a12"
"481ca36cf80fde8fda333915e33c27014dad65fa9f3b54bc4d8bc45be7c81ddf"
);
let quorum_hash: QuorumHash = QuorumHash::from_slice(
hex::decode("0000000000000019756ecc9c9c5f476d3f66876b1dcfa5dde1ea82f0d99334a2")
hex::decode("00000000000000197368b224f2f01031991dd07aad0b43b2293a51fce8853ba0")
.expect("expected bytes")
.as_slice(),
)
Expand All @@ -382,14 +382,14 @@ mod tests {

let (quorum, _, index) =
mn_list_engine.is_lock_quorum(&lock).expect("expected to get quorum");
assert_eq!(index, 4);
assert_eq!(index, 23);
assert_eq!(quorum.quorum_entry.quorum_hash, quorum_hash);

let sign_id =
lock.sign_id(LLMQType::Llmqtype60_75, quorum_hash, None).expect("expected sign id");
assert_eq!(
hex::encode(sign_id),
"0eaeba3a982f59144913b8d8150b2dfbb2dd2ba43bbcb54a3964a0d8d7ead62b"
"6fcbf58004b118d865a448bf89d9299c64d4ecedd754dabec655090224de91cd"
);
mn_list_engine.verify_is_lock(&lock).expect("expected to verify is lock");
}
Expand All @@ -406,18 +406,18 @@ mod tests {

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

assert_eq!(height, 2229204);
assert_eq!(height, 2243493);

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

let request_id = chain_lock.request_id().expect("expected to make request id");
assert_eq!(
hex::encode(request_id),
"88346d49ddf5c06528f3ede253e01a8ab54048935ff944d88bb149aaeef956f2"
"969ab4a945632f5fba1331f3d2556d317682142cf8aaa6544e407e683c61a177"
);

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

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

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

let request_id = chain_lock.request_id().expect("expected to make request id");
assert_eq!(
hex::encode(request_id),
"10a808c18307e3993dd4aa996d032b6b5d9d30a5d75ddf64fa10ea35ee63b2bb"
"675aed91d6098cdf575cc09bfd1ff4f750acde1e793f385c3c72bbb400068d28"
);

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

let expected_quorum_hash: QuorumHash = QuorumHash::from_slice(
hex::decode("000000000000000bbd0b1bb95540351e7ee99c5b08efde076b3d712a57ea74d6")
hex::decode("000000000000000c0e633b441b9e9c130732746c56ca3884220bab23b6c7ec6a")
.expect("expected bytes")
.as_slice(),
)
Expand Down
Loading
Loading