Skip to content

Commit 46f4236

Browse files
committed
oak-audit: issue 2
1 parent d775eb6 commit 46f4236

File tree

24 files changed

+337
-378
lines changed

24 files changed

+337
-378
lines changed

algorithms/beefy/verifier/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ beefy-prover = { path = "../prover" }
4343
hex = "0.4.3"
4444
futures = "0.3.21"
4545
sc-consensus-beefy = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" }
46-
hyperspace-core = { path = "../../../hyperspace/core", features = ["testing", "build-metadata-from-ws"] }
46+
hyperspace-core = { path = "../../../hyperspace/core", features = ["testing"] }
4747

4848

4949
[features]

algorithms/grandpa/primitives/src/lib.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub struct ParachainHeadersWithFinalityProof<H: codec::Codec> {
9494
/// Contains a map of relay chain header hashes to parachain headers
9595
/// finalzed at the relay chain height. We check for this parachain header finalization
9696
/// via state proofs. Also contains extrinsic proof for timestamp.
97-
pub parachain_headers: BTreeMap<Hash, ParachainHeaderProofs>,
97+
pub parachain_header: (Hash, ParachainHeaderProofs),
9898
/// The latest finalized height on the parachain.
9999
pub latest_para_height: u32,
100100
}
@@ -106,10 +106,6 @@ pub trait HostFunctions: light_client_common::HostFunctions + 'static {
106106

107107
/// Verify an ed25519 signature
108108
fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool;
109-
/// Stores the given list of RelayChain header hashes in the light client's storage.
110-
fn insert_relay_header_hashes(headers: &[<Self::Header as Header>::Hash]);
111-
/// Checks if a RelayChain header hash exists in the light client's storage.
112-
fn contains_relay_header_hash(hash: <Self::Header as Header>::Hash) -> bool;
113109
}
114110

115111
/// This returns the storage key for a parachain header on the relay chain.

algorithms/grandpa/prover/src/host_functions.rs

-8
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,4 @@ impl HostFunctions for HostFunctionsProvider {
3535
fn ed25519_verify(sig: &Signature, msg: &[u8], pubkey: &Public) -> bool {
3636
pubkey.verify(&msg, sig)
3737
}
38-
39-
fn insert_relay_header_hashes(_headers: &[<Self::Header as Header>::Hash]) {
40-
unimplemented!()
41-
}
42-
43-
fn contains_relay_header_hash(_hash: <Self::Header as Header>::Hash) -> bool {
44-
unimplemented!()
45-
}
4638
}

algorithms/grandpa/prover/src/lib.rs

