extend loom state to track last extracted median block

Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
Uncle Stinky 2026-10-21 19:32:57 +03:00
parent ba575d0ab8
commit 6ae8fd9ed4
Signed by: st1nky
GPG Key ID: 016064BD97603B40
5 changed files with 157 additions and 26 deletions

View File

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

View File

@ -325,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,
@ -365,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

@ -211,7 +211,7 @@ pub mod pallet {
_,
Twox64Concat, NetworkIdOf<T>,
Twox64Concat, WeavingSession,
H256,
LoomState,
ValueQuery,
>;
@ -297,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() {
@ -444,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!(
@ -453,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();
@ -510,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 {
@ -655,7 +662,7 @@ impl<T: Config> Pallet<T> {
let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(InvalidTransaction::BadProof)?;
let root_hash = LoomStates::<T>::get(&network_id, &session);
let root_hash = LoomStates::<T>::get(&network_id, &session).root_hash;
if root_hash == Default::default() {
return Err(InvalidTransaction::BadProof);
}

View File

@ -377,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()
@ -415,8 +418,8 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
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);
@ -523,8 +526,10 @@ 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(|| {
@ -546,7 +551,7 @@ 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);
@ -556,8 +561,10 @@ fn offchain_worker_should_fetch_hash_and_submit_unsigned_tx() {
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);
});
}
@ -648,8 +655,10 @@ fn should_successfully_pull_evm_thread_and_mint_currency() {
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;
@ -762,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];
@ -860,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];
@ -983,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

@ -52,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,