From 3cf96b182037e3d08c5c80ac8cbf3df04b369579 Mon Sep 17 00:00:00 2001 From: Uncle Stinky Date: Tue, 8 Sep 2026 14:17:01 +0300 Subject: [PATCH] make BFT function name more self-descriptive and align implementation with paper Signed-off-by: Uncle Stinky --- Cargo.lock | 6 ++-- pallets/exodus/Cargo.toml | 2 +- pallets/exodus/src/benchmarking.rs | 4 +-- pallets/exodus/src/lib.rs | 20 ++++++------ pallets/exodus/src/tests.rs | 8 ++--- pallets/helpers/Cargo.toml | 2 +- pallets/helpers/src/lib.rs | 52 ++++++++++++++++++++++++++---- pallets/weaver/Cargo.toml | 2 +- pallets/weaver/src/lib.rs | 6 ++-- pallets/weaver/src/tests.rs | 20 ++++++------ 10 files changed, 80 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95384b5..b9a7d6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3684,7 +3684,7 @@ dependencies = [ [[package]] name = "ghost-exodus" -version = "0.0.7" +version = "0.0.8" dependencies = [ "chacha20poly1305", "frame-benchmarking", @@ -3757,7 +3757,7 @@ dependencies = [ [[package]] name = "ghost-helpers" -version = "0.0.6" +version = "0.0.7" dependencies = [ "frame-support", "ghost-traits", @@ -4109,7 +4109,7 @@ dependencies = [ [[package]] name = "ghost-weaver" -version = "0.0.4" +version = "0.0.5" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/pallets/exodus/Cargo.toml b/pallets/exodus/Cargo.toml index 4e9f99b..1ff2a83 100644 --- a/pallets/exodus/Cargo.toml +++ b/pallets/exodus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-exodus" -version = "0.0.7" +version = "0.0.8" description = "Threshold signature generation with DKG included" license.workspace = true authors.workspace = true diff --git a/pallets/exodus/src/benchmarking.rs b/pallets/exodus/src/benchmarking.rs index 832c4cb..1a07074 100644 --- a/pallets/exodus/src/benchmarking.rs +++ b/pallets/exodus/src/benchmarking.rs @@ -74,7 +74,7 @@ mod benchmarks { c: Linear<4, { T::MaxAuthorities::get() }> ) -> Result<(), BenchmarkError> { let max_signers = c as AuthIndex; - let min_signers = get_byzantium_threshold(max_signers); + let min_signers = get_byzantium_quorum_threshold(max_signers); let (mut dkg_state, network_curve, authority, authority_index) = prepare_pallet::(max_signers as usize); @@ -637,7 +637,7 @@ mod benchmarks { #[benchmark] fn register_signature_share() -> Result<(), BenchmarkError> { let max_signers = T::MaxAuthorities::get(); - let threshold = get_byzantium_threshold(max_signers); + let threshold = get_byzantium_quorum_threshold(max_signers); let network_id: NetworkIdOf = 69_420u64.unique_saturated_into(); let (mut dkg_state, network_curve, authority, authority_index) = diff --git a/pallets/exodus/src/lib.rs b/pallets/exodus/src/lib.rs index ed20965..70cd6ba 100644 --- a/pallets/exodus/src/lib.rs +++ b/pallets/exodus/src/lib.rs @@ -29,7 +29,7 @@ use sp_runtime::traits::BlockNumberProvider; use sp_io::hashing::blake2_256; use ghost_helpers::{ - get_byzantium_threshold, SubstrateBlake2Hasher, + get_byzantium_quorum_threshold, SubstrateBlake2Hasher, hash_chain::sequential_hash, networks::{NetworkData, NetworkCurve, NetworkType}, bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap}, @@ -263,7 +263,7 @@ pub struct Round1MaxBytes(sp_std::marker::PhantomData); impl Get for Round1MaxBytes { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); - let min_authorities = get_byzantium_threshold(max_authorities); + let min_authorities = get_byzantium_quorum_threshold(max_authorities); round1_package_size( min_authorities as usize, @@ -324,7 +324,7 @@ pub struct BindingFactorsProof(sp_std::marker::PhantomData); impl Get for BindingFactorsProof { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); - get_byzantium_threshold(max_authorities) + get_byzantium_quorum_threshold(max_authorities) .next_power_of_two() .trailing_zeros() } @@ -1422,7 +1422,7 @@ pub mod pallet { }; let threshold = ActiveAuthorities::::decode_len(&network_curve) - .map(|max_participants| get_byzantium_threshold(max_participants)) + .map(|max_participants| get_byzantium_quorum_threshold(max_participants)) .ok_or(Error::::DkgAuthoritiesNotInitialized)?; let exodus_session = package.session; @@ -1515,7 +1515,7 @@ pub mod pallet { let max_authorities = ActiveAuthorities::::decode_len(&network_curve) .ok_or(Error::::DkgAuthoritiesNotInitialized)?; - let threshold = get_byzantium_threshold(max_authorities); + let threshold = get_byzantium_quorum_threshold(max_authorities); let exodus_session = package.session; ensure!( @@ -1633,7 +1633,7 @@ pub mod pallet { }; let threshold = ActiveAuthorities::::decode_len(&network_curve) - .map(|max_participants| get_byzantium_threshold(max_participants)) + .map(|max_participants| get_byzantium_quorum_threshold(max_participants)) .ok_or(Error::::DkgAuthoritiesNotInitialized)?; let exodus_session = package.session; @@ -1898,7 +1898,7 @@ pub mod pallet { None => return weight, }; - let min_threshold = get_byzantium_threshold(qualification_len); + let min_threshold = get_byzantium_quorum_threshold(qualification_len); weight.saturating_accrue(T::DbWeight::get().reads(1)); let maybe_cursor = DkgMaybeCursor::::get(&network_curve); @@ -2711,7 +2711,7 @@ impl Pallet { } let authorities = QualificationAuthorities::::get(&network_curve); - let expected_count = get_byzantium_threshold(authorities.len()); + let expected_count = get_byzantium_quorum_threshold(authorities.len()); let expected_package_len = round1_package_size( expected_count, @@ -3283,7 +3283,7 @@ impl Pallet { let max_authorities = ActiveAuthorities::::decode_len(&network_curve) .ok_or(ExodusError::NoActiveAuthorities)?; - let threshold = get_byzantium_threshold(max_authorities); + let threshold = get_byzantium_quorum_threshold(max_authorities); let mut exodus_requests = ExodusRequests::::iter_prefix(&network_curve) .collect::)>>(); @@ -3732,7 +3732,7 @@ impl Pallet { .map(|max_signers| max_signers as AuthIndex) .ok_or(ExodusError::InvalidMaxSigners(0))?; - let min_signers = get_byzantium_threshold(max_signers); + let min_signers = get_byzantium_quorum_threshold(max_signers); if qualification_state.contains_index(authority_index) { return Ok(ExodusOk::DkgRoundAlreadyPassed( diff --git a/pallets/exodus/src/tests.rs b/pallets/exodus/src/tests.rs index a3ad699..b95b5cc 100644 --- a/pallets/exodus/src/tests.rs +++ b/pallets/exodus/src/tests.rs @@ -841,7 +841,7 @@ fn run_dkg_session( let threshold = ext.execute_with(|| { let len = ActiveAuthorities::::decode_len(&curve) .unwrap_or(0); - get_byzantium_threshold(len) as u16 + get_byzantium_quorum_threshold(len) as u16 }); let mut exodus_steps_outer = BTreeMap::new(); @@ -1259,7 +1259,7 @@ fn test_exodus_dkg_robustness() { assert_eq!(state.get_dkg_index(), dkg_index); assert_eq!(state.get_phase(), DkgPhase::Finalized); - let bft_threshold = get_byzantium_threshold(authorities_len); + let bft_threshold = get_byzantium_quorum_threshold(authorities_len); assert!(qualification_count >= bft_threshold); assert!(state.count_ones::() >= bft_threshold); assert_eq!(state.get_indexes().active_bits_count(), authorities_len as u32); @@ -1299,7 +1299,7 @@ fn test_prepare_round4_abort_on_byzantine_attack() { } } - let bft_threshold = get_byzantium_threshold(authorities_len); + let bft_threshold = get_byzantium_quorum_threshold(authorities_len); ext.execute_with(|| { let mut complaints_map = BitmapByAuthority::::default(); for authority in 0..=bft_threshold { @@ -1344,7 +1344,7 @@ fn test_prepare_round5_abort_on_byzantine_attack() { } } - let bft_threshold = get_byzantium_threshold(authorities_len); + let bft_threshold = get_byzantium_quorum_threshold(authorities_len); ext.execute_with(|| { let mut justifications_map = BitmapByAuthority::::default(); for authority in 0..=bft_threshold { diff --git a/pallets/helpers/Cargo.toml b/pallets/helpers/Cargo.toml index 17fb3db..2eec5ce 100644 --- a/pallets/helpers/Cargo.toml +++ b/pallets/helpers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-helpers" -version = "0.0.6" +version = "0.0.7" description = "Cryptographic utility suite for custom runtimes: optimized Bitmaps, UTXO parsing, Merkle Tree proofs, and Hash Chain components." license.workspace = true authors.workspace = true diff --git a/pallets/helpers/src/lib.rs b/pallets/helpers/src/lib.rs index 46f25d6..c13576b 100644 --- a/pallets/helpers/src/lib.rs +++ b/pallets/helpers/src/lib.rs @@ -34,20 +34,18 @@ pub type NetworkIdU32 = u32; pub type NetworkIdU64 = u64; pub type NetworkIdU128 = u128; -pub fn get_byzantium_threshold(value: I) -> I +pub fn get_byzantium_quorum_threshold(value: I) -> I where I: num_traits::PrimInt, { let zero = I::zero(); let one = I::one(); - let two = one + one; - let three = two + one; + let three = one + one + one; - if value == zero { - return zero; - } + if value == zero { return zero; } - (value - one) / three * two + one + let f = (value - one) / three; + value - f } pub struct SubstrateBlake2Hasher; @@ -97,3 +95,43 @@ impl GhostHasher for UtxoSha2Hasher { H256::zero() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bft_threshold_standard_paper_cases() { + // Lower boundaries (f increases here) + assert_eq!(get_byzantium_quorum_threshold(1u32), 1); + assert_eq!(get_byzantium_quorum_threshold(4u32), 3); + assert_eq!(get_byzantium_quorum_threshold(7u32), 5); + assert_eq!(get_byzantium_quorum_threshold(10u32), 7); + + // Upper boundaries (max nodes for a given 'f') + assert_eq!(get_byzantium_quorum_threshold(3u32), 3); + assert_eq!(get_byzantium_quorum_threshold(6u32), 5); + assert_eq!(get_byzantium_quorum_threshold(9u32), 7); + } + + #[test] + fn test_bft_threshold_zero_nodes() { + assert_eq!(get_byzantium_quorum_threshold(0u32), 0); + } + + #[test] + fn test_bft_threshold_integer_boundaries() { + let max_val = usize::MAX; + let expected_f = (max_val - 1) / 3; + let expected_honest = max_val - expected_f; + + assert_eq!(get_byzantium_quorum_threshold(max_val), expected_honest); + } + + #[test] + fn test_bft_threshold_different_types() { + assert_eq!(get_byzantium_quorum_threshold(4u8), 3u8); + assert_eq!(get_byzantium_quorum_threshold(4u64), 3u64); + assert_eq!(get_byzantium_quorum_threshold(4usize), 3usize); + } +} diff --git a/pallets/weaver/Cargo.toml b/pallets/weaver/Cargo.toml index bcf5ba9..fa7438d 100644 --- a/pallets/weaver/Cargo.toml +++ b/pallets/weaver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-weaver" -version = "0.0.4" +version = "0.0.5" description = "Weaving a secure cryptographic tapestry across different external chains." license.workspace = true authors.workspace = true diff --git a/pallets/weaver/src/lib.rs b/pallets/weaver/src/lib.rs index 6065449..35ab4a6 100644 --- a/pallets/weaver/src/lib.rs +++ b/pallets/weaver/src/lib.rs @@ -25,7 +25,7 @@ use sp_runtime::{ use ghost_helpers::{ bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap}, - get_byzantium_threshold, + get_byzantium_quorum_threshold, networks::{NetworkData, NetworkType}, }; use ghost_traits::{ @@ -487,7 +487,7 @@ pub mod pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); let len = Authorities::::decode_len().unwrap_or_default(); - let weaving_threshold = get_byzantium_threshold(len); + let weaving_threshold = get_byzantium_quorum_threshold(len); let maybe_tapestry_drafts = TapestryDrafts::::get(&network_id); @@ -850,7 +850,7 @@ impl Pallet { .parse_block_requests(pending_requests, deadline, request_id); let total_blocks = block_numbers.len(); - let weaving_threshold = get_byzantium_threshold(total_blocks); + let weaving_threshold = get_byzantium_quorum_threshold(total_blocks); let median_block = Self::validate_block_numbers_consensus( &mut block_numbers, diff --git a/pallets/weaver/src/tests.rs b/pallets/weaver/src/tests.rs index 6df3fcb..4fa6a27 100644 --- a/pallets/weaver/src/tests.rs +++ b/pallets/weaver/src/tests.rs @@ -65,7 +65,9 @@ fn should_successfully_compute_median_and_start_weaving_round() { let pair3 = AuthorityPair::from_string("//Carol", None).expect("Should be valid seed"); let network_id = 1; - let network_data = NetworkDataBuilder::default().build(); + let network_data = NetworkDataBuilder::default() + .with_block_deviation(5) + .build(); let authorities = vec![pair1.clone(), pair2.clone(), pair3.clone()]; let networks = vec![(network_id, network_data, Default::default())]; @@ -122,8 +124,8 @@ fn should_apply_cooldown_delay_on_consensus_failure() { .with_network_id(network_id); let att1 = context.with_authority_index(0).build_block_attestation(100); - let att2 = context.with_authority_index(1).build_block_attestation(500); - let att3 = context.with_authority_index(2).build_block_attestation(900); + let att2 = context.with_authority_index(1).build_block_attestation(102); + let att3 = context.with_authority_index(2).build_block_attestation(104); let sig1 = pair1.sign(&att1.encode()); let sig2 = pair2.sign(&att2.encode()); @@ -933,7 +935,7 @@ fn should_successfully_pull_utxo_thread_and_mint_currency() { } #[test] -fn on_initialize_should_finalize_session_when_threshold_or_timeout_is_reached() { +fn on_initialize_should_finalize_session_when_timeout_is_reached() { let pair1 = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed"); let pair2 = AuthorityPair::from_string("//Bob", None).expect("Should be valid seed"); let pair3 = AuthorityPair::from_string("//Charlie", None).expect("Should be valid seed"); @@ -995,14 +997,12 @@ fn on_initialize_should_finalize_session_when_threshold_or_timeout_is_reached() let current_session = CurrentWeavingSession::::get(&network_id); GhostWeaver::on_initialize(current_substrate_block); - let saved_loot = LoomStates::::get(&network_id, current_session); - assert_eq!(saved_loot, target_hash); + let saved_loom = LoomStates::::get(&network_id, current_session); + assert_ne!(saved_loom, target_hash); + assert_eq!(saved_loom, saved_loom); let updated_session = CurrentWeavingSession::::get(&network_id); - assert_eq!(updated_session, current_session + 1); - - System::set_block_number(15); - GhostWeaver::on_initialize(15); + assert_eq!(updated_session, current_session); }); }