+59-85
Original file line numberDiff line numberDiff line change
@@ -339,101 +339,75 @@ where
339339
let change_set = self
340340
.relay_client
341341
.rpc()
342-
.query_storage(keys.clone(), start, Some(latest_finalized_hash))
343-
.await?;
344-
345-
let mut change_set_join_set: JoinSet<Result<Option<_>, anyhow::Error>> = JoinSet::new();
346-
let mut parachain_headers_with_proof = BTreeMap::<H256, ParachainHeaderProofs>::default();
347-
log::debug!(target:"hyperspace", "Got {} authority set changes", change_set.len());
348-
349-
fn clone_storage_change_sets<T: light_client_common::config::Config + Send + Sync>(
350-
changes: &[StorageChangeSet<T::Hash>],
351-
) -> Vec<StorageChangeSet<T::Hash>> {
352-
changes
353-
.iter()
354-
.map(|change| StorageChangeSet {
355-
block: change.block.clone(),
356-
changes: change.changes.clone(),
357-
})
358-
.collect()
359-
}
342+
.query_storage(keys.clone(), latest_finalized_hash, Some(latest_finalized_hash))
343+
.await?
344+
.pop()
345+
.unwrap();
346+
347+
log::debug!(target:"hyperspace", "Got {} authority set changes", change_set.changes.len());
348+
349+
let change = StorageChangeSet {
350+
block: change_set.block.clone(),
351+
changes: change_set.changes.clone(),
352+
};
360353
let latest_para_height = Arc::new(AtomicU32::new(0u32));
361-
for changes in change_set.chunks(PROCESS_CHANGES_SET_BATCH_SIZE) {
362-
for change in clone_storage_change_sets::<T>(changes) {
363-
let header_numbers = header_numbers.clone();
364-
let keys = vec![para_storage_key.clone()];
365-
let client = self.clone();
366-
let to = self.rpc_call_delay.as_millis();
367-
let duration1 = Duration::from_millis(rand::thread_rng().gen_range(1..to) as u64);
368-
let latest_para_height = latest_para_height.clone();
369-
change_set_join_set.spawn(async move {
370-
sleep(duration1).await;
371-
let header = client
372-
.relay_client
373-
.rpc()
374-
.header(Some(change.block))
375-
.await?
376-
.ok_or_else(|| anyhow!("block not found {:?}", change.block))?;
377-
378-
let parachain_header_bytes = {
379-
let key = T::Storage::paras_heads(client.para_id);
380-
let data = client
381-
.relay_client
382-
.storage()
383-
.at(header.hash())
384-
.fetch(&key)
385-
.await?
386-
.expect("Header exists in its own changeset; qed");
387-
<T::Storage as RuntimeStorage>::HeadData::from_inner(data)
388-
};
389-
390-
let para_header: T::Header =
391-
Decode::decode(&mut parachain_header_bytes.as_ref())?;
392-
let para_block_number = para_header.number();
393-
// skip genesis header or any unknown headers
394-
if para_block_number == Zero::zero() ||
395-
!header_numbers.contains(&para_block_number)
396-
{
397-
return Ok(None)
398-
}
399-
400-
let state_proof = client
401-
.relay_client
402-
.rpc()
403-
.read_proof(keys.iter().map(AsRef::as_ref), Some(header.hash()))
404-
.await?
405-
.proof
406-
.into_iter()
407-
.map(|p| p.0)
408-
.collect();
409-
410-
let TimeStampExtWithProof { ext: extrinsic, proof: extrinsic_proof } =
411-
fetch_timestamp_extrinsic_with_proof(
412-
&client.para_client,
413-
Some(para_header.hash()),
414-
)
415-
.await
416-
.map_err(|err| anyhow!("Error fetching timestamp with proof: {err:?}"))?;
417-
let proofs = ParachainHeaderProofs { state_proof, extrinsic, extrinsic_proof };
418-
latest_para_height.fetch_max(u32::from(para_block_number), Ordering::SeqCst);
419-
Ok(Some((H256::from(header.hash()), proofs)))
420-
});
421-
}
422354

423-
while let Some(res) = change_set_join_set.join_next().await {
424-
if let Some((hash, proofs)) = res?? {
425-
parachain_headers_with_proof.insert(hash, proofs);
426-
}
427-
}
355+
let header_numbers = header_numbers.clone();
356+
let keys = vec![para_storage_key.clone()];
357+
let client = self.clone();
358+
let to = self.rpc_call_delay.as_millis();
359+
let duration1 = Duration::from_millis(rand::thread_rng().gen_range(1..to) as u64);
360+
let latest_para_height = latest_para_height.clone();
361+
let header = client
362+
.relay_client
363+
.rpc()
364+
.header(Some(change.block))
365+
.await?
366+
.ok_or_else(|| anyhow!("block not found {:?}", change.block))?;
367+
368+
let parachain_header_bytes = {
369+
let key = T::Storage::paras_heads(client.para_id);
370+
let data = client
371+
.relay_client
372+
.storage()
373+
.at(header.hash())
374+
.fetch(&key)
375+
.await?
376+
.expect("Header exists in its own changeset; qed");
377+
<T::Storage as RuntimeStorage>::HeadData::from_inner(data)
378+
};
379+
380+
let para_header: T::Header = Decode::decode(&mut parachain_header_bytes.as_ref())?;
381+
let para_block_number = para_header.number();
382+
// skip genesis header or any unknown headers
383+
if para_block_number == Zero::zero() || !header_numbers.contains(&para_block_number) {
384+
return Err(anyhow!("genesis header or unknown header"))
428385
}
429386

387+
let state_proof = client
388+
.relay_client
389+
.rpc()
390+
.read_proof(keys.iter().map(AsRef::as_ref), Some(header.hash()))
391+
.await?
392+
.proof
393+
.into_iter()
394+
.map(|p| p.0)
395+
.collect();
396+
397+
let TimeStampExtWithProof { ext: extrinsic, proof: extrinsic_proof } =
398+
fetch_timestamp_extrinsic_with_proof(&client.para_client, Some(para_header.hash()))
399+
.await
400+
.map_err(|err| anyhow!("Error fetching timestamp with proof: {err:?}"))?;
401+
let proofs = ParachainHeaderProofs { state_proof, extrinsic, extrinsic_proof };
402+
latest_para_height.fetch_max(u32::from(para_block_number), Ordering::SeqCst);
403+
430404
unknown_headers.sort_by_key(|header| header.number());
431405
// overwrite unknown headers
432406
finality_proof.unknown_headers = unknown_headers;
433407

434408
Ok(ParachainHeadersWithFinalityProof {
435409
finality_proof,
436-
parachain_headers: parachain_headers_with_proof,
410+
parachain_header: (H256::from(header.hash()), proofs),
437411
latest_para_height: latest_para_height.load(Ordering::SeqCst),
438412
})
439413
}

