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] [package]
name = "ghost-weaver" name = "ghost-weaver"
version = "0.0.20" version = "0.0.21"
description = "Weaving a secure cryptographic tapestry across different external chains." description = "Weaving a secure cryptographic tapestry across different external chains."
license.workspace = true license.workspace = true
authors.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 first_sha = sp_io::hashing::sha2_256(&last_header[..80]);
let mut native_le = sp_io::hashing::sha2_256(&first_sha); let mut native_le = sp_io::hashing::sha2_256(&first_sha);
native_le.reverse(); 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( let utxo_thread_proof = UtxoThreadProof::new(
gettxproof_bytes, gettxproof_bytes,
@ -365,8 +368,11 @@ mod benchmarks {
let root_string = let root_string =
hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129") hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129")
.expect("EVM Root hex should be valid"); .expect("EVM Root hex should be valid");
let at_block = 420;
let expected_root = H256::from_slice(&root_string); 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] = let receiver_32: [u8; 32] =
hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e") hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e")

View File

@ -211,7 +211,7 @@ pub mod pallet {
_, _,
Twox64Concat, NetworkIdOf<T>, Twox64Concat, NetworkIdOf<T>,
Twox64Concat, WeavingSession, Twox64Concat, WeavingSession,
H256, LoomState,
ValueQuery, ValueQuery,
>; >;
@ -297,7 +297,12 @@ pub mod pallet {
); );
return; 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() { if !self.authorities.is_empty() {
@ -444,7 +449,7 @@ pub mod pallet {
let network_data = T::NetworkDataHandler::get(&network_id) let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(Error::<T>::NonExistentNetworkId)?; .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); let pulled_thread_key = thread_proof.get_unique_key(session, network_id);
ensure!( ensure!(
@ -453,7 +458,7 @@ pub mod pallet {
); );
let (receiver_account, amount) = thread_proof 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)?; .ok_or(Error::<T>::InvalidMerkleProof)?;
let receiver_bytes: &[u8; 32] = receiver_account.as_ref(); 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 current_session = CurrentWeavingSession::<T>::get(&network_id);
let loom_state_changed = let loom_state_changed =
LoomStates::<T>::mutate(&network_id, &current_session, |loom_state| { LoomStates::<T>::mutate(&network_id, &current_session, |loom_state| {
if *loom_state != weaving_state.hash && threshold_reached { if !threshold_reached { return false; }
*loom_state = weaving_state.hash;
return true; let hash_changed = loom_state.root_hash != weaving_state.hash;
} loom_state.at_block = tapestry_draft.median;
false if hash_changed { loom_state.root_hash = weaving_state.hash; }
hash_changed
}); });
if loom_state_changed && weaving_state.next_session != current_session { 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) let network_data = T::NetworkDataHandler::get(&network_id)
.ok_or(InvalidTransaction::BadProof)?; .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() { if root_hash == Default::default() {
return Err(InvalidTransaction::BadProof); 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 evm_network_id = 1u32;
let utxo_network_id = 69u32; 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 pair = AuthorityPair::from_string("//Alice", None).expect("Should be valid seed");
let new_evm_network_data = NetworkDataBuilder::default() 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); GhostWeaver::on_initialize(2);
let tapestry_draft = TapestryDraftBuilder::<crate::ExternalBlockNumber, u32>::default(); let tapestry_draft = TapestryDraftBuilder::<crate::ExternalBlockNumber, u32>::default();
let evm_tapestry_draft = tapestry_draft.with_median_external(25639556).build(); let evm_tapestry_draft = tapestry_draft.with_median_external(median_evm_block).build();
let utxo_tapestry_draft = tapestry_draft.with_median_external(960129).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(&evm_network_id, evm_session);
CurrentWeavingSession::<TestRuntime>::insert(&utxo_network_id, utxo_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); GhostWeaver::on_initialize(3u64);
let curr_state = LoomStates::<TestRuntime>::get(&utxo_network_id, &utxo_session); let curr_state = LoomStates::<TestRuntime>::get(&utxo_network_id, &utxo_session);
assert_eq!(prev_state, H256::default()); assert_eq!(prev_state.root_hash, H256::default());
assert_eq!(curr_state, block_hash); 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(|| { 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); assert_eq!(curr_weaving_state.next_session, evm_session + 1);
let bytes = hex::decode("f72b9ef0b66c8cc3952ffd11dc0b85f3b02e6edfa9236da400138f2fcba4668d") 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); let block_hash = H256::from_slice(&bytes);
assert_eq!(curr_weaving_state.hash, block_hash); 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); GhostWeaver::on_initialize(4u64);
let curr_state = LoomStates::<TestRuntime>::get(&evm_network_id, &evm_session); let curr_state = LoomStates::<TestRuntime>::get(&evm_network_id, &evm_session);
assert_eq!(prev_state, H256::default()); assert_eq!(prev_state.root_hash, H256::default());
assert_eq!(curr_state, block_hash); 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 = let root_string =
hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129") hex::decode("07f4a5ba4750d9ddfc080429287cbcbde7464aa2f8ae13feaade0dcb45ca6129")
.expect("Root hex should be valid"); .expect("Root hex should be valid");
let expected_root = H256::from_slice(&root_string); 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; 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"); .expect("Bitcoin Root hex should be valid");
let expected_root = H256::from_slice(&block_hash); 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 expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
let mut receiver_raw = [0u8; 32]; 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"); .expect("Bitcoin Root hex should be valid");
let expected_root = H256::from_slice(&block_hash); 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 expected_receiver_bytes = hex::decode("1657090d02985f0cdb41436fe2d5a83a3acb1995d963a698e7e619e927bdcc5e").unwrap();
let mut receiver_raw = [0u8; 32]; 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); GhostWeaver::on_initialize(current_substrate_block);
let saved_loom = LoomStates::<TestRuntime>::get(&network_id, current_session); let saved_loom = LoomStates::<TestRuntime>::get(&network_id, current_session);
assert_ne!(saved_loom, target_hash); assert_ne!(saved_loom.root_hash, target_hash);
assert_eq!(saved_loom, saved_loom); 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); let updated_session = CurrentWeavingSession::<TestRuntime>::get(&network_id);
assert_eq!(updated_session, current_session); 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( #[derive(
Default, Default,
RuntimeDebug, RuntimeDebug,