From cc7721e3899f9740e35f65f3feb4cdc621926166 Mon Sep 17 00:00:00 2001 From: Uncle Stinky Date: Sun, 1 Nov 2026 21:06:31 +0300 Subject: [PATCH] optimize storage layout for TapestryStrands and fix OCW deadlock Signed-off-by: Uncle Stinky --- Cargo.lock | 4 +- pallets/weaver/Cargo.toml | 2 +- pallets/weaver/src/benchmarking.rs | 73 +++++++++++----- pallets/weaver/src/lib.rs | 120 ++++++++++++++++++++++----- pallets/weaver/src/migrations/mod.rs | 9 ++ pallets/weaver/src/migrations/v2.rs | 33 ++++++++ runtime/casper/Cargo.toml | 2 +- runtime/casper/src/lib.rs | 3 +- 8 files changed, 199 insertions(+), 47 deletions(-) create mode 100644 pallets/weaver/src/migrations/v2.rs diff --git a/Cargo.lock b/Cargo.lock index 6339a55..79b8d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1195,7 +1195,7 @@ dependencies = [ [[package]] name = "casper-runtime" -version = "4.0.19" +version = "4.0.20" dependencies = [ "casper-runtime-constants", "frame-benchmarking", @@ -4114,7 +4114,7 @@ dependencies = [ [[package]] name = "ghost-weaver" -version = "0.0.26" +version = "0.0.27" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/pallets/weaver/Cargo.toml b/pallets/weaver/Cargo.toml index 6803485..bafc1d3 100644 --- a/pallets/weaver/Cargo.toml +++ b/pallets/weaver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-weaver" -version = "0.0.26" +version = "0.0.27" description = "Weaving a secure cryptographic tapestry across different external chains." license.workspace = true authors.workspace = true diff --git a/pallets/weaver/src/benchmarking.rs b/pallets/weaver/src/benchmarking.rs index 52e4e63..187af16 100644 --- a/pallets/weaver/src/benchmarking.rs +++ b/pallets/weaver/src/benchmarking.rs @@ -77,21 +77,38 @@ mod benchmarks { fn weave_weft( a: Linear<1, { T::MaxAuthorities::get() as u32 }> ) -> Result<(), BenchmarkError> { - let weaving_session = 420; - let next_session = weaving_session + 1; + let weaving_session: WeavingSession = 420; + let next_session: WeavingSession = weaving_session + 1; let root_hash = H256::repeat_byte(0x69); let (network_id, authority, authority_index) = prepare_pallet::(a, None); - let tapestry_key = (network_id, weaving_session, next_session, root_hash); - TapestryStrands::::mutate(&tapestry_key, |tapestry| { - (0..a).for_each(|i| { - let index = i as AuthIndex; - if index == authority_index { return; } - tapestry.insert(index); - }); + let next_session_bytes = next_session.to_be_bytes(); + let weaving_session_bytes = weaving_session.to_be_bytes(); + + let metadata: &[&[u8]] = &[ + root_hash.as_ref(), + next_session_bytes.as_ref(), + weaving_session_bytes.as_ref(), + ]; + + let tapestry_strand_key = + sequential_hash::(metadata); + + let mut bitmap = WeaversBitmap::::empty(a); + (0..a).for_each(|i| { + let index = i as AuthIndex; + if index == authority_index { return; } + bitmap.insert(index); }); + let mut tapestry_map = BoundedBTreeMap::<_, _, T::MaxAuthorities>::default(); + tapestry_map + .try_insert(tapestry_strand_key.clone(), bitmap) + .map_err(|_| BenchmarkError::Stop("Failed to insert bitmap into benchmark map"))?; + + TapestryStrands::::insert(&network_id, tapestry_map); + let current_block = T::BlockNumberProvider::current_block_number(); let dummy_draft = TapestryDraftBuilder::default() .with_current_block(current_block) @@ -112,18 +129,24 @@ mod benchmarks { .sign(&hash_attestation.encode()) .expect("Signature should be created"); - let tapestry_strand_key = (network_id, weaving_session, next_session, root_hash); - let weaving_state = WeavingStates::::get(&network_id); assert_eq!(weaving_state.hash, H256::default()); assert_eq!(weaving_state.next_session, 0); assert_eq!(weaving_state.count, 0); - assert!(!TapestryStrands::::get(&tapestry_strand_key).contains(authority_index)); - assert_eq!( - TapestryStrands::::get(&tapestry_strand_key).count_ones::(), - a - 1 - ); + let tapestry_strand = TapestryStrands::::get(&network_id); + let maybe_tapestry_bitmap = tapestry_strand.get(&tapestry_strand_key); + + let authority_does_not_exists = maybe_tapestry_bitmap + .map(|bitmap| !bitmap.contains(authority_index)) + .ok_or(BenchmarkError::Stop("NetworkId should be registered"))?; + + let tapestry_participants = maybe_tapestry_bitmap + .map(|bitmap| bitmap.count_ones::()) + .ok_or(BenchmarkError::Stop("NetworkId should be registered"))?; + + assert!(authority_does_not_exists); + assert_eq!(tapestry_participants, a - 1); #[extrinsic_call] _(RawOrigin::None, hash_attestation, signature); @@ -133,11 +156,19 @@ mod benchmarks { assert_eq!(new_weaving_state.next_session, next_session); assert_eq!(new_weaving_state.count, a as AuthIndex); - assert!(TapestryStrands::::get(&tapestry_strand_key).contains(authority_index)); - assert_eq!( - TapestryStrands::::get(&tapestry_strand_key).count_ones::(), - a - ); + let tapestry_strand = TapestryStrands::::get(&network_id); + let maybe_tapestry_bitmap = tapestry_strand.get(&tapestry_strand_key); + + let authority_exists = maybe_tapestry_bitmap + .map(|bitmap| bitmap.contains(authority_index)) + .ok_or(BenchmarkError::Stop("NetworkId should be registered"))?; + + let tapestry_participants = maybe_tapestry_bitmap + .map(|bitmap| bitmap.count_ones::()) + .ok_or(BenchmarkError::Stop("NetworkId should be registered"))?; + + assert!(authority_exists); + assert_eq!(tapestry_participants, a); Ok(()) } diff --git a/pallets/weaver/src/lib.rs b/pallets/weaver/src/lib.rs index c5c5c50..f21f615 100644 --- a/pallets/weaver/src/lib.rs +++ b/pallets/weaver/src/lib.rs @@ -31,14 +31,19 @@ use sp_runtime::{ use ghost_helpers::{ bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap}, - get_byzantium_quorum_threshold, get_submission_probability, networks::{NetworkData, NetworkType}, + hash_chain::sequential_hash, + get_byzantium_quorum_threshold, get_submission_probability, + SubstrateBlake2Hasher, }; use ghost_traits::{ - bounded_bitmap::{BoundedBitmapReader, BoundedBitmapWriter}, + bounded_bitmap::{ + BoundedBitmapReader, BoundedBitmapWriter, + BoundedBitmapGenerator, + }, networks::{ - NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, - NetworkRpcResolver, + NetworkDataBasicHandler, NetworkDataInspectHandler, + NetworkDataMutateHandler, NetworkRpcResolver, }, }; @@ -103,7 +108,7 @@ type WeaversBitmap = BoundedBitmap::MaxAuthorities pub mod pallet { use super::*; - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -174,6 +179,7 @@ pub mod pallet { InvalidParticipantAttestation, NonExistentNetworkId, TooManyAttestations, + TooManyTapestries, TimeWentBackwards, WeavingIsActive, WeavingIsInactive, @@ -222,15 +228,10 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn tapestry_strands)] - pub(super) type TapestryStrands = StorageNMap< + pub(super) type TapestryStrands = StorageMap< _, - ( - NMapKey>, - NMapKey, - NMapKey, - NMapKey, - ), - WeaversBitmap, + Twox64Concat, NetworkIdOf, + BoundedBTreeMap, T::MaxAuthorities>, ValueQuery, >; @@ -399,20 +400,46 @@ pub mod pallet { Error::::WeavingIsInactive, ); + let authorities_len = Authorities::::decode_len() + .unwrap_or(0); + let root_hash = hash_attestation.root_hash; let next_session = hash_attestation.next_session; let weaving_session = hash_attestation.weaving_session(); + let next_session_bytes = next_session.to_be_bytes(); + let weaving_session_bytes = weaving_session.to_be_bytes(); + + let metadata: &[&[u8]] = &[ + root_hash.as_ref(), + next_session_bytes.as_ref(), + weaving_session_bytes.as_ref(), + ]; + + let tapestry_strand_key = + sequential_hash::(metadata); + let weavers_count = TapestryStrands::::try_mutate( - &(network_id, weaving_session, next_session, root_hash), + &network_id, |tapestry_strand| -> Result { + if !tapestry_strand.contains_key(&tapestry_strand_key) { + let empty_bitmap = WeaversBitmap::::empty(authorities_len); + tapestry_strand + .try_insert(tapestry_strand_key, empty_bitmap) + .map_err(|_| Error::::TooManyTapestries)?; + } + + let tapestry_bitmap = tapestry_strand + .get_mut(&tapestry_strand_key) + .ok_or(Error::::TooManyTapestries)?; + ensure!( - !tapestry_strand.contains(authority_index), + !tapestry_bitmap.contains(authority_index), Error::::AuthorityPartOfTrapestry, ); - tapestry_strand.insert(authority_index); - Ok(tapestry_strand.count_ones::()) + tapestry_bitmap.insert(authority_index); + Ok(tapestry_bitmap.count_ones::()) }, )?; @@ -594,8 +621,9 @@ pub mod pallet { .with_median_external(median_block) .build(); + TapestryStrands::::remove(&network_id); TapestryDrafts::::insert(&network_id, tapestry_draft); - weight.saturating_accrue(T::DbWeight::get().writes(1)); + weight.saturating_accrue(T::DbWeight::get().writes(2)); } } } @@ -890,8 +918,19 @@ impl Pallet { network_id, ))?; - let tapestry_key = (network_id, weaving_session, first_next_session, first_hash); - let tapestry_strand = TapestryStrands::::get(&tapestry_key); + let next_session_bytes = first_next_session.to_be_bytes(); + let weaving_session_bytes = weaving_session.to_be_bytes(); + + let metadata: &[&[u8]] = &[ + first_hash.as_ref(), + next_session_bytes.as_ref(), + weaving_session_bytes.as_ref(), + ]; + + let tapestry_strand_key = + sequential_hash::(metadata); + + let tapestry_strand = TapestryStrands::::get(&network_id); let context = AttestationContext::default() .with_weaving_session(weaving_session) @@ -899,7 +938,12 @@ impl Pallet { .with_network_id(network_id); Self::local_authorities().for_each(|(authority_index, authority_key)| { - if tapestry_strand.contains(authority_index) { + let already_part_of_tapestry_strand = tapestry_strand + .get(&tapestry_strand_key) + .map(|bitmap| bitmap.contains(authority_index)) + .unwrap_or(false); + + if already_part_of_tapestry_strand { log::error!( target: LOG_TARGET, "🕸️ Weft already weaved from authority #{:?} for network #{:?}.", @@ -909,6 +953,23 @@ impl Pallet { return; } + let last_attested = + NetworkAttestedBlocks::::get(&network_id, &authority_index); + + let lock: BlockNumberFor = LOCK_BLOCK_EXPIRATION.unique_saturated_into(); + let cooling_off_until = last_attested.saturating_add(lock); + + if current_block < cooling_off_until { + log::error!( + target: LOG_TARGET, + "🕸️ Weft is cooling for authority #{:?} for network #{:?} blocks left {:?}.", + authority_index, + network_id, + cooling_off_until.saturating_sub(current_block) + ); + return; + } + let hash_attestation = context .with_authority_index(authority_index) .build_hash_attestation(first_hash, first_next_session); @@ -1004,6 +1065,23 @@ impl Pallet { .with_network_id(network_id); Self::local_authorities().for_each(|(authority_index, authority_key)| { + let last_attested = + NetworkAttestedBlocks::::get(&network_id, &authority_index); + + let lock: BlockNumberFor = LOCK_BLOCK_EXPIRATION.unique_saturated_into(); + let cooling_off_until = last_attested.saturating_add(lock); + + if current_block < cooling_off_until { + log::error!( + target: LOG_TARGET, + "🕸️ Attestation is cooling for authority #{:?} for network #{:?} blocks left {:?}.", + authority_index, + network_id, + cooling_off_until.saturating_sub(current_block) + ); + return; + } + let block_attestation = context .with_authority_index(authority_index) .build_block_attestation(finalized_block); diff --git a/pallets/weaver/src/migrations/mod.rs b/pallets/weaver/src/migrations/mod.rs index 5cd70fb..4bf301e 100644 --- a/pallets/weaver/src/migrations/mod.rs +++ b/pallets/weaver/src/migrations/mod.rs @@ -1,4 +1,5 @@ pub mod v1; +pub mod v2; pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< 0, @@ -7,3 +8,11 @@ pub type MigrateV0ToV1 = frame_support::migrations::VersionedMigration< crate::Pallet::, ::DbWeight, >; + +pub type MigrateV1ToV2 = frame_support::migrations::VersionedMigration< + 1, + 2, + v2::UpdateTapestryStrandsStorageLayout, + crate::Pallet::, + ::DbWeight, +>; diff --git a/pallets/weaver/src/migrations/v2.rs b/pallets/weaver/src/migrations/v2.rs new file mode 100644 index 0000000..bebdb7b --- /dev/null +++ b/pallets/weaver/src/migrations/v2.rs @@ -0,0 +1,33 @@ +use frame_support::{ + traits::{Get, UncheckedOnRuntimeUpgrade}, + weights::Weight, +}; + +use core::marker::PhantomData; +use crate::{Config, LOG_TARGET}; + +pub struct UpdateTapestryStrandsStorageLayout(PhantomData); +impl UncheckedOnRuntimeUpgrade for UpdateTapestryStrandsStorageLayout { + fn on_runtime_upgrade() -> Weight { + let clear_key = frame_support::storage::storage_prefix( + b"GhostWeaver", + b"TapestryStrands" + ); + + let removal_results = frame_support::storage::unhashed::clear_prefix( + &clear_key, + None, + None + ); + + let killed_keys = removal_results.unique; + + log::info!( + target: LOG_TARGET, + "🧹 Success: Old StorageNMap for TapestryStrands wiped. Wiped {} keys from Trie.", + killed_keys + ); + + T::DbWeight::get().reads_writes(1, killed_keys as u64) + } +} diff --git a/runtime/casper/Cargo.toml b/runtime/casper/Cargo.toml index e7ee7eb..c912ef3 100644 --- a/runtime/casper/Cargo.toml +++ b/runtime/casper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "casper-runtime" -version = "4.0.19" +version = "4.0.20" build = "build.rs" description = "Runtime of the Casper Network" edition.workspace = true diff --git a/runtime/casper/src/lib.rs b/runtime/casper/src/lib.rs index 5112ad5..8a820db 100644 --- a/runtime/casper/src/lib.rs +++ b/runtime/casper/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("casper"), impl_name: create_runtime_str!("casper-svengali"), authoring_version: 0, - spec_version: 13, + spec_version: 14, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -1037,6 +1037,7 @@ pub type SignedExtra = ( /// Should be cleared after release. pub type Migrations = ( ghost_weaver::migrations::MigrateV0ToV1, + ghost_weaver::migrations::MigrateV1ToV2, ); /// Unchecked extrinsic type as expected by this runtime.