algorithms/grandpa/verifier/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ grandpa-prover = { path = "../prover" }
4040
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.43" }
4141
jsonrpsee-ws-client = "0.16.2"
4242
jsonrpsee-core = "0.16.2"
43-
hyperspace-core = { path = "../../../hyperspace/core", features = ["testing", "build-metadata-from-ws"] }
43+
hyperspace-core = { path = "../../../hyperspace/core", features = ["testing", ] }
4444
light-client-common = { path = "../../../light-clients/common", features = ["std"] }
4545

4646
[features]

algorithms/grandpa/verifier/src/lib.rs

+38-41
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ where
5454
Host: HostFunctions,
5555
Host::BlakeTwo256: Hasher<Out = H256>,
5656
{
57-
let ParachainHeadersWithFinalityProof { finality_proof, parachain_headers, latest_para_height } =
57+
let ParachainHeadersWithFinalityProof { finality_proof, parachain_header, latest_para_height } =
5858
proof;
5959

6060
// 1. First validate unknown headers.
@@ -102,51 +102,48 @@ where
102102
justification.verify::<Host>(client_state.current_set_id, &client_state.current_authorities)?;
103103

104104
// 3. verify state proofs of parachain headers in finalized relay chain headers.
105-
let mut para_heights = vec![];
106-
for (hash, proofs) in parachain_headers {
107-
if finalized.binary_search(&hash).is_err() {
108-
// seems relay hash isn't in the finalized chain.
109-
continue
110-
}
111-
let relay_chain_header =
112-
headers.header(&hash).expect("Headers have been checked by AncestryChain; qed");
113-
114-
let ParachainHeaderProofs { extrinsic_proof, extrinsic, state_proof } = proofs;
115-
let proof = StorageProof::new(state_proof);
116-
let key = parachain_header_storage_key(client_state.para_id);
117-
// verify patricia-merkle state proofs
118-
let header = state_machine::read_proof_check::<Host::BlakeTwo256, _>(
119-
relay_chain_header.state_root(),
120-
proof,
121-
&[key.as_ref()],
122-
)
123-
.map_err(|err| anyhow!("error verifying parachain header state proof: {err}"))?
124-
.remove(key.as_ref())
125-
.flatten()
126-
.ok_or_else(|| anyhow!("Invalid proof, parachain header not found"))?;
127-
let parachain_header = H::decode(&mut &header[..])?;
128-
para_heights.push(parachain_header.number().clone().into());
129-
// Timestamp extrinsic should be the first inherent and hence the first extrinsic
130-
// https://github.com/paritytech/substrate/blob/d602397a0bbb24b5d627795b797259a44a5e29e9/primitives/trie/src/lib.rs#L99-L101
131-
let key = codec::Compact(0u64).encode();
132-
// verify extrinsic proof for timestamp extrinsic
133-
sp_trie::verify_trie_proof::<LayoutV0<Host::BlakeTwo256>, _, _, _>(
134-
parachain_header.extrinsics_root(),
135-
&extrinsic_proof,
136-
&vec![(key, Some(&extrinsic[..]))],
137-
)
138-
.map_err(|_| anyhow!("Invalid extrinsic proof"))?;
139-
}
105+
106+
let (hash, proofs) = parachain_header;
107+
finalized
108+
.binary_search(&hash)
109+
.map_err(|err| anyhow!("error searching for relaychain hash: {err}"))?;
110+
let relay_chain_header =
111+
headers.header(&hash).expect("Headers have been checked by AncestryChain; qed");
112+
113+
let ParachainHeaderProofs { extrinsic_proof, extrinsic, state_proof } = proofs;
114+
let proof = StorageProof::new(state_proof);
115+
let key = parachain_header_storage_key(client_state.para_id);
116+
// verify patricia-merkle state proofs
117+
let header = state_machine::read_proof_check::<Host::BlakeTwo256, _>(
118+
relay_chain_header.state_root(),
119+
proof,
120+
&[key.as_ref()],
121+
)
122+
.map_err(|err| anyhow!("error verifying parachain header state proof: {err}"))?
123+
.remove(key.as_ref())
124+
.flatten()
125+
.ok_or_else(|| anyhow!("Invalid proof, parachain header not found"))?;
126+
let parachain_header = H::decode(&mut &header[..])?;
127+
// Timestamp extrinsic should be the first inherent and hence the first extrinsic
128+
// https://github.com/paritytech/substrate/blob/d602397a0bbb24b5d627795b797259a44a5e29e9/primitives/trie/src/lib.rs#L99-L101
129+
let key = codec::Compact(0u64).encode();
130+
// verify extrinsic proof for timestamp extrinsic
131+
sp_trie::verify_trie_proof::<LayoutV0<Host::BlakeTwo256>, _, _, _>(
132+
parachain_header.extrinsics_root(),
133+
&extrinsic_proof,
134+
&vec![(key, Some(&extrinsic[..]))],
135+
)
136+
.map_err(|_| anyhow!("Invalid extrinsic proof"))?;
140137

141138
// 4. set new client state, optionally rotating authorities
142139
client_state.latest_relay_hash = target.hash();
143140
client_state.latest_relay_height = (*target.number()).into();
144-
if let Some(max_height) = para_heights.into_iter().max() {
145-
if max_height != latest_para_height {
146-
Err(anyhow!("Latest parachain header height doesn't match the one in the proof"))?;
147-
}
148-
client_state.latest_para_height = max_height;
141+
142+
if *parachain_header.number() != latest_para_height {
143+
Err(anyhow!("Latest parachain header height doesn't match the one in the proof"))?;
149144
}
145+
client_state.latest_para_height = *parachain_header.number();
146+
150147
if let Some(scheduled_change) = find_scheduled_change::<H>(&target) {
151148
client_state.current_set_id += 1;
152149
client_state.current_authorities = scheduled_change.next_authorities;

contracts/pallet-ibc/src/client.rs

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ where
275275
let cs_state = ics10_grandpa::consensus_state::ConsensusState {
276276
timestamp,
277277
root: header.state_root().as_ref().to_vec().into(),
278+
relaychain_hashes: vec![],
278279
};
279280
let cs = AnyConsensusState::Grandpa(cs_state);
280281

contracts/pallet-ibc/src/light_clients.rs

+1-34
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ use prost::Message;
4848
use sp_core::{crypto::ByteArray, ed25519, H256};
4949
use sp_runtime::{
5050
app_crypto::RuntimePublic,
51-
traits::{BlakeTwo256, ConstU32, Header},
51+
traits::{BlakeTwo256, ConstU32},
5252
BoundedBTreeSet, BoundedVec,
5353
};
5454
use tendermint::{
@@ -161,39 +161,6 @@ impl grandpa_client_primitives::HostFunctions for HostFunctionsManager {
161161
fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool {
162162
pub_key.verify(&msg, sig)
163163
}
164-
165-
fn insert_relay_header_hashes(new_hashes: &[<Self::Header as Header>::Hash]) {
166-
if new_hashes.is_empty() {
167-
return
168-
}
169-
170-
GrandpaHeaderHashesSetStorage::mutate(|hashes_set| {
171-
GrandpaHeaderHashesStorage::mutate(|hashes| {
172-
for hash in new_hashes {
173-
match hashes.try_push(*hash) {
174-
Ok(_) => {},
175-
Err(_) => {
176-
let old_hash = hashes.remove(0);
177-
hashes_set.remove(&old_hash);
178-
hashes.try_push(*hash).expect(
179-
"we just removed an element, so there is space for this one; qed",
180-
);
181-
},
182-
}
183-
match hashes_set.try_insert(*hash) {
184-
Ok(_) => {},
185-
Err(_) => {
186-
log::warn!("duplicated value in GrandpaHeaderHashesStorage or the storage is corrupted");
187-
},
188-
}
189-
}
190-
});
191-
});
192-
}
193-
194-
fn contains_relay_header_hash(hash: <Self::Header as Header>::Hash) -> bool {
195-
GrandpaHeaderHashesSetStorage::get().contains(&hash)
196-
}
197164
}
198165

199166
impl light_client_common::HostFunctions for HostFunctionsManager {

0 commit comments

Comments
 (0)