diff --git a/packages/rs-drive-abci/src/config.rs b/packages/rs-drive-abci/src/config.rs index 1a0f90a9235..fa7740957c6 100644 --- a/packages/rs-drive-abci/src/config.rs +++ b/packages/rs-drive-abci/src/config.rs @@ -8,7 +8,10 @@ use dpp::util::deserializer::ProtocolVersion; use dpp::version::INITIAL_PROTOCOL_VERSION; use drive::config::{DriveConfig, DEFAULT_EPOCH_TIME_LENGTH_S}; use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize}; +use std::fmt::Display; +use std::num::{NonZeroU16, NonZeroU32}; use std::path::PathBuf; +use std::str::FromStr; /// Configuration for Dash Core RPC client used in consensus logic #[derive(Clone, Debug, Serialize, Deserialize, Default)] @@ -307,10 +310,10 @@ pub trait QuorumLikeConfig: Sized { fn quorum_size(&self) -> u16; /// Quorum DKG interval - fn quorum_window(&self) -> u32; + fn quorum_window(&self) -> NonZeroU32; /// Quorum active signers count - fn quorum_active_signers(&self) -> u16; + fn quorum_active_signers(&self) -> NonZeroU16; /// Quorum rotation (dip24) or classic fn quorum_rotation(&self) -> bool; @@ -339,16 +342,16 @@ pub struct ValidatorSetConfig { /// On Testnet Chain Locks are signed using 50_60: One quorum in every 24 blocks and activeQuorumCount is 24. #[serde( rename = "validator_set_quorum_window", - deserialize_with = "from_str_or_number" + deserialize_with = "deserialize_validator_set_quorum_window" )] - pub quorum_window: u32, + pub quorum_window: NonZeroU32, /// The number of active signers #[serde( rename = "validator_set_quorum_active_signers", - deserialize_with = "from_str_or_number" + deserialize_with = "deserialize_validator_set_quorum_active_signers" )] - pub quorum_active_signers: u16, + pub quorum_active_signers: NonZeroU16, /// Whether the quorum is rotated DIP24 or classic #[serde( @@ -371,8 +374,8 @@ impl ValidatorSetConfig { Self { quorum_type: QuorumType::Llmq100_67, quorum_size: 100, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(24).expect("24 is not 0"), quorum_rotation: false, } } @@ -387,11 +390,11 @@ impl QuorumLikeConfig for ValidatorSetConfig { self.quorum_size } - fn quorum_window(&self) -> u32 { + fn quorum_window(&self) -> NonZeroU32 { self.quorum_window } - fn quorum_active_signers(&self) -> u16 { + fn quorum_active_signers(&self) -> NonZeroU16 { self.quorum_active_signers } @@ -423,16 +426,16 @@ pub struct ChainLockConfig { /// On Testnet Chain Locks are signed using 50_60: One quorum in every 24 blocks and activeQuorumCount is 24. #[serde( rename = "chain_lock_quorum_window", - deserialize_with = "from_str_or_number" + deserialize_with = "deserialize_chain_lock_quorum_window" )] - pub quorum_window: u32, + pub quorum_window: NonZeroU32, /// The number of active signers #[serde( rename = "chain_lock_quorum_active_signers", - deserialize_with = "from_str_or_number" + deserialize_with = "deserialize_chain_lock_quorum_active_signers" )] - pub quorum_active_signers: u16, + pub quorum_active_signers: NonZeroU16, /// Whether the quorum is rotated DIP24 or classic #[serde( @@ -448,8 +451,8 @@ impl Default for ChainLockConfig { Self { quorum_type: QuorumType::Llmq400_60, quorum_size: 400, - quorum_window: 24 * 12, - quorum_active_signers: 4, + quorum_window: NonZeroU32::new(24 * 12).expect("24 * 12 is not 0"), + quorum_active_signers: NonZeroU16::new(4).expect("4 is not 0"), quorum_rotation: false, } } @@ -464,11 +467,11 @@ impl QuorumLikeConfig for ChainLockConfig { self.quorum_size } - fn quorum_window(&self) -> u32 { + fn quorum_window(&self) -> NonZeroU32 { self.quorum_window } - fn quorum_active_signers(&self) -> u16 { + fn quorum_active_signers(&self) -> NonZeroU16 { self.quorum_active_signers } @@ -483,8 +486,8 @@ impl ChainLockConfig { Self { quorum_type: QuorumType::Llmq100_67, quorum_size: 100, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(24).expect("24 is not 0"), quorum_rotation: false, } } @@ -513,16 +516,16 @@ pub struct InstantLockConfig { /// On Testnet Chain Locks are signed using 50_60: One quorum in every 24 blocks and activeQuorumCount is 24. #[serde( rename = "instant_lock_quorum_window", - deserialize_with = "from_str_or_number" + deserialize_with = "deserialize_instant_lock_quorum_window" )] - pub quorum_window: u32, + pub quorum_window: NonZeroU32, /// The number of active signers #[serde( rename = "instant_lock_quorum_active_signers", - deserialize_with = "from_str_or_number" + deserialize_with = "deserialize_instant_lock_quorum_active_signers" )] - pub quorum_active_signers: u16, + pub quorum_active_signers: NonZeroU16, /// Whether the quorum is rotated DIP24 or classic #[serde( @@ -537,9 +540,9 @@ impl Default for InstantLockConfig { // Mainnet Self { quorum_type: QuorumType::Llmq60_75, - quorum_active_signers: 32, + quorum_active_signers: NonZeroU16::new(32).expect("32 is not 0"), quorum_size: 60, - quorum_window: 24 * 12, + quorum_window: NonZeroU32::new(24 * 12).expect("24 * 12 is not 0"), quorum_rotation: true, } } @@ -551,8 +554,8 @@ impl InstantLockConfig { Self { quorum_type: QuorumType::Llmq100_67, quorum_size: 100, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(24).expect("24 is not 0"), quorum_rotation: false, } } @@ -567,11 +570,11 @@ impl QuorumLikeConfig for InstantLockConfig { self.quorum_size } - fn quorum_window(&self) -> u32 { + fn quorum_window(&self) -> NonZeroU32 { self.quorum_window } - fn quorum_active_signers(&self) -> u16 { + fn quorum_active_signers(&self) -> NonZeroU16 { self.quorum_active_signers } @@ -609,6 +612,69 @@ where Ok(quorum_type) } +/// Reads a quorum setting that has to be a number greater than 0, refusing anything else with an +/// error naming `setting`, the variable it was read from. Chain lock verification divides a core +/// height by the quorum window to find its DKG interval, and choosing a rotated quorum takes the +/// base-2 logarithm of the active signers, so neither can be 0 wherever it is held. +fn deserialize_non_zero<'de, D, N>(deserializer: D, setting: &str) -> Result +where + D: serde::Deserializer<'de>, + N: Deserialize<'de> + FromStr, + ::Err: Display, +{ + from_str_or_number(deserializer).map_err(|e| { + serde::de::Error::custom(format!("{setting} must be a number greater than 0: {e}")) + }) +} + +fn deserialize_validator_set_quorum_window<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + deserialize_non_zero(deserializer, "VALIDATOR_SET_QUORUM_WINDOW") +} + +fn deserialize_validator_set_quorum_active_signers<'de, D>( + deserializer: D, +) -> Result +where + D: serde::Deserializer<'de>, +{ + deserialize_non_zero(deserializer, "VALIDATOR_SET_QUORUM_ACTIVE_SIGNERS") +} + +fn deserialize_chain_lock_quorum_window<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + deserialize_non_zero(deserializer, "CHAIN_LOCK_QUORUM_WINDOW") +} + +fn deserialize_chain_lock_quorum_active_signers<'de, D>( + deserializer: D, +) -> Result +where + D: serde::Deserializer<'de>, +{ + deserialize_non_zero(deserializer, "CHAIN_LOCK_QUORUM_ACTIVE_SIGNERS") +} + +fn deserialize_instant_lock_quorum_window<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + deserialize_non_zero(deserializer, "INSTANT_LOCK_QUORUM_WINDOW") +} + +fn deserialize_instant_lock_quorum_active_signers<'de, D>( + deserializer: D, +) -> Result +where + D: serde::Deserializer<'de>, +{ + deserialize_non_zero(deserializer, "INSTANT_LOCK_QUORUM_ACTIVE_SIGNERS") +} + impl ExecutionConfig { fn default_verify_sum_trees() -> bool { true @@ -704,22 +770,22 @@ impl PlatformConfig { validator_set: ValidatorSetConfig { quorum_type: QuorumType::LlmqTestPlatform, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 2, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(2).expect("2 is not 0"), quorum_rotation: false, }, chain_lock: ChainLockConfig { quorum_type: QuorumType::LlmqTest, - quorum_active_signers: 2, + quorum_active_signers: NonZeroU16::new(2).expect("2 is not 0"), quorum_size: 3, - quorum_window: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::LlmqTest, - quorum_active_signers: 2, + quorum_active_signers: NonZeroU16::new(2).expect("2 is not 0"), quorum_size: 3, - quorum_window: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), quorum_rotation: false, }, block_spacing_ms: 5000, @@ -746,22 +812,22 @@ impl PlatformConfig { validator_set: ValidatorSetConfig { quorum_type: QuorumType::LlmqDevnetPlatform, quorum_size: 12, - quorum_window: 24, - quorum_active_signers: 8, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(8).expect("8 is not 0"), quorum_rotation: false, }, chain_lock: ChainLockConfig { quorum_type: QuorumType::LlmqDevnetPlatform, quorum_size: 12, - quorum_window: 24, - quorum_active_signers: 8, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(8).expect("8 is not 0"), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::LlmqDevnetDip0024, - quorum_active_signers: 4, + quorum_active_signers: NonZeroU16::new(4).expect("4 is not 0"), quorum_size: 8, - quorum_window: 48, + quorum_window: NonZeroU32::new(48).expect("48 is not 0"), quorum_rotation: true, }, block_spacing_ms: 5000, @@ -788,22 +854,22 @@ impl PlatformConfig { validator_set: ValidatorSetConfig { quorum_type: QuorumType::Llmq25_67, quorum_size: 25, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(24).expect("24 is not 0"), quorum_rotation: false, }, chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq50_60, - quorum_active_signers: 24, + quorum_active_signers: NonZeroU16::new(24).expect("24 is not 0"), quorum_size: 50, - quorum_window: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq60_75, - quorum_active_signers: 32, + quorum_active_signers: NonZeroU16::new(32).expect("32 is not 0"), quorum_size: 60, - quorum_window: 24 * 12, + quorum_window: NonZeroU32::new(24 * 12).expect("24 * 12 is not 0"), quorum_rotation: true, }, block_spacing_ms: 5000, @@ -830,22 +896,22 @@ impl PlatformConfig { validator_set: ValidatorSetConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 100, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).expect("24 is not 0"), + quorum_active_signers: NonZeroU16::new(24).expect("24 is not 0"), quorum_rotation: false, }, chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq400_60, - quorum_active_signers: 4, + quorum_active_signers: NonZeroU16::new(4).expect("4 is not 0"), quorum_size: 400, - quorum_window: 24 * 12, + quorum_window: NonZeroU32::new(24 * 12).expect("24 * 12 is not 0"), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq60_75, - quorum_active_signers: 32, + quorum_active_signers: NonZeroU16::new(32).expect("32 is not 0"), quorum_size: 60, - quorum_window: 24 * 12, + quorum_window: NonZeroU32::new(24 * 12).expect("24 * 12 is not 0"), quorum_rotation: true, }, block_spacing_ms: 5000, @@ -1111,6 +1177,66 @@ mod tests { ); } + /// A zero quorum window divides by zero in chain lock verification, and zero active signers + /// underflow choosing a rotated quorum, so loading the config must refuse either instead of + /// letting the node start. The variables are read from `.env.local` without touching the + /// process environment, which other tests set. + #[test] + fn should_reject_zero_quorum_windows_and_active_signers_when_loading_config() { + let envfile = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".env.local"); + let vars: Vec<(String, String)> = dotenvy::from_path_iter(envfile.as_path()) + .expect("cannot open .env file") + .collect::>() + .expect("cannot parse .env file"); + let with_var = |name: &str, setting: &str| -> Vec<(String, String)> { + vars.iter() + .map(|(key, value)| { + if key == name { + (key.clone(), setting.to_string()) + } else { + (key.clone(), value.clone()) + } + }) + .collect() + }; + + type Setting = fn(&PlatformConfig) -> u32; + let settings: [(&str, Setting); 6] = [ + ("VALIDATOR_SET_QUORUM_WINDOW", |config| { + config.validator_set.quorum_window.get() + }), + ("VALIDATOR_SET_QUORUM_ACTIVE_SIGNERS", |config| { + config.validator_set.quorum_active_signers.get().into() + }), + ("CHAIN_LOCK_QUORUM_WINDOW", |config| { + config.chain_lock.quorum_window.get() + }), + ("CHAIN_LOCK_QUORUM_ACTIVE_SIGNERS", |config| { + config.chain_lock.quorum_active_signers.get().into() + }), + ("INSTANT_LOCK_QUORUM_WINDOW", |config| { + config.instant_lock.quorum_window.get() + }), + ("INSTANT_LOCK_QUORUM_ACTIVE_SIGNERS", |config| { + config.instant_lock.quorum_active_signers.get().into() + }), + ]; + + for (name, setting) in settings { + let err = envy::from_iter::<_, PlatformConfig>(with_var(name, "0")) + .expect_err("a zero quorum setting should fail"); + let msg = err.to_string(); + assert!( + msg.contains(&format!("{name} must be a number greater than 0")), + "the error should name {name}, got: {msg}" + ); + + let config = envy::from_iter::<_, PlatformConfig>(with_var(name, "1")) + .unwrap_or_else(|e| panic!("{name}=1 should be accepted: {e}")); + assert_eq!(setting(&config), 1, "{name}"); + } + } + // --- Core RPC URL composition and defaults --- /// Exercises `ConsensusCoreRpcConfig::url` formatting. @@ -1180,8 +1306,8 @@ mod tests { let cfg = ValidatorSetConfig::default_100_67(); assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67); assert_eq!(cfg.quorum_size(), 100); - assert_eq!(cfg.quorum_window(), 24); - assert_eq!(cfg.quorum_active_signers(), 24); + assert_eq!(cfg.quorum_window().get(), 24); + assert_eq!(cfg.quorum_active_signers().get(), 24); assert!(!cfg.quorum_rotation()); } @@ -1191,8 +1317,8 @@ mod tests { let cfg = ChainLockConfig::default_100_67(); assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67); assert_eq!(cfg.quorum_size(), 100); - assert_eq!(cfg.quorum_window(), 24); - assert_eq!(cfg.quorum_active_signers(), 24); + assert_eq!(cfg.quorum_window().get(), 24); + assert_eq!(cfg.quorum_active_signers().get(), 24); assert!(!cfg.quorum_rotation()); } @@ -1202,8 +1328,8 @@ mod tests { let cfg = ChainLockConfig::default(); assert_eq!(cfg.quorum_type(), QuorumType::Llmq400_60); assert_eq!(cfg.quorum_size(), 400); - assert_eq!(cfg.quorum_window(), 24 * 12); - assert_eq!(cfg.quorum_active_signers(), 4); + assert_eq!(cfg.quorum_window().get(), 24 * 12); + assert_eq!(cfg.quorum_active_signers().get(), 4); assert!(!cfg.quorum_rotation()); } @@ -1213,8 +1339,8 @@ mod tests { let cfg = InstantLockConfig::default(); assert_eq!(cfg.quorum_type(), QuorumType::Llmq60_75); assert_eq!(cfg.quorum_size(), 60); - assert_eq!(cfg.quorum_window(), 24 * 12); - assert_eq!(cfg.quorum_active_signers(), 32); + assert_eq!(cfg.quorum_window().get(), 24 * 12); + assert_eq!(cfg.quorum_active_signers().get(), 32); assert!(cfg.quorum_rotation()); } @@ -1224,8 +1350,8 @@ mod tests { let cfg = InstantLockConfig::default_100_67(); assert_eq!(cfg.quorum_type(), QuorumType::Llmq100_67); assert_eq!(cfg.quorum_size(), 100); - assert_eq!(cfg.quorum_window(), 24); - assert_eq!(cfg.quorum_active_signers(), 24); + assert_eq!(cfg.quorum_window().get(), 24); + assert_eq!(cfg.quorum_active_signers().get(), 24); assert!(!cfg.quorum_rotation()); } diff --git a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs index 000d46582a2..ef44bc8cac3 100644 --- a/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs +++ b/packages/rs-drive-abci/src/execution/platform_events/core_chain_lock/verify_chain_lock_locally/v0/mod.rs @@ -66,11 +66,14 @@ where let window_width = quorum_config.window; - // The last block in the window where the quorums would be the same - let last_block_in_window = platform_state.last_committed_core_height() - - platform_state.last_committed_core_height() % window_width - + window_width - - 1; + // The last block in the window where the quorums would be the same. The window is a + // `NonZeroU32`, so the remainder cannot divide by zero, and a window ending past the + // largest core height wraps instead of overflowing where overflow is checked. For every + // window this equals the wrapping `u32` arithmetic of release builds, which the protocol + // versions selecting this method validate their chains with. + let last_block_in_window = (platform_state.last_committed_core_height() + - platform_state.last_committed_core_height() % window_width) + .wrapping_add(window_width.get() - 1); let verification_height = chain_lock_height.saturating_sub(SIGN_OFFSET); @@ -235,12 +238,21 @@ where #[cfg(test)] mod tests { + use crate::config::ChainLockConfig; use crate::execution::platform_events::core_chain_lock::verify_chain_lock_locally::v0::CHAIN_LOCK_REQUEST_ID_PREFIX; - use crate::test::helpers::setup::TestPlatformBuilder; - use dpp::bls_signatures::{Bls12381G2Impl, Pairing}; + use crate::platform_types::platform_state::{PlatformState, PlatformStateV0Methods}; + use crate::platform_types::signature_verification_quorum_set::{ + SignatureVerificationQuorumSet, SignatureVerificationQuorumSetV0Methods, VerificationQuorum, + }; + use crate::rpc::core::MockCoreRPCLike; + use crate::test::helpers::setup::{TempPlatform, TestPlatformBuilder}; + use dpp::block::block_info::BlockInfo; + use dpp::bls_signatures::{Bls12381G2Impl, Pairing, SecretKey, SignatureSchemes}; use dpp::dashcore::hashes::{sha256d, Hash, HashEngine}; - use dpp::dashcore::{BlockHash, ChainLock, QuorumSigningRequestId}; + use dpp::dashcore::{BlockHash, ChainLock, QuorumHash, QuorumSigningRequestId}; + use dpp::dashcore_rpc::json::QuorumType; use dpp::version::PlatformVersion; + use std::num::{NonZeroU16, NonZeroU32}; #[test] fn verify_request_id() { @@ -371,4 +383,127 @@ mod tests { msg ); } + + /// The secret key of the only chain lock quorum held by `state_with_one_chain_lock_quorum` + fn quorum_secret_key() -> SecretKey { + let mut key_bytes = [0u8; 32]; + key_bytes[0] = 1; + key_bytes[31] = 1; + SecretKey::::from_be_bytes(&key_bytes).expect("valid secret key") + } + + const QUORUM_HASH: [u8; 32] = [1u8; 32]; + + /// `platform`'s state with last committed core height `core_height`, holding one llmq_test + /// chain lock quorum with a 24 block DKG interval + fn state_with_one_chain_lock_quorum( + platform: &TempPlatform, + core_height: u32, + ) -> PlatformState { + let mut state = platform.platform.state.load().as_ref().clone(); + state.set_last_committed_block_info(None); + state.set_genesis_block_info(Some(BlockInfo { + core_height, + ..Default::default() + })); + + let mut quorum_set = SignatureVerificationQuorumSet::V0( + ChainLockConfig { + quorum_type: QuorumType::LlmqTest, + quorum_size: 3, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(2).unwrap(), + quorum_rotation: false, + } + .into(), + ); + quorum_set.set_current_quorums( + [( + QuorumHash::from_byte_array(QUORUM_HASH), + VerificationQuorum { + index: None, + public_key: quorum_secret_key().public_key(), + }, + )] + .into_iter() + .collect(), + ); + state.set_chain_lock_validating_quorums(quorum_set); + state + } + + /// A llmq_test chain lock at `height` signed by the quorum of + /// `state_with_one_chain_lock_quorum` + fn sign_chain_lock(height: u32) -> ChainLock { + let block_hash = BlockHash::from_byte_array([7u8; 32]); + + let mut engine = QuorumSigningRequestId::engine(); + engine.input(&[CHAIN_LOCK_REQUEST_ID_PREFIX.len() as u8]); + engine.input(CHAIN_LOCK_REQUEST_ID_PREFIX.as_bytes()); + engine.input(height.to_le_bytes().as_slice()); + let request_id = QuorumSigningRequestId::from_engine(engine); + + let mut reversed_quorum_hash = QUORUM_HASH; + reversed_quorum_hash.reverse(); + + let mut engine = sha256d::Hash::engine(); + engine.input(&[QuorumType::LlmqTest as u8]); + engine.input(reversed_quorum_hash.as_slice()); + engine.input(request_id.as_byte_array()); + engine.input(block_hash.as_byte_array()); + let message_digest = sha256d::Hash::from_engine(engine); + + let signature = quorum_secret_key() + .sign(SignatureSchemes::Basic, message_digest.as_byte_array()) + .expect("signs"); + + ChainLock { + block_height: height, + block_hash, + signature: signature.as_raw_value().to_compressed().into(), + } + } + + fn chain_lock_test_platform() -> TempPlatform { + TestPlatformBuilder::new() + .with_latest_protocol_version() + .build_with_mock_rpc() + .set_initial_state_structure() + } + + #[test] + fn should_leave_chain_lock_to_core_without_overflowing_when_quorum_window_ends_past_largest_core_height( + ) { + let platform = chain_lock_test_platform(); + let platform_version = PlatformVersion::latest(); + + // The quorum verifies its chain locks locally while its window ends below u32::MAX + let state = state_with_one_chain_lock_quorum(&platform, 1106); + assert_eq!( + platform + .platform + .verify_chain_lock_locally_v0(0, &state, &sign_chain_lock(1110), platform_version) + .expect("verification runs"), + Some(true) + ); + + // The 24 block window holding core height u32::MAX - 5 would end past u32::MAX. Release + // builds have always wrapped its last block to 7, below any verification height near + // the core height, so the chain lock is left to Core; that must stay so, and must not + // overflow in builds that check arithmetic. + let core_height = u32::MAX - 5; + let state = state_with_one_chain_lock_quorum(&platform, core_height); + assert_eq!( + platform + .platform + .verify_chain_lock_locally_v0( + 0, + &state, + &sign_chain_lock(core_height + 3), + platform_version + ) + .expect("verification runs"), + None + ); + } } diff --git a/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/mod.rs b/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/mod.rs index 0f9032227ec..22a583c69bb 100644 --- a/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/mod.rs +++ b/packages/rs-drive-abci/src/execution/storage/fetch_platform_state/mod.rs @@ -34,3 +34,146 @@ impl Platform { } } } + +#[cfg(test)] +mod tests { + use crate::error::Error; + use crate::platform_types::platform::Platform; + use crate::platform_types::platform_state::platform_state_for_saving::PlatformStateForSaving; + use crate::platform_types::platform_state::PlatformStateV0Methods; + use crate::platform_types::signature_verification_quorum_set::SignatureVerificationQuorumSetV0Methods; + use crate::rpc::core::MockCoreRPCLike; + use crate::test::fixture::platform_state::{ + PLATFORM_STATE_V3_TESTNET, PLATFORM_STATE_V8_DEVNET, + }; + use crate::test::helpers::setup::TestPlatformBuilder; + use bincode::config::{BigEndian, Configuration, NoLimit, Varint}; + use dpp::dashcore_rpc::json::QuorumType; + use dpp::version::PlatformVersion; + use dpp::ProtocolError; + use std::ops::Deref; + + /// The bincode configuration platform states are saved with + const PLATFORM_BINCODE_CONFIG: Configuration = + bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + /// A saved quorum config: its type, active signers, rotation and window + type SavedQuorumConfig = (QuorumType, u16, bool, u32); + + /// A testnet state in the first saved format and a devnet state in the second, each with + /// the chain lock quorum config it was saved with + fn fixtures() -> [(&'static str, SavedQuorumConfig); 2] { + [ + ( + PLATFORM_STATE_V3_TESTNET.deref(), + (QuorumType::Llmq50_60, 24, false, 24), + ), + ( + PLATFORM_STATE_V8_DEVNET.deref(), + (QuorumType::LlmqDevnet, 4, false, 24), + ), + ] + } + + /// The bytes of `fixture` with its chain lock quorum config, saved as `saved`, holding + /// `active_signers` and `window`, encoded the way a `u16` and a `u32` were + fn with_chain_lock_quorum_config( + fixture: &str, + saved: SavedQuorumConfig, + active_signers: u16, + window: u32, + ) -> Vec { + let encode = |config: SavedQuorumConfig| { + bincode::encode_to_vec(config, PLATFORM_BINCODE_CONFIG).unwrap() + }; + let saved_bytes = encode(saved); + let mut bytes = hex::decode(fixture).unwrap(); + let positions: Vec = bytes + .windows(saved_bytes.len()) + .enumerate() + .filter_map(|(position, window)| (window == saved_bytes).then_some(position)) + .collect(); + assert_eq!( + positions.len(), + 1, + "the chain lock quorum config is saved once" + ); + let (quorum_type, _, rotation, _) = saved; + bytes.splice( + positions[0]..positions[0] + saved_bytes.len(), + encode((quorum_type, active_signers, rotation, window)), + ); + bytes + } + + #[test] + fn should_save_historical_platform_states_with_unchanged_bytes() { + for (fixture, _) in fixtures() { + let bytes = hex::decode(fixture).unwrap(); + let (saved, read): (PlatformStateForSaving, _) = + bincode::decode_from_slice(&bytes, PLATFORM_BINCODE_CONFIG).unwrap(); + assert_eq!(read, bytes.len()); + assert_eq!( + bincode::encode_to_vec(&saved, PLATFORM_BINCODE_CONFIG).unwrap(), + bytes + ); + } + } + + #[test] + fn should_refuse_to_restore_saved_state_holding_zero_chain_lock_active_signers_or_window() { + // A restarted node reads its chain lock quorum config from its saved state rather than from + // its config. Choosing a rotated quorum takes the logarithm of the active signers and chain + // lock verification divides by the window, so a saved 0 in either must stop the node from + // loading that state instead of reaching them. + let platform = TestPlatformBuilder::new().build_with_mock_rpc(); + let restore = |bytes: &[u8], platform_version: &PlatformVersion| { + let transaction = platform.drive.grove.start_transaction(); + platform + .drive + .store_platform_state_bytes(bytes, Some(&transaction), platform_version) + .unwrap(); + Platform::::fetch_platform_state( + &platform.drive, + Some(&transaction), + platform_version, + ) + }; + + // Protocol version 13 reads the saved state with fetch_platform_state v0, 14 with v1 + for platform_version in [PlatformVersion::get(13).unwrap(), PlatformVersion::latest()] { + for (fixture, saved) in fixtures() { + let state = restore( + &with_chain_lock_quorum_config(fixture, saved, 1, 1), + platform_version, + ) + .unwrap() + .unwrap(); + let restored = state.chain_lock_validating_quorums().config(); + assert_eq!( + (restored.active_signers.get(), restored.window.get()), + (1, 1) + ); + + for (active_signers, window) in [(0, saved.3), (saved.1, 0)] { + let result = restore( + &with_chain_lock_quorum_config(fixture, saved, active_signers, window), + platform_version, + ); + assert!( + matches!( + &result, + Err(Error::Protocol(ProtocolError::PlatformDeserializationError(message))) + if message.contains("NonZeroTypeIsZero") + ), + "protocol version {}, {:?}, active signers {active_signers}, window {window}: got {result:?}", + platform_version.protocol_version, + saved.0 + ); + } + } + } + } +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_config_for_saving_v0.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_config_for_saving_v0.rs index ddffeec3893..85780a761ed 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_config_for_saving_v0.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_config_for_saving_v0.rs @@ -2,13 +2,17 @@ use crate::platform_types::signature_verification_quorum_set::QuorumConfig; use bincode::Encode; use dpp::dashcore_rpc::dashcore_rpc_json::QuorumType; use dpp::platform_serialization::de::Decode; +use std::num::{NonZeroU16, NonZeroU32}; +/// bincode encodes the active signers and the window exactly as the `u16` and `u32` they hold, +/// the bytes of plain integer fields, and fails to decode a saved 0 in either, so a 0 cannot reach +/// quorum selection or chain lock verification. #[derive(Debug, Clone, Encode, Decode)] pub struct QuorumConfigForSavingV0 { quorum_type: QuorumType, - active_signers: u16, + active_signers: NonZeroU16, rotation: bool, - window: u32, + window: NonZeroU32, } impl From for QuorumConfigForSavingV0 { @@ -32,3 +36,80 @@ impl From for QuorumConfig { } } } + +#[cfg(test)] +mod tests { + use super::*; + use bincode::config::{BigEndian, Configuration, NoLimit, Varint}; + use bincode::error::{DecodeError, IntegerType}; + + /// The bincode configuration platform states are saved with + const PLATFORM_BINCODE_CONFIG: Configuration = + bincode::config::standard() + .with_big_endian() + .with_no_limit(); + + /// The saved config as it was written while the active signers and the window were a `u16` + /// and a `u32` + fn saved_with_integers(active_signers: u16, window: u32) -> Vec { + bincode::encode_to_vec( + (QuorumType::Llmq400_60, active_signers, false, window), + PLATFORM_BINCODE_CONFIG, + ) + .expect("encodes") + } + + #[test] + fn should_save_and_restore_active_signers_and_window_with_the_bytes_of_integers() { + // Every varint width: one byte up to 250, then the u16 and u32 markers + let active_signers_counts = [1, 4, 250, 251, u16::MAX]; + let windows = [1, 24, 250, 251, 288, u32::from(u16::MAX) + 1, u32::MAX]; + for active_signers in active_signers_counts { + for window in windows { + let config = QuorumConfigForSavingV0 { + quorum_type: QuorumType::Llmq400_60, + active_signers: NonZeroU16::new(active_signers).unwrap(), + rotation: false, + window: NonZeroU32::new(window).unwrap(), + }; + + let bytes = bincode::encode_to_vec(&config, PLATFORM_BINCODE_CONFIG).unwrap(); + assert_eq!( + bytes, + saved_with_integers(active_signers, window), + "active signers {active_signers}, window {window}" + ); + + let (restored, read): (QuorumConfigForSavingV0, _) = + bincode::decode_from_slice(&bytes, PLATFORM_BINCODE_CONFIG).unwrap(); + assert_eq!(read, bytes.len()); + let restored = QuorumConfig::from(restored); + assert_eq!(restored.active_signers.get(), active_signers); + assert_eq!(restored.window.get(), window); + } + } + } + + #[test] + fn should_fail_to_restore_saved_active_signers_or_window_of_zero() { + // Choosing a rotated quorum takes the logarithm of the active signers and chain lock + // verification divides by the window, so a saved 0 in either must not decode + for (bytes, zero_type) in [ + (saved_with_integers(0, 288), IntegerType::U16), + (saved_with_integers(4, 0), IntegerType::U32), + ] { + let result = bincode::decode_from_slice::( + &bytes, + PLATFORM_BINCODE_CONFIG, + ); + + assert!( + matches!( + &result, + Err(DecodeError::NonZeroTypeIsZero { non_zero_type }) if *non_zero_type == zero_type + ), + "expected a zero {zero_type:?}, got {result:?}" + ); + } + } +} diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs index 881347bc6ae..bb86b2fdfa4 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorum_set.rs @@ -3,6 +3,7 @@ use crate::platform_types::signature_verification_quorum_set::v0::quorums::Quoru use crate::platform_types::signature_verification_quorum_set::VerificationQuorum; use dpp::dashcore::QuorumHash; use dpp::dashcore_rpc::json::QuorumType; +use std::num::{NonZeroU16, NonZeroU32}; use std::vec::IntoIter; /// Offset for signature verification @@ -144,11 +145,11 @@ pub struct QuorumConfig { /// Type pub quorum_type: QuorumType, /// Active quorum signers count - pub active_signers: u16, + pub active_signers: NonZeroU16, /// Is it a DIP24 rotating quorum or classic pub rotation: bool, /// DKG interval - pub window: u32, + pub window: NonZeroU32, } impl SignatureVerificationQuorumSetV0Methods for SignatureVerificationQuorumSetV0 { @@ -339,8 +340,8 @@ mod tests { ChainLockConfig { quorum_type: QuorumType::Llmq400_60, quorum_size: 400, - quorum_window: 288, - quorum_active_signers: 4, + quorum_window: NonZeroU32::new(288).unwrap(), + quorum_active_signers: NonZeroU16::new(4).unwrap(), quorum_rotation: false, } } @@ -353,9 +354,9 @@ mod tests { let qs = SignatureVerificationQuorumSetV0::new(&config); assert_eq!(qs.config().quorum_type, QuorumType::Llmq400_60); - assert_eq!(qs.config().active_signers, 4); + assert_eq!(qs.config().active_signers.get(), 4); assert!(!qs.config().rotation); - assert_eq!(qs.config().window, 288); + assert_eq!(qs.config().window.get(), 288); assert!(qs.current_quorums().is_empty()); assert!(!qs.has_previous_past_quorums()); } @@ -365,16 +366,16 @@ mod tests { let config = ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 100, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: true, }; let qs: SignatureVerificationQuorumSetV0 = config.into(); assert_eq!(qs.config().quorum_type, QuorumType::Llmq100_67); - assert_eq!(qs.config().active_signers, 24); + assert_eq!(qs.config().active_signers.get(), 24); assert!(qs.config().rotation); - assert_eq!(qs.config().window, 24); + assert_eq!(qs.config().window.get(), 24); } // ---- set_current_quorums / current_quorums ---- diff --git a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs index 2781697e1dc..94b9d5f5083 100644 --- a/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs +++ b/packages/rs-drive-abci/src/platform_types/signature_verification_quorum_set/v0/quorums.rs @@ -100,10 +100,9 @@ impl Quorums { quorum_config: &QuorumConfig, request_id: &[u8; 32], ) -> Option<(QuorumHash, &Q)> { - let active_signers = quorum_config.active_signers as u32; - - // binary (base-2) logarithm from active_signers - let n = 31 - active_signers.leading_zeros(); + // binary (base-2) logarithm of active_signers, rounded down; at least one signer is + // active, so it is always defined + let n = quorum_config.active_signers.ilog2(); // Extract last 64 bits of request_id let b = u64::from_le_bytes( @@ -232,6 +231,7 @@ mod tests { use dpp::bls_signatures::{Bls12381G2Impl, SecretKey as BlsPrivateKey}; use dpp::dashcore::hashes::Hash; use dpp::dashcore_rpc::json::QuorumType; + use std::num::{NonZeroU16, NonZeroU32}; /// Helper: generate a deterministic BLS public key from a seed byte. fn make_public_key(seed: u8) -> ThresholdBlsPublicKey { @@ -253,18 +253,18 @@ mod tests { fn make_classic_config() -> QuorumConfig { QuorumConfig { quorum_type: QuorumType::Llmq100_67, - active_signers: 24, + active_signers: NonZeroU16::new(24).unwrap(), rotation: false, - window: 24, + window: NonZeroU32::new(24).unwrap(), } } fn make_rotating_config(active_signers: u16) -> QuorumConfig { QuorumConfig { quorum_type: QuorumType::Llmq60_75, - active_signers, + active_signers: NonZeroU16::new(active_signers).unwrap(), rotation: true, - window: 24, + window: NonZeroU32::new(24).unwrap(), } } @@ -457,6 +457,34 @@ mod tests { ); } + #[test] + fn should_take_rotating_quorum_signer_from_log2_of_active_signers_bits() { + // The signer is the `log2(active_signers)` bits below the top bit of the request id's + // last 8 bytes. All of them are set here, so the signer is the largest index those bits + // can hold, from a single active signer (no bits) to u16::MAX of them (15 bits). + let request_id = [0xff; 32]; + for (active_signers, signer) in [(1, 0), (2, 1), (32, 31), (u16::MAX, 32767)] { + let quorums: Quorums = [0, signer] + .into_iter() + .map(|index| { + ( + QuorumHash::from_byte_array([index as u8; 32]), + make_verification_quorum(1, Some(index)), + ) + }) + .collect(); + + let (_, chosen) = quorums + .choose_quorum(&make_rotating_config(active_signers), &request_id) + .expect("the signer's quorum is held"); + assert_eq!( + chosen.index, + Some(signer), + "{active_signers} active signers" + ); + } + } + #[test] fn choose_quorum_routes_by_config_rotation_flag() { let hash = QuorumHash::from_byte_array([20u8; 32]); diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/basic_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/basic_tests.rs index 8447a00afbd..254060057e5 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/basic_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/basic_tests.rs @@ -8,6 +8,7 @@ mod tests { use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters; use dpp::dashcore_rpc::json::QuorumType; use std::collections::BTreeMap; + use std::num::{NonZeroU16, NonZeroU32}; use strategy_tests::{IdentityInsertInfo, StartAddresses, StartIdentities, Strategy}; use drive_abci::config::{ @@ -462,15 +463,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/comprehensive_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/comprehensive_tests.rs index 54140c26377..06a58177fc4 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/comprehensive_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/comprehensive_tests.rs @@ -36,6 +36,7 @@ mod tests { use rand::prelude::StdRng; use rand::SeedableRng; use simple_signer::signer::SimpleSigner; + use std::num::{NonZeroU16, NonZeroU32}; use strategy_tests::frequency::Frequency; use strategy_tests::operations::DocumentAction::DocumentActionReplaceRandom; use strategy_tests::operations::{ @@ -364,15 +365,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/core_height_increase.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/core_height_increase.rs index 19b1b861005..2129dc4a685 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/core_height_increase.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/core_height_increase.rs @@ -20,6 +20,7 @@ mod tests { use drive_abci::test::helpers::setup::TestPlatformBuilder; use itertools::Itertools; use platform_version::version::PlatformVersion; + use std::num::{NonZeroU16, NonZeroU32}; use strategy_tests::frequency::Frequency; use strategy_tests::{IdentityInsertInfo, StartAddresses, StartIdentities, Strategy}; #[tokio::test] @@ -255,15 +256,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { @@ -366,15 +367,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { @@ -458,15 +459,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { @@ -545,15 +546,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 10, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { @@ -666,15 +667,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { @@ -863,15 +864,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig { @@ -1039,15 +1040,15 @@ mod tests { chain_lock: ChainLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, instant_lock: InstantLockConfig { quorum_type: QuorumType::Llmq100_67, quorum_size: 3, - quorum_window: 24, - quorum_active_signers: 24, + quorum_window: NonZeroU32::new(24).unwrap(), + quorum_active_signers: NonZeroU16::new(24).unwrap(), quorum_rotation: false, }, execution: ExecutionConfig {