Merge branch 'pallet-weaver' into pallet-combined

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2026-10-29 14:25:50 +03:00
commit 8e44a7f5d2
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
5 changed files with 275 additions and 71 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "ghost-weaver"
version = "0.0.16"
version = "0.0.21"
description = "Weaving a secure cryptographic tapestry across different external chains."
license.workspace = true
authors.workspace = true

View File

@ -35,6 +35,9 @@ fn prepare_pallet<T: Config>(
T::NetworkDataHandler::register(network_id, network_data)
.expect("network should be valid for insertion");
let current_block = T::BlockNumberProvider::current_block_number();
Pallet::<T>::on_initialize(current_block);
(network_id, authority, 0)
}
@ -53,6 +56,7 @@ mod benchmarks {
let block_attestation = AttestationContext::default()
.with_network_id(network_id)
.with_authority_index(authority_index)
.with_current_block(1337u16.into())
.build_block_attestation(420);
let signature = authority
@ -101,6 +105,7 @@ mod benchmarks {
.with_network_id(network_id)
.with_weaving_session(weaving_session)
.with_authority_index(authority_index)
.with_current_block(1337u16.into())
.build_hash_attestation(root_hash, next_session);
let signature = authority
@ -320,9 +325,12 @@ mod benchmarks {
let first_sha = sp_io::hashing::sha2_256(&last_header[..80]);
let mut native_le = sp_io::hashing::sha2_256(&first_sha);
native_le.reverse();
let dynamic_trusted_root = H256::from(native_le);
LoomStates::<T>::insert(&network_id, &session, dynamic_trusted_root);
let at_block = 420;
let dynamic_trusted_root = H256::from(native_le);
let loom_state = LoomState { at_block, root_hash: dynamic_trusted_root };
LoomStates::<T>::insert(&network_id, &session, loom_state);
let utxo_thread_proof = UtxoThreadProof::new(
gettxproof_bytes,
@ -360,8 +368,11 @@ mod benchmarks {
let root_string =
hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129")
.expect("EVM Root hex should be valid");
let at_block = 420;
let expected_root = H256::from_slice(&root_string);
LoomStates::<T>::insert(&network_id, &session, expected_root);
let loom_state = LoomState { at_block, root_hash: expected_root };
LoomStates::<T>::insert(&network_id, &session, loom_state);
let receiver_32: [u8; 32] =
hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e")

View File

@ -22,7 +22,7 @@ use sp_runtime::{
storage::StorageValueRef,
storage_lock::{StorageLock, Time},
},
traits::{BlockNumberProvider, UniqueSaturatedInto},
traits::{BlockNumberProvider, Saturating, UniqueSaturatedInto},
RuntimeAppPublic,
};
@ -182,6 +182,15 @@ pub mod pallet {
CouldNotRegisterIncoming,
}
#[pallet::storage]
#[pallet::getter(fn attested_blocks)]
pub(super) type AttestedBlocks<T: Config> = StorageMap<
_,
Twox64Concat, AuthIndex,
BlockNumberFor<T>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn current_weaving_session)]
pub(super) type CurrentWeavingSession<T: Config> =
@ -202,7 +211,7 @@ pub mod pallet {
_,
Twox64Concat, NetworkIdOf<T>,
Twox64Concat, WeavingSession,
H256,
LoomState,
ValueQuery,
>;
@ -288,7 +297,12 @@ pub mod pallet {
);
return;
}
LoomStates::<T>::insert(&data.0, data.1, data.2);
let initial_loom_state = LoomState {
at_block: 0u16.into(),
root_hash: data.2,
};
LoomStates::<T>::insert(&data.0, data.1, initial_loom_state);
});
if !self.authorities.is_empty() {
@ -330,26 +344,20 @@ pub mod pallet {
let external_block = block_attestation.external_block;
let current_authorities_count = Authorities::<T>::decode_len().
unwrap_or_default();
NetworkAttestations::<T>::try_mutate(
&network_id,
|attestations| -> DispatchResult {
if attestations.is_empty() {
attestations.bounded_resize(current_authorities_count, None);
}
let index = authority_index as usize;
ensure!(
(authority_index as usize) < attestations.len(),
Error::<T>::TooManyAttestations,
);
attestations[authority_index as usize] = Some(external_block);
ensure!(index < attestations.len(), Error::<T>::TooManyAttestations);
attestations[index] = Some(external_block);
Ok(())
})?;
let current_block = T::BlockNumberProvider::current_block_number();
AttestedBlocks::<T>::insert(&authority_index, current_block);
Self::deposit_event(Event::<T>::BlockAttestated {
network_id,
authority_index,
@ -406,6 +414,9 @@ pub mod pallet {
}
});
let current_block = T::BlockNumberProvider::current_block_number();
AttestedBlocks::<T>::insert(&authority_index, current_block);
Self::deposit_event(Event::<T>::HashAttestated {
authority_index,
next_session,
@ -438,7 +449,7 @@ pub mod pallet {
let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(Error::<T>::NonExistentNetworkId)?;
let root_hash = LoomStates::<T>::get(&network_id, &session);
let state = LoomStates::<T>::get(&network_id, &session);
let pulled_thread_key = thread_proof.get_unique_key(session, network_id);
ensure!(
@ -447,7 +458,7 @@ pub mod pallet {
);
let (receiver_account, amount) = thread_proof
.verify_thread_proof(root_hash, network_id, &network_data.gatekeeper)
.verify_thread_proof(state.root_hash, network_id, &network_data.gatekeeper)
.ok_or(Error::<T>::InvalidMerkleProof)?;
let receiver_bytes: &[u8; 32] = receiver_account.as_ref();
@ -504,11 +515,13 @@ pub mod pallet {
let current_session = CurrentWeavingSession::<T>::get(&network_id);
let loom_state_changed =
LoomStates::<T>::mutate(&network_id, &current_session, |loom_state| {
if *loom_state != weaving_state.hash && threshold_reached {
*loom_state = weaving_state.hash;
return true;
}
false
if !threshold_reached { return false; }
let hash_changed = loom_state.root_hash != weaving_state.hash;
loom_state.at_block = tapestry_draft.median;
if hash_changed { loom_state.root_hash = weaving_state.hash; }
hash_changed
});
if loom_state_changed && weaving_state.next_session != current_session {
@ -518,8 +531,9 @@ pub mod pallet {
CurrentWeavingSession::<T>::insert(&network_id, next_session);
}
weight.saturating_accrue(T::DbWeight::get().writes(1));
weight.saturating_accrue(T::DbWeight::get().writes(2));
TapestryDrafts::<T>::remove(&network_id);
WeavingStates::<T>::remove(&network_id);
}
}
None => {
@ -532,9 +546,11 @@ pub mod pallet {
weight.saturating_accrue(T::DbWeight::get().reads(1));
let attestation_len = NetworkAttestations::<T>::decode_len(&network_id);
if attestation_len.unwrap_or(0) > len {
weight.saturating_accrue(T::DbWeight::get().writes(1));
NetworkAttestations::<T>::remove(&network_id);
if attestation_len.unwrap_or(0) != len {
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
NetworkAttestations::<T>::mutate(&network_id, |attestations| {
attestations.bounded_resize(len, None);
});
return weight;
}
@ -616,6 +632,21 @@ pub mod pallet {
.propagate(true)
.build()
}
Call::pull_thread {
network_id,
session,
thread_proof,
} => {
let thread_context =
Self::validate_thread(network_id, session, thread_proof)?;
ValidTransaction::with_tag_prefix("WeaverThread")
.priority(T::UnsignedPriority::get())
.and_provides(thread_context.to_fixed_bytes())
.longevity(LOCK_BLOCK_EXPIRATION)
.propagate(true)
.build()
}
_ => InvalidTransaction::Call.into(),
}
}
@ -623,6 +654,31 @@ pub mod pallet {
}
impl<T: Config> Pallet<T> {
fn validate_thread(
network_id: &NetworkIdOf<T>,
session: &WeavingSession,
thread_proof: &ThreadProof<BalanceOf<T>>,
) -> Result<H256, InvalidTransaction> {
let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(InvalidTransaction::BadProof)?;
let root_hash = LoomStates::<T>::get(&network_id, &session).root_hash;
if root_hash == Default::default() {
return Err(InvalidTransaction::BadProof);
}
thread_proof
.verify_thread_proof(root_hash, *network_id, &network_data.gatekeeper)
.ok_or(InvalidTransaction::BadProof)?;
let pulled_thread_key = thread_proof.get_unique_key(*session, *network_id);
if PulledThreads::<T>::contains_key(&pulled_thread_key) {
return Err(InvalidTransaction::Stale);
}
Ok(pulled_thread_key)
}
fn validate_attestation_signature<A>(
attestation: &A,
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
@ -632,16 +688,6 @@ impl<T: Config> Pallet<T> {
A: Encode,
{
let authority_index = attestation.authority_index();
let attestation_block = attestation.block();
let network_id = attestation.network_id();
if DisabledAuthorities::<T>::get().contains(authority_index) {
return Err(InvalidTransaction::BadSigner);
}
if T::BlockNumberProvider::current_block_number() <= attestation_block {
return Err(InvalidTransaction::BadProof);
}
Authorities::<T>::get()
.get(authority_index as usize)
@ -653,6 +699,31 @@ impl<T: Config> Pallet<T> {
.ok_or(InvalidTransaction::BadSigner)
})?;
let attestation_block = attestation.block();
let current_block = T::BlockNumberProvider::current_block_number();
let lock: BlockNumberFor<T> = LOCK_BLOCK_EXPIRATION.unique_saturated_into();
let block_expiration = attestation_block.saturating_add(lock);
let block_expired = current_block >= block_expiration;
let block_attested = AttestedBlocks::<T>::get(authority_index)
.ge(&attestation_block);
if block_expired || block_attested {
return Err(InvalidTransaction::BadProof);
}
let network_id = attestation.network_id();
let current_attestation_len = NetworkAttestations::<T>::decode_len(&network_id)
.unwrap_or(0);
if authority_index as usize >= current_attestation_len {
return Err(InvalidTransaction::BadSigner.into());
}
if DisabledAuthorities::<T>::get().contains(authority_index) {
return Err(InvalidTransaction::BadSigner);
}
if !T::NetworkDataHandler::contains_key(&network_id) {
return Err(InvalidTransaction::BadProof);
}

View File

@ -76,6 +76,7 @@ fn should_successfully_compute_median_and_start_weaving_round() {
let networks = vec![(network_id, network_data, Default::default())];
new_test_ext(authorities, networks).execute_with(|| {
GhostWeaver::on_initialize(1);
System::set_block_number(2);
let context = AttestationContext::default()
@ -120,6 +121,7 @@ fn should_apply_cooldown_delay_on_consensus_failure() {
let networks = vec![(network_id, network_data, Default::default())];
new_test_ext(authorities, networks).execute_with(|| {
GhostWeaver::on_initialize(9);
System::set_block_number(10);
let context = AttestationContext::default()
@ -168,6 +170,7 @@ fn validate_unsigned_should_prevent_mempool_dos_via_tags() {
let networks = vec![(network_id, network_data, Default::default())];
new_test_ext(authorities, networks).execute_with(|| {
GhostWeaver::on_initialize(1);
System::set_block_number(2);
let att = AttestationContext::default()
@ -240,6 +243,9 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
ext.register_extension(TransactionPoolExt::new(pool));
ext.execute_with(|| {
GhostWeaver::on_initialize(1);
GhostWeaver::on_initialize(2);
let network_id_encoded = evm_network_id.encode();
let mut endpoint_key = b"ghost-weaver::".to_vec();
@ -313,7 +319,6 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
ext.execute_with(|| {
System::set_block_number(1u64);
GhostWeaver::offchain_worker(1u64);
System::set_block_number(2u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 1);
@ -341,7 +346,6 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
ext.execute_with(|| {
System::set_block_number(2u64);
GhostWeaver::offchain_worker(2u64);
System::set_block_number(3u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 2);
@ -362,6 +366,7 @@ fn offchain_worker_should_fetch_block_and_submit_unsigned_tx() {
assert_eq!(curr_evm_block, 25639556);
assert!(TapestryDrafts::<TestRuntime>::get(&evm_network_id).is_none());
GhostWeaver::on_initialize(3u64);
GhostWeaver::on_initialize(4u64);
assert!(TapestryDrafts::<TestRuntime>::get(&evm_network_id).is_some());
});
@ -372,6 +377,9 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
let evm_network_id = 1u32;
let utxo_network_id = 69u32;
let median_utxo_block = 960129;
let median_evm_block = 25639556;
let pair = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
let new_evm_network_data = NetworkDataBuilder::default()
@ -406,11 +414,12 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
ext.register_extension(TransactionPoolExt::new(pool));
ext.execute_with(|| {
GhostWeaver::on_initialize(1);
GhostWeaver::on_initialize(2);
let tapestry_draft = TapestryDraftBuilder::<crate::ExternalBlockNumber, u32>::default();
let evm_tapestry_draft = tapestry_draft.with_median_external(25639556).build();
let utxo_tapestry_draft = tapestry_draft.with_median_external(960129).build();
let evm_tapestry_draft = tapestry_draft.with_median_external(median_evm_block).build();
let utxo_tapestry_draft = tapestry_draft.with_median_external(median_utxo_block).build();
CurrentWeavingSession::<TestRuntime>::insert(&evm_network_id, evm_session);
CurrentWeavingSession::<TestRuntime>::insert(&utxo_network_id, utxo_session);
@ -492,7 +501,6 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
ext.execute_with(|| {
System::set_block_number(1u64);
GhostWeaver::offchain_worker(1u64);
System::set_block_number(2u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 1);
@ -518,14 +526,15 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
GhostWeaver::on_initialize(3u64);
let curr_state = LoomStates::<TestRuntime>::get(&utxo_network_id, &utxo_session);
assert_eq!(prev_state, H256::default());
assert_eq!(curr_state, block_hash);
assert_eq!(prev_state.root_hash, H256::default());
assert_eq!(curr_state.root_hash, block_hash);
assert_eq!(prev_state.at_block, 0);
assert_eq!(curr_state.at_block, median_utxo_block);
});
ext.execute_with(|| {
System::set_block_number(2u64);
GhostWeaver::offchain_worker(2u64);
System::set_block_number(3u64);
let state = pool_state.read();
assert_eq!(state.transactions.len(), 2);
@ -542,17 +551,20 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
assert_eq!(curr_weaving_state.next_session, evm_session + 1);
let bytes = hex::decode("f72b9ef0b66c8cc3952ffd11dc0b85f3b02e6edfa9236da400138f2fcba4668d")
.expect("UTXO block hash should be valid");
.expect("EVM block hash should be valid");
let block_hash = H256::from_slice(&bytes);
assert_eq!(curr_weaving_state.hash, block_hash);
let prev_state = LoomStates::<TestRuntime>::get(&evm_network_id, &evm_session);
GhostWeaver::on_initialize(3u64);
GhostWeaver::on_initialize(4u64);
let curr_state = LoomStates::<TestRuntime>::get(&evm_network_id, &evm_session);
assert_eq!(prev_state, H256::default());
assert_eq!(curr_state, block_hash);
assert_eq!(prev_state.root_hash, H256::default());
assert_eq!(curr_state.root_hash, block_hash);
assert_eq!(prev_state.at_block, 0);
assert_eq!(curr_state.at_block, median_evm_block);
});
}
@ -635,14 +647,18 @@ fn should_successfully_pull_evm_thread_and_mint_currency() {
];
new_test_ext(authorities, networks).execute_with(|| {
GhostWeaver::on_initialize(1);
// https://sepolia.etherscan.io/address/0x7fec11e652f4d54e8cf11c4b52c360380332f2bd#readContract
// getRoot: session 0, atBlock 11716130
let session = 0;
let root_string =
hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129")
.expect("Root hex should be valid");
let expected_root = H256::from_slice(&root_string);
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_root);
let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root };
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_loom_state);
let mut total_issuance = 0;
@ -755,7 +771,8 @@ fn should_successfully_pull_utxo_thread_and_mint_currency() {
.expect("Bitcoin Root hex should be valid");
let expected_root = H256::from_slice(&block_hash);
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_root);
let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root };
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_loom_state);
let expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
let mut receiver_raw = [0u8; 32];
@ -853,7 +870,8 @@ fn should_successfully_pull_utxo_thread_and_reject_nominal_varint_encoding() {
.expect("Bitcoin Root hex should be valid");
let expected_root = H256::from_slice(&block_hash);
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_root);
let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root };
LoomStates::<TestRuntime>::insert(&network_id, &session, expected_loom_state);
let expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
let mut receiver_raw = [0u8; 32];
@ -928,6 +946,7 @@ fn on_initialize_should_finalize_session_when_timeout_is_reached() {
new_test_ext(authorities, networks).execute_with(|| {
let current_substrate_block = 10;
GhostWeaver::on_initialize(current_substrate_block);
System::set_block_number(current_substrate_block + 1);
let tapestry_draft = TapestryDraftBuilder::default()
@ -975,8 +994,96 @@ fn on_initialize_should_finalize_session_when_timeout_is_reached() {
GhostWeaver::on_initialize(current_substrate_block);
let saved_loom = LoomStates::<TestRuntime>::get(&network_id, current_session);
assert_ne!(saved_loom, target_hash);
assert_eq!(saved_loom, saved_loom);
assert_ne!(saved_loom.root_hash, target_hash);
assert_eq!(saved_loom.at_block, 0);
let updated_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
assert_eq!(updated_session, current_session);
});
}
#[test]
fn on_initialize_should_update_at_block_when_threshold_reached_with_same_hash() {
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");
let network_id = 1;
let new_network_data = NetworkDataBuilder::default()
.with_network_type(NetworkType::Evm)
.build();
let authorities = vec![pair1.clone(), pair2.clone(), pair3.clone()];
let networks = vec![(network_id, new_network_data, 0u64)];
new_test_ext(authorities, networks).execute_with(|| {
let current_substrate_block = 10;
let current_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
let target_hash = H256::repeat_byte(0xAA);
LoomStates::<TestRuntime>::insert(&network_id, &current_session, crate::LoomState {
root_hash: target_hash,
at_block: 0,
});
System::set_block_number(current_substrate_block);
GhostWeaver::on_initialize(current_substrate_block);
let tapestry_draft = TapestryDraftBuilder::default()
.with_current_block(current_substrate_block)
.with_block_delay(5u32)
.with_median_external(100)
.build();
TapestryDrafts::<TestRuntime>::insert(&network_id, tapestry_draft);
let attestation = AttestationContext::default()
.with_current_block(current_substrate_block)
.with_network_id(network_id)
.with_weaving_session(current_session);
let hash_att1 = attestation
.with_authority_index(0)
.build_hash_attestation(target_hash, current_session);
let hash_att2 = attestation
.with_authority_index(1)
.build_hash_attestation(target_hash, current_session);
let hash_att3 = attestation
.with_authority_index(2)
.build_hash_attestation(target_hash, current_session);
assert!(GhostWeaver::weave_weft(
RuntimeOrigin::none(),
hash_att1,
pair1.sign(&hash_att1.encode())
)
.is_ok());
assert!(GhostWeaver::weave_weft(
RuntimeOrigin::none(),
hash_att2,
pair2.sign(&hash_att2.encode())
)
.is_ok());
assert!(GhostWeaver::weave_weft(
RuntimeOrigin::none(),
hash_att3,
pair3.sign(&hash_att3.encode())
)
.is_ok());
let state_before = WeavingStates::<TestRuntime>::get(&network_id);
assert_eq!(state_before.count, 3);
assert_eq!(state_before.hash, target_hash);
GhostWeaver::on_initialize(current_substrate_block);
let saved_loom = LoomStates::<TestRuntime>::get(&network_id, current_session);
assert_eq!(saved_loom.root_hash, target_hash);
assert_eq!(saved_loom.at_block, 100);
let updated_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
assert_eq!(updated_session, current_session);

View File

@ -14,7 +14,6 @@ use ghost_helpers::{
merkle_tree::verify_tree_proof,
SubstrateBlake2Hasher, SubstrateKeccakHasher, UtxoSha2Hasher,
};
use ghost_traits::hashing::GhostHasher;
use crate::{ExternalBlockNumber, ThreadId, WeavingSession};
@ -53,6 +52,25 @@ where
}
}
#[derive(
Default,
RuntimeDebug,
Clone,
Copy,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
TypeInfo,
MaxEncodedLen,
)]
pub struct LoomState {
pub at_block: ExternalBlockNumber,
pub root_hash: H256,
}
#[derive(
Default,
RuntimeDebug,
@ -350,8 +368,10 @@ where
{
if EVM_MERKLE_DEPTH as usize != self.proof.len() { return None; }
let preimage_hash = self.get_preimage_hash(network_id)?;
let proof_valid = verify_tree_proof::<SubstrateKeccakHasher, _>(
&self.get_preimage_slice(network_id).to_fixed_bytes(),
&preimage_hash.to_fixed_bytes(),
self.proof.as_ref(),
root_hash,
self.proof_index,
@ -362,22 +382,17 @@ where
Some((self.receiver.clone(), self.amount))
}
fn get_preimage_slice<NetworkId>(&self, network_id: NetworkId) -> H256
fn get_preimage_hash<NetworkId>(&self, network_id: NetworkId) -> Option<H256>
where
NetworkId: Copy + Clone + Eq + PartialEq + UniqueSaturatedInto<u64>,
{
let slot_index_usize: usize = self.slot_index.unique_saturated_into();
let mut slot_values: Vec<H256> = self.slots.to_vec();
match slot_values.get_mut(slot_index_usize) {
Some(slot) => {
*slot = self.compute_arguments_hash(network_id);
cumulative_hash::<SubstrateKeccakHasher, _>(slot_values)
}
None => {
SubstrateKeccakHasher::empty()
}
}
self.slots.get(slot_index_usize)
.filter(|&&provided_hash| {
provided_hash == self.compute_arguments_hash(network_id)
})
.map(|_| cumulative_hash::<SubstrateKeccakHasher, _>(self.slots.iter()))
}
pub fn get_unique_key<NetworkId>(