diff --git a/pallets/weaver/Cargo.toml b/pallets/weaver/Cargo.toml index c2aed44..ba049dc 100644 --- a/pallets/weaver/Cargo.toml +++ b/pallets/weaver/Cargo.toml @@ -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 diff --git a/pallets/weaver/src/benchmarking.rs b/pallets/weaver/src/benchmarking.rs index 5e047b8..52e4e63 100644 --- a/pallets/weaver/src/benchmarking.rs +++ b/pallets/weaver/src/benchmarking.rs @@ -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::::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::::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::::insert(&network_id, &session, expected_root); + let loom_state = LoomState { at_block, root_hash: expected_root }; + LoomStates::::insert(&network_id, &session, loom_state); let receiver_32: [u8; 32] = hex::decode("0ae3ba32f8555fe3031461e2cb6feeadb67029ef7490b982824fc90751a4cb3e") diff --git a/pallets/weaver/src/lib.rs b/pallets/weaver/src/lib.rs index c92c456..6e01a67 100644 --- a/pallets/weaver/src/lib.rs +++ b/pallets/weaver/src/lib.rs @@ -211,7 +211,7 @@ pub mod pallet { _, Twox64Concat, NetworkIdOf, Twox64Concat, WeavingSession, - H256, + LoomState, ValueQuery, >; @@ -297,7 +297,12 @@ pub mod pallet { ); return; } - LoomStates::::insert(&data.0, data.1, data.2); + let initial_loom_state = LoomState { + at_block: 0u16.into(), + root_hash: data.2, + }; + + LoomStates::::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::::NonExistentNetworkId)?; - let root_hash = LoomStates::::get(&network_id, &session); + let state = LoomStates::::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::::InvalidMerkleProof)?; let receiver_bytes: &[u8; 32] = receiver_account.as_ref(); @@ -510,11 +515,13 @@ pub mod pallet { let current_session = CurrentWeavingSession::::get(&network_id); let loom_state_changed = LoomStates::::mutate(&network_id, ¤t_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 Pallet { let network_data = T::NetworkDataHandler::get(&network_id) .ok_or(InvalidTransaction::BadProof)?; - let root_hash = LoomStates::::get(&network_id, &session); + let root_hash = LoomStates::::get(&network_id, &session).root_hash; if root_hash == Default::default() { return Err(InvalidTransaction::BadProof); } diff --git a/pallets/weaver/src/tests.rs b/pallets/weaver/src/tests.rs index 97fd8e4..412f507 100644 --- a/pallets/weaver/src/tests.rs +++ b/pallets/weaver/src/tests.rs @@ -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::::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::::insert(&evm_network_id, evm_session); CurrentWeavingSession::::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::::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::::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::::insert(&network_id, &session, expected_root); + let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root }; + LoomStates::::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::::insert(&network_id, &session, expected_root); + let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root }; + LoomStates::::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::::insert(&network_id, &session, expected_root); + let expected_loom_state = LoomState { at_block: 420, root_hash: expected_root }; + LoomStates::::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::::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::::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::::get(&network_id); + + let target_hash = H256::repeat_byte(0xAA); + LoomStates::::insert(&network_id, ¤t_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::::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::::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::::get(&network_id, current_session); + + assert_eq!(saved_loom.root_hash, target_hash); + assert_eq!(saved_loom.at_block, 100); let updated_session = CurrentWeavingSession::::get(&network_id); assert_eq!(updated_session, current_session); diff --git a/pallets/weaver/src/types.rs b/pallets/weaver/src/types.rs index e0ff0fa..e4be6c6 100644 --- a/pallets/weaver/src/types.rs +++ b/pallets/weaver/src/types.rs @@ -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,