diff --git a/algorithms/beefy/verifier/Cargo.toml b/algorithms/beefy/verifier/Cargo.toml index 947184673..27977fa54 100644 --- a/algorithms/beefy/verifier/Cargo.toml +++ b/algorithms/beefy/verifier/Cargo.toml @@ -43,7 +43,7 @@ beefy-prover = { path = "../prover" } hex = "0.4.3" futures = "0.3.21" sc-consensus-beefy = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } -hyperspace-core = { path = "../../../hyperspace/core", features = ["testing", "build-metadata-from-ws"] } +hyperspace-core = { path = "../../../hyperspace/core", features = ["testing"] } [features] diff --git a/algorithms/grandpa/primitives/src/lib.rs b/algorithms/grandpa/primitives/src/lib.rs index 3f6a462e4..4a413a064 100644 --- a/algorithms/grandpa/primitives/src/lib.rs +++ b/algorithms/grandpa/primitives/src/lib.rs @@ -94,7 +94,7 @@ pub struct ParachainHeadersWithFinalityProof { /// Contains a map of relay chain header hashes to parachain headers /// finalzed at the relay chain height. We check for this parachain header finalization /// via state proofs. Also contains extrinsic proof for timestamp. - pub parachain_headers: BTreeMap, + pub parachain_header: (Hash, ParachainHeaderProofs), /// The latest finalized height on the parachain. pub latest_para_height: u32, } @@ -106,10 +106,6 @@ pub trait HostFunctions: light_client_common::HostFunctions + 'static { /// Verify an ed25519 signature fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool; - /// Stores the given list of RelayChain header hashes in the light client's storage. - fn insert_relay_header_hashes(headers: &[::Hash]); - /// Checks if a RelayChain header hash exists in the light client's storage. - fn contains_relay_header_hash(hash: ::Hash) -> bool; } /// This returns the storage key for a parachain header on the relay chain. diff --git a/algorithms/grandpa/prover/src/host_functions.rs b/algorithms/grandpa/prover/src/host_functions.rs index e1c6e1cd6..87003cad7 100644 --- a/algorithms/grandpa/prover/src/host_functions.rs +++ b/algorithms/grandpa/prover/src/host_functions.rs @@ -35,12 +35,4 @@ impl HostFunctions for HostFunctionsProvider { fn ed25519_verify(sig: &Signature, msg: &[u8], pubkey: &Public) -> bool { pubkey.verify(&msg, sig) } - - fn insert_relay_header_hashes(_headers: &[::Hash]) { - unimplemented!() - } - - fn contains_relay_header_hash(_hash: ::Hash) -> bool { - unimplemented!() - } } diff --git a/algorithms/grandpa/prover/src/lib.rs b/algorithms/grandpa/prover/src/lib.rs index 109bf9d92..4efda89ab 100644 --- a/algorithms/grandpa/prover/src/lib.rs +++ b/algorithms/grandpa/prover/src/lib.rs @@ -339,101 +339,75 @@ where let change_set = self .relay_client .rpc() - .query_storage(keys.clone(), start, Some(latest_finalized_hash)) - .await?; - - let mut change_set_join_set: JoinSet, anyhow::Error>> = JoinSet::new(); - let mut parachain_headers_with_proof = BTreeMap::::default(); - log::debug!(target:"hyperspace", "Got {} authority set changes", change_set.len()); - - fn clone_storage_change_sets( - changes: &[StorageChangeSet], - ) -> Vec> { - changes - .iter() - .map(|change| StorageChangeSet { - block: change.block.clone(), - changes: change.changes.clone(), - }) - .collect() - } + .query_storage(keys.clone(), latest_finalized_hash, Some(latest_finalized_hash)) + .await? + .pop() + .unwrap(); + + log::debug!(target:"hyperspace", "Got {} authority set changes", change_set.changes.len()); + + let change = StorageChangeSet { + block: change_set.block.clone(), + changes: change_set.changes.clone(), + }; let latest_para_height = Arc::new(AtomicU32::new(0u32)); - for changes in change_set.chunks(PROCESS_CHANGES_SET_BATCH_SIZE) { - for change in clone_storage_change_sets::(changes) { - let header_numbers = header_numbers.clone(); - let keys = vec![para_storage_key.clone()]; - let client = self.clone(); - let to = self.rpc_call_delay.as_millis(); - let duration1 = Duration::from_millis(rand::thread_rng().gen_range(1..to) as u64); - let latest_para_height = latest_para_height.clone(); - change_set_join_set.spawn(async move { - sleep(duration1).await; - let header = client - .relay_client - .rpc() - .header(Some(change.block)) - .await? - .ok_or_else(|| anyhow!("block not found {:?}", change.block))?; - - let parachain_header_bytes = { - let key = T::Storage::paras_heads(client.para_id); - let data = client - .relay_client - .storage() - .at(header.hash()) - .fetch(&key) - .await? - .expect("Header exists in its own changeset; qed"); - ::HeadData::from_inner(data) - }; - - let para_header: T::Header = - Decode::decode(&mut parachain_header_bytes.as_ref())?; - let para_block_number = para_header.number(); - // skip genesis header or any unknown headers - if para_block_number == Zero::zero() || - !header_numbers.contains(¶_block_number) - { - return Ok(None) - } - - let state_proof = client - .relay_client - .rpc() - .read_proof(keys.iter().map(AsRef::as_ref), Some(header.hash())) - .await? - .proof - .into_iter() - .map(|p| p.0) - .collect(); - - let TimeStampExtWithProof { ext: extrinsic, proof: extrinsic_proof } = - fetch_timestamp_extrinsic_with_proof( - &client.para_client, - Some(para_header.hash()), - ) - .await - .map_err(|err| anyhow!("Error fetching timestamp with proof: {err:?}"))?; - let proofs = ParachainHeaderProofs { state_proof, extrinsic, extrinsic_proof }; - latest_para_height.fetch_max(u32::from(para_block_number), Ordering::SeqCst); - Ok(Some((H256::from(header.hash()), proofs))) - }); - } - while let Some(res) = change_set_join_set.join_next().await { - if let Some((hash, proofs)) = res?? { - parachain_headers_with_proof.insert(hash, proofs); - } - } + let header_numbers = header_numbers.clone(); + let keys = vec![para_storage_key.clone()]; + let client = self.clone(); + let to = self.rpc_call_delay.as_millis(); + let duration1 = Duration::from_millis(rand::thread_rng().gen_range(1..to) as u64); + let latest_para_height = latest_para_height.clone(); + let header = client + .relay_client + .rpc() + .header(Some(change.block)) + .await? + .ok_or_else(|| anyhow!("block not found {:?}", change.block))?; + + let parachain_header_bytes = { + let key = T::Storage::paras_heads(client.para_id); + let data = client + .relay_client + .storage() + .at(header.hash()) + .fetch(&key) + .await? + .expect("Header exists in its own changeset; qed"); + ::HeadData::from_inner(data) + }; + + let para_header: T::Header = Decode::decode(&mut parachain_header_bytes.as_ref())?; + let para_block_number = para_header.number(); + // skip genesis header or any unknown headers + if para_block_number == Zero::zero() || !header_numbers.contains(¶_block_number) { + return Err(anyhow!("genesis header or unknown header")) } + let state_proof = client + .relay_client + .rpc() + .read_proof(keys.iter().map(AsRef::as_ref), Some(header.hash())) + .await? + .proof + .into_iter() + .map(|p| p.0) + .collect(); + + let TimeStampExtWithProof { ext: extrinsic, proof: extrinsic_proof } = + fetch_timestamp_extrinsic_with_proof(&client.para_client, Some(para_header.hash())) + .await + .map_err(|err| anyhow!("Error fetching timestamp with proof: {err:?}"))?; + let proofs = ParachainHeaderProofs { state_proof, extrinsic, extrinsic_proof }; + latest_para_height.fetch_max(u32::from(para_block_number), Ordering::SeqCst); + unknown_headers.sort_by_key(|header| header.number()); // overwrite unknown headers finality_proof.unknown_headers = unknown_headers; Ok(ParachainHeadersWithFinalityProof { finality_proof, - parachain_headers: parachain_headers_with_proof, + parachain_header: (H256::from(header.hash()), proofs), latest_para_height: latest_para_height.load(Ordering::SeqCst), }) } diff --git a/algorithms/grandpa/verifier/Cargo.toml b/algorithms/grandpa/verifier/Cargo.toml index 0f70ee461..721e4d73b 100644 --- a/algorithms/grandpa/verifier/Cargo.toml +++ b/algorithms/grandpa/verifier/Cargo.toml @@ -40,7 +40,7 @@ grandpa-prover = { path = "../prover" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } jsonrpsee-ws-client = "0.16.2" jsonrpsee-core = "0.16.2" -hyperspace-core = { path = "../../../hyperspace/core", features = ["testing", "build-metadata-from-ws"] } +hyperspace-core = { path = "../../../hyperspace/core", features = ["testing", ] } light-client-common = { path = "../../../light-clients/common", features = ["std"] } [features] diff --git a/algorithms/grandpa/verifier/src/lib.rs b/algorithms/grandpa/verifier/src/lib.rs index 42354cac5..baaeaff9e 100644 --- a/algorithms/grandpa/verifier/src/lib.rs +++ b/algorithms/grandpa/verifier/src/lib.rs @@ -54,7 +54,7 @@ where Host: HostFunctions, Host::BlakeTwo256: Hasher, { - let ParachainHeadersWithFinalityProof { finality_proof, parachain_headers, latest_para_height } = + let ParachainHeadersWithFinalityProof { finality_proof, parachain_header, latest_para_height } = proof; // 1. First validate unknown headers. @@ -102,51 +102,48 @@ where justification.verify::(client_state.current_set_id, &client_state.current_authorities)?; // 3. verify state proofs of parachain headers in finalized relay chain headers. - let mut para_heights = vec![]; - for (hash, proofs) in parachain_headers { - if finalized.binary_search(&hash).is_err() { - // seems relay hash isn't in the finalized chain. - continue - } - let relay_chain_header = - headers.header(&hash).expect("Headers have been checked by AncestryChain; qed"); - - let ParachainHeaderProofs { extrinsic_proof, extrinsic, state_proof } = proofs; - let proof = StorageProof::new(state_proof); - let key = parachain_header_storage_key(client_state.para_id); - // verify patricia-merkle state proofs - let header = state_machine::read_proof_check::( - relay_chain_header.state_root(), - proof, - &[key.as_ref()], - ) - .map_err(|err| anyhow!("error verifying parachain header state proof: {err}"))? - .remove(key.as_ref()) - .flatten() - .ok_or_else(|| anyhow!("Invalid proof, parachain header not found"))?; - let parachain_header = H::decode(&mut &header[..])?; - para_heights.push(parachain_header.number().clone().into()); - // Timestamp extrinsic should be the first inherent and hence the first extrinsic - // https://github.com/paritytech/substrate/blob/d602397a0bbb24b5d627795b797259a44a5e29e9/primitives/trie/src/lib.rs#L99-L101 - let key = codec::Compact(0u64).encode(); - // verify extrinsic proof for timestamp extrinsic - sp_trie::verify_trie_proof::, _, _, _>( - parachain_header.extrinsics_root(), - &extrinsic_proof, - &vec![(key, Some(&extrinsic[..]))], - ) - .map_err(|_| anyhow!("Invalid extrinsic proof"))?; - } + + let (hash, proofs) = parachain_header; + finalized + .binary_search(&hash) + .map_err(|err| anyhow!("error searching for relaychain hash: {err}"))?; + let relay_chain_header = + headers.header(&hash).expect("Headers have been checked by AncestryChain; qed"); + + let ParachainHeaderProofs { extrinsic_proof, extrinsic, state_proof } = proofs; + let proof = StorageProof::new(state_proof); + let key = parachain_header_storage_key(client_state.para_id); + // verify patricia-merkle state proofs + let header = state_machine::read_proof_check::( + relay_chain_header.state_root(), + proof, + &[key.as_ref()], + ) + .map_err(|err| anyhow!("error verifying parachain header state proof: {err}"))? + .remove(key.as_ref()) + .flatten() + .ok_or_else(|| anyhow!("Invalid proof, parachain header not found"))?; + let parachain_header = H::decode(&mut &header[..])?; + // Timestamp extrinsic should be the first inherent and hence the first extrinsic + // https://github.com/paritytech/substrate/blob/d602397a0bbb24b5d627795b797259a44a5e29e9/primitives/trie/src/lib.rs#L99-L101 + let key = codec::Compact(0u64).encode(); + // verify extrinsic proof for timestamp extrinsic + sp_trie::verify_trie_proof::, _, _, _>( + parachain_header.extrinsics_root(), + &extrinsic_proof, + &vec![(key, Some(&extrinsic[..]))], + ) + .map_err(|_| anyhow!("Invalid extrinsic proof"))?; // 4. set new client state, optionally rotating authorities client_state.latest_relay_hash = target.hash(); client_state.latest_relay_height = (*target.number()).into(); - if let Some(max_height) = para_heights.into_iter().max() { - if max_height != latest_para_height { - Err(anyhow!("Latest parachain header height doesn't match the one in the proof"))?; - } - client_state.latest_para_height = max_height; + + if *parachain_header.number() != latest_para_height { + Err(anyhow!("Latest parachain header height doesn't match the one in the proof"))?; } + client_state.latest_para_height = *parachain_header.number(); + if let Some(scheduled_change) = find_scheduled_change::(&target) { client_state.current_set_id += 1; client_state.current_authorities = scheduled_change.next_authorities; diff --git a/contracts/pallet-ibc/src/client.rs b/contracts/pallet-ibc/src/client.rs index 177562c6d..0bcf190c2 100644 --- a/contracts/pallet-ibc/src/client.rs +++ b/contracts/pallet-ibc/src/client.rs @@ -275,6 +275,7 @@ where let cs_state = ics10_grandpa::consensus_state::ConsensusState { timestamp, root: header.state_root().as_ref().to_vec().into(), + relaychain_hashes: vec![], }; let cs = AnyConsensusState::Grandpa(cs_state); diff --git a/contracts/pallet-ibc/src/light_clients.rs b/contracts/pallet-ibc/src/light_clients.rs index 66f97d4ea..ead7da4e9 100644 --- a/contracts/pallet-ibc/src/light_clients.rs +++ b/contracts/pallet-ibc/src/light_clients.rs @@ -48,7 +48,7 @@ use prost::Message; use sp_core::{crypto::ByteArray, ed25519, H256}; use sp_runtime::{ app_crypto::RuntimePublic, - traits::{BlakeTwo256, ConstU32, Header}, + traits::{BlakeTwo256, ConstU32}, BoundedBTreeSet, BoundedVec, }; use tendermint::{ @@ -161,39 +161,6 @@ impl grandpa_client_primitives::HostFunctions for HostFunctionsManager { fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool { pub_key.verify(&msg, sig) } - - fn insert_relay_header_hashes(new_hashes: &[::Hash]) { - if new_hashes.is_empty() { - return - } - - GrandpaHeaderHashesSetStorage::mutate(|hashes_set| { - GrandpaHeaderHashesStorage::mutate(|hashes| { - for hash in new_hashes { - match hashes.try_push(*hash) { - Ok(_) => {}, - Err(_) => { - let old_hash = hashes.remove(0); - hashes_set.remove(&old_hash); - hashes.try_push(*hash).expect( - "we just removed an element, so there is space for this one; qed", - ); - }, - } - match hashes_set.try_insert(*hash) { - Ok(_) => {}, - Err(_) => { - log::warn!("duplicated value in GrandpaHeaderHashesStorage or the storage is corrupted"); - }, - } - } - }); - }); - } - - fn contains_relay_header_hash(hash: ::Hash) -> bool { - GrandpaHeaderHashesSetStorage::get().contains(&hash) - } } impl light_client_common::HostFunctions for HostFunctionsManager { diff --git a/hyperspace/parachain/src/chain.rs b/hyperspace/parachain/src/chain.rs index 3cf422a22..7be1937e7 100644 --- a/hyperspace/parachain/src/chain.rs +++ b/hyperspace/parachain/src/chain.rs @@ -36,7 +36,9 @@ use ibc_proto::google::protobuf::Any; use ics10_grandpa::client_message::{ClientMessage, Misbehaviour, RelayChainHeader}; use itertools::Itertools; use jsonrpsee_ws_client::WsClientBuilder; -use light_client_common::config::{EventRecordT, RuntimeCall, RuntimeTransactions}; +use light_client_common::config::{ + AsInner, EventRecordT, RuntimeCall, RuntimeStorage, RuntimeTransactions, +}; use pallet_ibc::light_clients::AnyClientMessage; use primitives::{ mock::LocalClientTypes, Chain, CommonClientState, IbcProvider, MisbehaviourHandler, @@ -68,7 +70,7 @@ type BeefyJustification = /// An encoded justification proving that the given header has been finalized #[derive(Clone, serde::Serialize, serde::Deserialize)] -struct JustificationNotification(sp_core::Bytes); +pub struct JustificationNotification(sp_core::Bytes); #[async_trait::async_trait] impl Chain @@ -496,9 +498,30 @@ where )); } + let parachain_header_bytes = { + let key = T::Storage::paras_heads(self.para_id); + let data = self + .relay_client + .storage() + .at(common_ancestor_header.hash()) + .fetch(&key) + .await? + .expect("Header exists in its own changeset; qed"); + ::HeadData::from_inner(data) + }; + + let para_header = sp_runtime::generic::Header::< + u32, + sp_runtime::traits::BlakeTwo256, + >::decode(&mut parachain_header_bytes.as_ref())?; + let para_block_number = para_header.number; + let misbehaviour = ClientMessage::Misbehaviour(Misbehaviour { first_finality_proof: header.finality_proof, second_finality_proof: trusted_finality_proof, + // consensus state height which gives us our relaychain base header against + // which we validate misbehaviour + para_height: para_block_number.into(), }); counterparty diff --git a/hyperspace/parachain/src/finality_protocol.rs b/hyperspace/parachain/src/finality_protocol.rs index f8e7dc5c6..517260caf 100644 --- a/hyperspace/parachain/src/finality_protocol.rs +++ b/hyperspace/parachain/src/finality_protocol.rs @@ -644,7 +644,7 @@ where headers_with_events.insert(finalized_para_header.number()); } - let ParachainHeadersWithFinalityProof { finality_proof, parachain_headers, .. } = prover + let ParachainHeadersWithFinalityProof { finality_proof, parachain_header, .. } = prover .query_finalized_parachain_headers_with_proof::( client_state.latest_relay_height, justification.commit.target_number, @@ -677,7 +677,7 @@ where let grandpa_header = GrandpaHeader { finality_proof: codec::Decode::decode(&mut &*finality_proof.encode()) .expect("Same struct from different crates,decode should not fail"), - parachain_headers: parachain_headers.into(), + parachain_header: parachain_header.into(), height: Height::new(source.para_id as u64, finalized_para_height as u64), }; let height = grandpa_header.height(); diff --git a/hyperspace/parachain/src/lib.rs b/hyperspace/parachain/src/lib.rs index efeb6d137..16dd515d1 100644 --- a/hyperspace/parachain/src/lib.rs +++ b/hyperspace/parachain/src/lib.rs @@ -14,6 +14,7 @@ #![allow(clippy::all)] +use anyhow::anyhow; use std::{ collections::{BTreeMap, HashSet}, path::PathBuf, @@ -45,14 +46,15 @@ use crate::{ use beefy_light_client_primitives::{ClientState, MmrUpdateProof}; use beefy_prover::Prover; use codec::Decode; -use grandpa_light_client_primitives::ParachainHeaderProofs; +use finality_grandpa_rpc::GrandpaApiClient; +use grandpa_light_client_primitives::{FinalityProof, ParachainHeaderProofs}; use grandpa_prover::GrandpaProver; use ibc::{ core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}, timestamp::Timestamp, }; use ics10_grandpa::{ - client_state::ClientState as GrandpaClientState, + client_message::RelayChainHeader, client_state::ClientState as GrandpaClientState, consensus_state::ConsensusState as GrandpaConsensusState, }; use ics11_beefy::{ @@ -644,12 +646,34 @@ where .expect("Timestamp should exist"); let timestamp_nanos = Duration::from_millis(unix_timestamp_millis).as_nanos() as u64; + let encoded = + GrandpaApiClient::::prove_finality( + &*self.relay_ws_client, + light_client_state.latest_relay_height, + ) + .await? + .ok_or_else(|| { + Error::Custom(format!( + "No justification found for block: {:?}", + light_client_state.latest_relay_height, + )) + })? + .0; + + let mut trusted_finality_proof = + FinalityProof::::decode(&mut &encoded[..])?; + let consensus_state = AnyConsensusState::Grandpa(GrandpaConsensusState { timestamp: Timestamp::from_nanoseconds(timestamp_nanos) .unwrap() .into_tm_time() .unwrap(), root: decoded_para_head.state_root.as_bytes().to_vec().into(), + relaychain_hashes: trusted_finality_proof + .unknown_headers + .into_iter() + .map(|hash| hash.hash()) + .collect::>(), }); return Ok((AnyClientState::Grandpa(client_state), consensus_state)) diff --git a/hyperspace/parachain/src/light_client_sync.rs b/hyperspace/parachain/src/light_client_sync.rs index e4e614c11..1f74bcb23 100644 --- a/hyperspace/parachain/src/light_client_sync.rs +++ b/hyperspace/parachain/src/light_client_sync.rs @@ -330,7 +330,7 @@ where ) }) .collect(); - let ParachainHeadersWithFinalityProof { finality_proof, parachain_headers, .. } = prover + let ParachainHeadersWithFinalityProof { finality_proof, parachain_header, .. } = prover .query_finalized_parachain_headers_with_proof::( previous_finalized_height, latest_finalized_height, @@ -342,7 +342,7 @@ where let grandpa_header = GrandpaHeader { finality_proof: codec::Decode::decode(&mut &*finality_proof.encode()) .expect("Same struct from different crates,decode should not fail"), - parachain_headers: parachain_headers.into(), + parachain_header: parachain_header.into(), height: Height::new(para_id as u64, finalized_para_height as u64), }; diff --git a/hyperspace/testsuite/Cargo.toml b/hyperspace/testsuite/Cargo.toml index a1ae42df0..3ade5e66a 100644 --- a/hyperspace/testsuite/Cargo.toml +++ b/hyperspace/testsuite/Cargo.toml @@ -45,10 +45,20 @@ grandpa-light-client = { path = "../../algorithms/grandpa/verifier", package = " hex = "0.4.3" rand = "0.8.5" toml = "0.7.4" +#tonic = { version = "0.8", features = ["tls", "tls-roots"] } +#prost = "0.11.6" +#sp-rpc = { git = "https://github.com/paritytech//substrate.git", branch = "polkadot-v0.9.43" } +#tendermint = { git = "https://github.com/informalsystems/tendermint-rs", rev = "e81f7bf23d63ffbcd242381d1ce5e35da3515ff1", default-features = false, features = [ +# "secp256k1", +#] } +#tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", rev = "e81f7bf23d63ffbcd242381d1ce5e35da3515ff1", default-features = false, features = [ +# "http-client", +# "websocket-client", +#] } [dev-dependencies] subxt = { git = "https://github.com/paritytech/subxt", tag = "v0.29.0", features = ["substrate-compat"] } -hyperspace-core = { path = "../core", features = ["testing", "build-metadata-from-ws"] } +hyperspace-core = { path = "../core", features = ["testing"] } hyperspace-parachain = { path = "../parachain", features = ["testing"] } hyperspace-cosmos = { path = "../cosmos", features = [] } diff --git a/light-clients/ics10-grandpa/Cargo.toml b/light-clients/ics10-grandpa/Cargo.toml index b398c07bd..6a9c51b3b 100644 --- a/light-clients/ics10-grandpa/Cargo.toml +++ b/light-clients/ics10-grandpa/Cargo.toml @@ -82,5 +82,5 @@ json = { package = "serde_json", version = "1.0.85" } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } finality-grandpa-rpc = { package = "sc-consensus-grandpa-rpc", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } jsonrpsee-ws-client = "0.16.2" -hyperspace-core = { path = "../../hyperspace/core", features = ["testing", "build-metadata-from-ws"] } +hyperspace-core = { path = "../../hyperspace/core", features = ["testing"] } light-client-common = { path = "../common", features = ["std"] } diff --git a/light-clients/ics10-grandpa/src/client_def.rs b/light-clients/ics10-grandpa/src/client_def.rs index f72ae3d7b..9fdf0289a 100644 --- a/light-clients/ics10-grandpa/src/client_def.rs +++ b/light-clients/ics10-grandpa/src/client_def.rs @@ -33,6 +33,7 @@ use ibc::{ core::{ ics02_client::{ client_def::{ClientDef, ConsensusUpdateResult}, + context::ClientTypes, error::Error as Ics02Error, }, ics03_connection::connection::ConnectionEnd, @@ -77,8 +78,8 @@ where fn verify_client_message( &self, - _ctx: &Ctx, - _client_id: ClientId, + ctx: &Ctx, + client_id: ClientId, client_state: Self::ClientState, client_message: Self::ClientMessage, ) -> Result<(), Ics02Error> { @@ -93,7 +94,7 @@ where } let headers_with_finality_proof = ParachainHeadersWithFinalityProof { finality_proof: header.finality_proof, - parachain_headers: header.parachain_headers, + parachain_header: header.parachain_header, latest_para_height: header.height.revision_height as u32, }; @@ -163,7 +164,17 @@ where } // TODO: should we handle genesis block here somehow? - if !H::contains_relay_header_hash(first_parent) { + + let cs = + ctx.consensus_state(&client_id, Height::new(client_state.para_id as u64, misbehavior.para_height)) + .map_err(|_| { + Error::Custom( + "Could not find the known header for first finality proof".to_string(), + ) + })? + .downcast::() + .ok_or_else(|| Error::Custom(format!("Wrong consensus state type stored for Grandpa client with {client_id} at {}", misbehavior.para_height)))?; + if !cs.relaychain_hashes.contains(&first_parent) { Err(Error::Custom( "Could not find the known header for first finality proof".to_string(), ))? @@ -220,43 +231,41 @@ where }; let ancestry = AncestryChain::::new(&header.finality_proof.unknown_headers); - let mut consensus_states = vec![]; let from = client_state.latest_relay_hash; - let finalized = ancestry + let relay_finalized = ancestry .ancestry(from, header.finality_proof.block) .map_err(|_| Error::Custom(format!("[update_state] Invalid ancestry!")))?; - let mut finalized_sorted = finalized.clone(); - finalized_sorted.sort(); - - for (relay_hash, parachain_header_proof) in header.parachain_headers { - // we really shouldn't set consensus states for parachain headers not in the finalized - // chain. - if finalized_sorted.binary_search(&relay_hash).is_err() { - continue - } - - let header = ancestry.header(&relay_hash).ok_or_else(|| { - Error::Custom(format!("No relay chain header found for hash: {relay_hash:?}")) - })?; + let mut relay_finalized_sorted = relay_finalized.clone(); + relay_finalized_sorted.sort(); + + let (relay_hash, parachain_header_proof) = header.parachain_header; + // we really shouldn't set consensus states for parachain headers not in the finalized + // chain. + if relay_finalized_sorted.clone().binary_search(&relay_hash).is_err() { + Err(Ics02Error::implementation_specific(format!("Finalized realyer haeder not found")))? + } - let (height, consensus_state) = ConsensusState::from_header::( - parachain_header_proof, - client_state.para_id, - header.state_root.clone(), - )?; + let relay_header = ancestry.header(&relay_hash).ok_or_else(|| { + Error::Custom(format!("No relay chain header found for hash: {relay_hash:?}")) + })?; - // Skip duplicate consensus states - if ctx.consensus_state(&client_id, height).is_ok() { - continue - } + let (parachain_height, mut consensus_state) = ConsensusState::from_header::( + parachain_header_proof, + client_state.para_id, + relay_header.state_root.clone(), + relay_finalized_sorted.clone(), + )?; - let wrapped = Ctx::AnyConsensusState::wrap(&consensus_state) - .expect("AnyConsenusState is type checked; qed"); - consensus_states.push((height, wrapped)); + // Skip duplicate consensus states + if ctx.consensus_state(&client_id, parachain_height).is_ok() { + return Ok((client_state, ConsensusUpdateResult::Batch(vec![]))) } + let wrapped = Ctx::AnyConsensusState::wrap(&consensus_state) + .expect("AnyConsenusState is type checked; qed"); + // updates let target = ancestry .header(&header.finality_proof.block) @@ -269,25 +278,12 @@ where )))? } - let mut heights = consensus_states - .iter() - .map(|(h, ..)| { - // this cast is safe, see [`ConsensusState::from_header`] - h.revision_height as u32 - }) - .collect::>(); - - heights.sort(); - - if let Some((min_height, max_height)) = heights.first().zip(heights.last()) { - // can't try to rewind parachain. - if *min_height <= client_state.latest_para_height { - Err(Ics02Error::implementation_specific(format!( - "Light client can only be updated to new parachain height." - )))? - } - client_state.latest_para_height = *max_height + if parachain_height.revision_height as u32 <= client_state.latest_para_height { + Err(Ics02Error::implementation_specific(format!( + "Light client can only be updated to new parachain height." + )))? } + client_state.latest_para_height = parachain_height.revision_height as u32; client_state.latest_relay_hash = header.finality_proof.block; client_state.latest_relay_height = target.number; @@ -297,9 +293,13 @@ where client_state.current_authorities = scheduled_change.next_authorities; } - H::insert_relay_header_hashes(&finalized); - - Ok((client_state, ConsensusUpdateResult::Batch(consensus_states))) + Ok(( + client_state, + ConsensusUpdateResult::Single( + Ctx::AnyConsensusState::wrap(&consensus_state) + .expect("AnyConsenusState is type checked; qed"), + ), + )) } fn update_state_on_misbehaviour( @@ -325,45 +325,50 @@ where // we also check that this update doesn't include competing consensus states for heights we // already processed. - let header = match client_message { + let update = match client_message { ClientMessage::Header(header) => header, _ => unreachable!("We've checked for misbehavior in line 180; qed"), }; //forced authority set change is handled as a misbehaviour let ancestry = - AncestryChain::::new(&header.finality_proof.unknown_headers); + AncestryChain::::new(&update.finality_proof.unknown_headers); - for (relay_hash, parachain_header_proof) in header.parachain_headers { - let header = ancestry.header(&relay_hash).ok_or_else(|| { - Error::Custom(format!("No relay chain header found for hash: {relay_hash:?}")) - })?; + let (relay_hash, parachain_header_proof) = update.parachain_header; + let header = ancestry.header(&relay_hash).ok_or_else(|| { + Error::Custom(format!("No relay chain header found for hash: {relay_hash:?}")) + })?; - if find_forced_change(header).is_some() { - return Ok(true) - } - - let (height, consensus_state) = ConsensusState::from_header::( - parachain_header_proof, - client_state.para_id, - header.state_root.clone(), - )?; - - match ctx.maybe_consensus_state(&client_id, height)? { - Some(cs) => { - let cs: ConsensusState = cs - .downcast() - .ok_or(Ics02Error::client_args_type_mismatch(client_state.client_type()))?; - - if cs != consensus_state { - // Houston we have a problem - return Ok(true) - } - }, - None => {}, - }; + if find_forced_change(header).is_some() { + return Ok(true) } - + let from = client_state.latest_relay_hash; + let relay_finalized = ancestry + .ancestry(from, update.finality_proof.block) + .map_err(|_| Error::Custom(format!("[update_state] Invalid ancestry!")))?; + let mut relay_finalized_sorted = relay_finalized.clone(); + relay_finalized_sorted.sort(); + + let (height, consensus_state) = ConsensusState::from_header::( + parachain_header_proof, + client_state.para_id, + header.state_root.clone(), + relay_finalized_sorted, + )?; + + match ctx.maybe_consensus_state(&client_id, height)? { + Some(cs) => { + let cs: ConsensusState = cs + .downcast() + .ok_or(Ics02Error::client_args_type_mismatch(client_state.client_type()))?; + + if cs != consensus_state { + // Houston we have a problem + return Ok(true) + } + }, + None => {}, + }; Ok(false) } diff --git a/light-clients/ics10-grandpa/src/client_message.rs b/light-clients/ics10-grandpa/src/client_message.rs index 960a94722..43232bacc 100644 --- a/light-clients/ics10-grandpa/src/client_message.rs +++ b/light-clients/ics10-grandpa/src/client_message.rs @@ -46,7 +46,7 @@ pub struct Header { /// Contains a map of relay chain header hashes to parachain headers /// finalzed at the relay chain height. We check for this parachain header finalization /// via state proofs. Also contains extrinsic proof for timestamp. - pub parachain_headers: BTreeMap, + pub parachain_header: (H256, ParachainHeaderProofs), /// Lazily initialized height pub height: Height, } @@ -66,6 +66,7 @@ pub struct Misbehaviour { pub first_finality_proof: FinalityProof, /// second proof of misbehaviour pub second_finality_proof: FinalityProof, + pub para_height: u64, } /// [`ClientMessage`] for Ics10-GRANDPA @@ -98,24 +99,20 @@ impl TryFrom for Header { Err(anyhow!("Invalid hash type with length: {}", finality_proof.block.len()))? }; - let parachain_headers = raw_header - .parachain_headers - .into_iter() - .map(|header| { - let block = if header.relay_hash.len() == 32 { - H256::from_slice(&*header.relay_hash) - } else { - Err(anyhow!("Invalid hash type with length: {}", header.relay_hash.len()))? - }; - let proto::ParachainHeaderProofs { state_proof, extrinsic_proof, extrinsic } = - header - .parachain_header - .ok_or_else(|| anyhow!("Parachain header is required!"))?; - let parachain_header_proofs = - ParachainHeaderProofs { state_proof, extrinsic, extrinsic_proof }; - Ok((block, parachain_header_proofs)) - }) - .collect::>()?; + let ph = raw_header.parachain_header.ok_or_else(|| { + anyhow!("Invalid hash type with length: {}", finality_proof.block.len()) + })?; + let block = if ph.relay_hash.len() == 32 { + H256::from_slice(&ph.relay_hash) + } else { + Err(anyhow!("Invalid hash type with length: {}", ph.relay_hash.len()))? + }; + let proto::ParachainHeaderProofs { state_proof, extrinsic_proof, extrinsic } = + ph.parachain_header.ok_or_else(|| anyhow!("Parachain header is required!"))?; + let parachain_header_proofs = + ParachainHeaderProofs { state_proof, extrinsic, extrinsic_proof }; + + let parachain_header = (block, parachain_header_proofs); let unknown_headers = finality_proof .unknown_headers @@ -132,7 +129,7 @@ impl TryFrom for Header { justification: finality_proof.justification, unknown_headers, }, - parachain_headers, + parachain_header, height: Height::new(raw_header.para_id as u64, raw_header.para_height as u64), }) } @@ -140,18 +137,15 @@ impl TryFrom for Header { impl From
for RawHeader { fn from(header: Header) -> Self { - let parachain_headers = header - .parachain_headers - .into_iter() - .map(|(hash, parachain_header_proofs)| proto::ParachainHeaderWithRelayHash { - relay_hash: hash.as_bytes().to_vec(), - parachain_header: Some(proto::ParachainHeaderProofs { - state_proof: parachain_header_proofs.state_proof, - extrinsic: parachain_header_proofs.extrinsic, - extrinsic_proof: parachain_header_proofs.extrinsic_proof, - }), - }) - .collect(); + let (hash, parachain_header_proofs) = header.parachain_header; + let parachain_header = Some(proto::ParachainHeaderWithRelayHash { + relay_hash: hash.as_bytes().to_vec(), + parachain_header: Some(proto::ParachainHeaderProofs { + state_proof: parachain_header_proofs.state_proof, + extrinsic: parachain_header_proofs.extrinsic, + extrinsic_proof: parachain_header_proofs.extrinsic_proof, + }), + }); let finality_proof = proto::FinalityProof { block: header.finality_proof.block.as_bytes().to_vec(), justification: header.finality_proof.justification, @@ -165,7 +159,7 @@ impl From
for RawHeader { RawHeader { finality_proof: Some(finality_proof), - parachain_headers, + parachain_header, para_id: header.height.revision_number as u32, para_height: header.height.revision_height as u32, } @@ -181,6 +175,7 @@ impl TryFrom for Misbehaviour { Ok(Misbehaviour { first_finality_proof: Decode::decode(&mut &*value.first_finality_proof)?, second_finality_proof: Decode::decode(&mut &*value.second_finality_proof)?, + para_height: value.para_height, }) } } @@ -190,6 +185,7 @@ impl From for RawMisbehaviour { RawMisbehaviour { first_finality_proof: value.first_finality_proof.encode(), second_finality_proof: value.second_finality_proof.encode(), + para_height: value.para_height, } } } diff --git a/light-clients/ics10-grandpa/src/consensus_state.rs b/light-clients/ics10-grandpa/src/consensus_state.rs index 6c16785ee..51b0fd055 100644 --- a/light-clients/ics10-grandpa/src/consensus_state.rs +++ b/light-clients/ics10-grandpa/src/consensus_state.rs @@ -39,11 +39,12 @@ pub const GRANDPA_CONSENSUS_STATE_TYPE_URL: &str = "/ibc.lightclients.grandpa.v1 pub struct ConsensusState { pub timestamp: Time, pub root: CommitmentRoot, + pub relaychain_hashes: Vec, } impl ConsensusState { - pub fn new(root: Vec, timestamp: Time) -> Self { - Self { timestamp, root: root.into() } + pub fn new(root: Vec, timestamp: Time, relaychain_hashes: Vec) -> Self { + Self { timestamp, root: root.into(), relaychain_hashes } } pub fn to_any(&self) -> Any { @@ -57,6 +58,7 @@ impl ConsensusState { parachain_header_proof: ParachainHeaderProofs, para_id: u32, relay_state_root: H256, + relaychain_hashes: Vec, ) -> Result<(Height, Self), Error> where H: grandpa_client_primitives::HostFunctions, @@ -85,7 +87,7 @@ impl ConsensusState { Ok(( Height::new(para_id as u64, parachain_header.number as u64), - Self { root: root.into(), timestamp }, + Self { root: root.into(), timestamp, relaychain_hashes }, )) } } @@ -120,7 +122,12 @@ impl TryFrom for ConsensusState { Error::Custom(format!("Invalid consensus state: invalid timestamp {e}")) })?; - Ok(Self { root: raw.root.into(), timestamp }) + let relaychain_hashes = raw + .relaychain_hashes + .into_iter() + .map(|hash| H256::from_slice(&hash)) + .collect::>(); + Ok(Self { root: raw.root.into(), timestamp, relaychain_hashes }) } } @@ -129,7 +136,17 @@ impl From for RawConsensusState { let tpb::Timestamp { seconds, nanos } = value.timestamp.into(); let timestamp = prost_types::Timestamp { seconds, nanos }; - RawConsensusState { timestamp: Some(timestamp), root: value.root.into_vec() } + let relaychain_hashes = value + .relaychain_hashes + .into_iter() + .map(|hash| hash.as_bytes().to_vec()) + .collect::>>(); + + RawConsensusState { + timestamp: Some(timestamp), + root: value.root.into_vec(), + relaychain_hashes, + } } } @@ -142,6 +159,7 @@ pub mod test_util { AnyConsensusState::Grandpa(ConsensusState { timestamp: Time::now(), root: vec![0; 32].into(), + relaychain_hashes: vec![], }) } } diff --git a/light-clients/ics10-grandpa/src/mock.rs b/light-clients/ics10-grandpa/src/mock.rs index d376b7a50..cacac6f21 100644 --- a/light-clients/ics10-grandpa/src/mock.rs +++ b/light-clients/ics10-grandpa/src/mock.rs @@ -65,19 +65,6 @@ impl grandpa_client_primitives::HostFunctions for HostFunctionsManager { fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool { pub_key.verify(&msg, sig) } - - fn insert_relay_header_hashes(headers: &[::Hash]) { - HEADER_HASHES.with(|set| { - let mut set_mut = set.borrow_mut(); - for hash in headers { - set_mut.insert(hash.clone()); - } - }) - } - - fn contains_relay_header_hash(hash: ::Hash) -> bool { - HEADER_HASHES.with(|set| set.borrow().contains(&hash)) - } } impl light_client_common::HostFunctions for HostFunctionsManager { diff --git a/light-clients/ics10-grandpa/src/proto/grandpa.proto b/light-clients/ics10-grandpa/src/proto/grandpa.proto index 3c4cf7a9c..09765f701 100644 --- a/light-clients/ics10-grandpa/src/proto/grandpa.proto +++ b/light-clients/ics10-grandpa/src/proto/grandpa.proto @@ -91,6 +91,7 @@ message ConsensusState { google.protobuf.Timestamp timestamp = 1; // packet commitment root bytes root = 2; + repeated bytes relaychain_hashes = 3; } // GRANDPA finality proof and parachain headers @@ -98,7 +99,7 @@ message Header { // GRANDPA finality proof FinalityProof finality_proof = 1; // new parachain headers finalized by the GRANDPA finality proof - repeated ParachainHeaderWithRelayHash parachain_headers = 2; + ParachainHeaderWithRelayHash parachain_header = 2; uint32 para_id = 3; uint32 para_height = 4; } @@ -109,6 +110,7 @@ message Misbehaviour { bytes first_finality_proof = 1; // Second SCALE-encoded finality proof. bytes second_finality_proof = 2; + uint64 para_height = 4; } // ClientMessage for ics10-GRANDPA diff --git a/light-clients/ics10-grandpa/src/tests.rs b/light-clients/ics10-grandpa/src/tests.rs index dd5ac188f..a1f2f4b45 100644 --- a/light-clients/ics10-grandpa/src/tests.rs +++ b/light-clients/ics10-grandpa/src/tests.rs @@ -177,6 +177,7 @@ async fn test_continuous_update_of_grandpa_client() { header_proof, prover.para_id, latest_relay_header.state_root, + vec![], ) .unwrap(); @@ -254,7 +255,7 @@ async fn test_continuous_update_of_grandpa_client() { let header = Header { finality_proof: proof.finality_proof, - parachain_headers: proof.parachain_headers.clone(), + parachain_header: proof.parachain_header.clone(), height: Height::new(prover.para_id as u64, finalized_para_header.number as u64), }; let msg = MsgUpdateAnyClient { diff --git a/light-clients/ics11-beefy/Cargo.toml b/light-clients/ics11-beefy/Cargo.toml index 5304c93cc..cd9e9456b 100644 --- a/light-clients/ics11-beefy/Cargo.toml +++ b/light-clients/ics11-beefy/Cargo.toml @@ -81,4 +81,4 @@ sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot- sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } json = { package = "serde_json", version = "1.0.85" } frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" } -hyperspace-core = { path = "../../hyperspace/core", features = ["testing", "build-metadata-from-ws"] } +hyperspace-core = { path = "../../hyperspace/core", features = ["testing"] }